Angular.js: Opción en $ubicación para permitir el cambio de hash/ruta sin recargar la ruta

Creado en 12 dic. 2012  ·  194Comentarios  ·  Fuente: angular/angular.js

Tenemos un paradigma en nuestra aplicación en el que los usuarios crean cosas nuevas en la misma página en la que ven las cosas. Así que nuestra ruta es como /cosa/:id. Al crear una cosa nueva van a /cosa/nuevo. Una vez que la cosa se haya guardado con éxito, queremos cambiar la ruta a /cosa/1234 (o cualquiera que sea su nueva identificación). Ahora el parcial no necesita ser recargado porque los datos son todos iguales. Solo queremos que la ruta se actualice para que un usuario pueda marcar el enlace correcto, etc.

Tener una opción en $ubicación (no en la definición de la ruta) para habilitar/deshabilitar la carga de la ruta funcionaría, pero estoy seguro de que hay otras formas de implementar la función.

Lots of comments $location ngRoute high confusing feature

Comentario más útil

La solución es esencialmente agregar un parámetro adicional a $ubicación.ruta().

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}]);

Y luego solo usa así

$location.path('/path/that/you/need', false);

Todos 194 comentarios

+1, tengo el mismo problema.

@cgross Dado que el servicio $ubicación utiliza el encadenamiento de métodos de estilo JQuery, y todos los comandos se posponen hasta el resumen de todos modos, sugiero agregar un nuevo método de encadenamiento, por ejemplo, recargar (booleano):

$location.path("/thing/"+thing.id).replace().reload(false)

PD: ¿Es este el Chris Gross que inició el proyecto Eclipse Nebula?

Sip :)

+1 :+1:

+1

En este momento, estamos usando un truco sucio para solucionar este problema.

    $scope.$on('$locationChangeSuccess', function(event) {

        // Want to prevent re-loading when going from /dataEntry/1 to some other dataEntry path
        if ($route && $route.current && $route.current.$route.templateUrl.indexOf('dataEntry') > 0) {
            $route.current = lastRoute; //Does the actual prevention of routing
        }
});

+1

+1

+1

+1

+1

Aquí está mi intento de arreglar esto apoyando preventDefault() por $routeChangeStart . ¿Qué piensas sobre eso?
Confirmar: https://github.com/redhotsly/angular.js/commit/f8ac46e6ac9d76cf855077d21b68b4c2c8043db1

He estado trabajando en el enfoque de @qualidafial durante unos días y ahora el PR está listo. Creé un nuevo método notify que permite omitir la notificación de cambio de ubicación por una vez. Esto debería evitar desencadenar cualquier cambio de ruta o recarga.

Esto no toca el sistema de enrutamiento, por lo que no tiene que cambiar sus definiciones de ruta. Todavía funcionará incluso si usa ui-router lugar del sistema de enrutamiento AngularJS estándar.

Ejemplo: $location.path('/client/2').replace().notify(false);

También he trabajado algunas pruebas y bits de documentación.

+1

otro +1
Usando la solicitud de extracción de @lrlopez ya. Funciona de maravilla.

+1

+1

Lamentablemente, PR https://github.com/angular/angular.js/pull/2398 ha sido rechazado por el equipo de Angular, ya que omitir las notificaciones podría generar inconsistencias entre la URL real y la ruta actual. Hay una larga explicación al final de la discusión.

No necesito esta función en ninguno de mis proyectos, pero la mantendré en mi repositorio para que pueda fusionarse si lo desea. Solo avíseme si se desincroniza con la rama maestra para que pueda reorganizar los cambios. ¡Gracias!

+1

+1, necesito esto :(

+1

Editar: mejor enfoque aquí: https://github.com/angular/angular.js/issues/1699#issuecomment -22511464

Creé una fábrica reutilizable para evitar esto (basado en la idea de @shahmirn):

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
app.factory('DoNotReloadCurrentTemplate', ['$route', function($route) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate not reloading template: ' + $route.current.$$route.templateUrl);
        $route.current = lastRoute;
      }
    });
  };
}]);

Cómo utilizar:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);
}]);

Fuente: http://stackoverflow.com/a/16496112/990356

Si establece "reloadOnSearch" en falso en la ruta, entonces parece corregir los cambios de hash al recargar toda la página.

+1

Esto sería excelente para aplicaciones móviles, donde el ciclo de vida de creación/destrucción de ng-view no es apropiado (mata el rendimiento y la usabilidad).

@tkrotoff Gracias por el ejemplo. Dado que su solución es cancelar las notificaciones de eventos, agregué una transmisión en el rootscope para que otros controladores aún puedan recibir notificaciones del cambio de ruta:

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
app.factory('DoNotReloadCurrentTemplate', ['$route', '$rootScope', function($route, $rootScope) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate not reloading template: ' + $route.current.$$route.templateUrl);
        $rootScope.$broadcast('locationChangedWithoutReload', $route.current);
        $route.current = lastRoute;        
      }
    });
  };
}]);

Entonces:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);

  $rootScope.$on('locationChangedWithoutReload', function(event, route) {
    // set location specific breadcrumbs
    $scope.breadcrumbs = assembleBreadCrumbs();

    // do something for specific route
    if (route.$$route.action == 'item') {
      $scope.item = $scope.items[params.itemId];
      $rootScope.title = $scope.item.name;
    }
  });
}]);

Tengo el mismo problema que el autor del tema y creé una versión modificada de la solución descrita anteriormente.

app.factory('skipReload', [
    '$route',
    '$rootScope',
    function ($route, $rootScope) {
        return function () {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        };
    }
]);

Uso:

app.controller('ThingCtrl', ['$scope', '$http', '$location', 'skipReload', function ($scope, $http, $location, skipReload) {
    ...
    $scope.submit = function () {
        ...
        $http.post('thing', thing).success(function (id) {
            skipReload();
            $location.path('/thing/' + id).replace();
        });
    };
}]);

El uso de un detector de eventos permanente que prueba templateUrl para decidir si permitir la recarga de la ruta, podría interrumpir el enrutamiento a otras vistas que usan el mismo templateUrl. Por ejemplo, enruté a /cosa/nuevo y la vista muestra un enlace a la última cosa agregada /cosa/5, no podré navegar a la cosa 5 ni a ninguna otra cosa (incluso cambiando la URL en el navegador explícitamente). Otro problema con la prueba de templateUrl es que las plantillas se pueden especificar mediante el uso de propiedades de plantilla (como cadena o función).

Este truco permite omitir exactamente una recarga de ruta y no interrumpe el enrutamiento.

También probé una solución menos detallada (para uso en el controlador):

app.factory('location', [
    '$location',
    '$route',
    '$rootScope',
    function ($location, $route, $rootScope) {
        $location.skipReload = function () {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
            return $location;
        };
        return $location;
    }
]);
app.controller('ThingCtrl', ['$scope', '$http', 'location', function ($scope, $http, location) {
    ...
    $scope.submit = function () {
        ...
        $http.post('thing', thing).success(function (id) {
            location.skipReload().path('/thing/' + id).replace();
        });
    };
}]);

Parece funcionar bien.

+1

@sergey-buturlakin su $ubicación.skipReload es simplemente maravilloso. Lo uso en lugar de DoNotReloadCurrentTemplate

@ sergey-buturlakin Su solución parece estar funcionando para mí. ¡Gracias!

después de ejecutar skipReload() desde https://github.com/angular/angular.js/issues/1699#issuecomment -22511464 sigue recargando deshabilitado
parece que un() debería llamarse más tarde

Previene cualquier recarga siguiente y debe llamarse justo antes del cambio de ruta.
Cuando finalice el cambio de ruta, se llamará "un".

sí, pero quería omitir la recarga solo una vez, sin embargo, el enfoque propuesto evita cualquier recarga adicional

+1

+1

+1

PR https://github.com/angular/angular.js/pull/2398 solo salta la recarga una vez. No sé si tiene conflictos de fusión con el maestro actual. Si ese es el caso, solo escríbeme y lo arreglaré.

Utilizo la solución de Sergey, mientras dejo caer la inútil (según yo) variable "un" y la llamada.

Esto lleva a:

  angular.module('services.locationChanger', [])
    .service('locationChanger', ['$location', '$route', '$rootScope', function ($location, $route, $rootScope) {

        this.skipReload = function () {
            var lastRoute = $route.current;
            $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
            });
            return this;
        };

        this.withoutRefresh = function (url, doesReplace) {
            if(doesReplace){
                $location.path(url).replace();
            }
            else {
                $location.path(url || '/');
            }
        };
    }]);

y cualquier persona que llama:

 locationChanger.skipReload().withoutRefresh("/", true);

+1

+1

+1

+1

@mica16 ¿No se usa la variable un para desvincular el evento?

Creé dos plunkrs para probar, con y sin la variable un para desvincular los eventos y parece que sin ella, el evento sigue disparando una y otra vez.

Sí, de hecho, la variable un suena rara.
Según el autor original, se consideraría útil ... pero no estoy convencido de que sea la solución perfecta.

Preferí reescribirlo, pero descarté la función porque ya no la espero.

Así que hazlo si la prueba de tu unidad puede garantizar que funcione como esperas :)

tienen razon chicos
mi solución fue

app.factory 'location', ['$location', '$route', '$rootScope', '$timeout', ($location, $route, $rootScope, $timeout) ->
  $location.skipReload = ->
    lastRoute = $route.current
    un = $rootScope.$on('$locationChangeSuccess', ->
      $route.current = lastRoute
    )
    $timeout((->
      un()
    ), 1000)
    $location

  $location
]

luego en el controlador

location.skipReload().path('/new/path').replace()

+1

+1

+1

+1

otra forma de hacerlo es sacarlo del "paso" de javascript actual para que la ruta no sea consciente del cambio realizado en la ubicación de la ventana:

setTimeout(function() {
    window.location.hash = "";
    if (typeof (history.pushState) !== "undefined") {
        history.pushState("", document.title, location.pathname);
    }
}, 0);

+1

+1

+1

+1

¿Qué pasa con el uso de $anchorScroll? Estaba hurgando en la red y descubrí que hay algunas herramientas para hacer esto. Funciona para mí al menos.

Debería comprobarlo: http://docs.angularjs.org/api/ng. $anchorScroll

+1

¿Es una mala idea? Admito que no lo he probado exhaustivamente. No me gustó la idea de llamar a un método separado antes de establecer la ruta. Desde el punto de vista del uso, al menos esto parece más limpio.

Extendí la función incorporada $ubicación.ruta, usando la técnica de Sergey.

App.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
        var original = $location.path;
        $location.path = function (path, reload) {
            if (reload === false) {
                var lastRoute = $route.current;
                var un = $rootScope.$on('$locationChangeSuccess', function () {
                    $route.current = lastRoute;
                    un();
                });
            }

            return original.apply($location, [path]);
        };
    }])

Uso:

$location.path('/my/path', false);

+1

Las soluciones de

+1

La solución de @EvanWinstanley/sergey se ve bien y no recarga el controlador, pero al cambiar la ruta, se solicitan resoluciones nuevamente.

Mi principal caso de uso para esto sería cambiar manualmente $ubicación.ruta sin recargar las resoluciones/controlador para poder hacer el almacenamiento en caché.

Por ejemplo: tengo una selección que permite al usuario elegir entre múltiples aplicaciones, lo que también cambia $ubicación.ruta para direcciones URL más agradables. Cuando un usuario selecciona una aplicación, los resultados de la llamada API se almacenan en caché. Incluso con la solución, siempre vuelve a resolver, lo cual no es necesario ya que los resultados ya están almacenados en caché.

+1

La solución @sergey-buturlakin funciona bien para mí, no recarga el controlador pero aún recarga la función de reslove y envía la solicitud al servidor.

+1

La solución de @EvanWinstanley no funciona para mí. Por otro lado, la solución de @ mica16 funcionó demasiado bien... (el botón Atrás no actualizaba la página). Intenté escribir mi propia versión sin éxito :(.

+1

+1

+1

+1
Por favor apoye: $ubicación.recargar();

@jvmvik, ¿has probado $route.current.reload() ?

7.1.1

no hay noticias sobre esto después de más de un año?

+1

+1

He mejorado un poco la solución de Sergey.
Ahora admite, por ejemplo, direcciones URL personalizadas para pestañas (pestañas de navegación) sin recargar toda la página.

// Original: https://github.com/angular/angular.js/issues/1699#issuecomment-22511464
//
// Usage:
//
// (interception is needed for Back/Forward buttons to work)
//
// location.intercept($scope._url_pattern, function(matched) {
//   // can return false to abort interception
//   var type = matched[1]
//   if (!type) {
//     return;
//   }
//   $scope.safeApply(function() {
//     $scope.data_type = type; 
//     $scope.params.page = 1; 
//     $scope.get_data(); 
//   });
// });
// 
// anywhere in your controller: 
// location.skipReload().path(url);
//
// to replace in history stack:
// location.skipReload().path(url).replace();

app.factory('location', [
    '$location',
    '$route',
    '$rootScope',
    function ($location, $route, $rootScope) {
        var page_route = $route.current;

        $location.skipReload = function () {
            //var lastRoute = $route.current;
            var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = page_route;
                unbind();
            });
            return $location;
        };

        if ($location.intercept) {
            throw '$location.intercept is already defined';
        }

        $location.intercept = function(url_pattern, load_url) {

            function parse_path() {
                var match = $location.path().match(url_pattern)
                if (match) {
                    match.shift();
                    return match;
                }
            }

            var unbind = $rootScope.$on("$locationChangeSuccess", function() {
                var matched = parse_path();
                if (!matched || load_url(matched) === false) {
                  return unbind();
                }
                $route.current = page_route;
            });
        };

        return $location;
    }
]);

+1

+1

Otra solución para evitar que la ruta se vuelva a cargar al cambiar la ruta de $ubicación es establecer los parámetros de ruta de la ruta actual para que coincidan con la nueva ruta de ubicación.

En mi configuración de $routeProvider, tengo una ruta a /tasks de la siguiente manera

when('/tasks/:section/:taskId?', {
      templateUrl: 'partials/tasks.html',
      controller: 'TaskCtrl',
      reloadOnSearch: false
      })

La ruta anterior tiene dos secciones pathParams y taskId (taskId es opcional) y reloadOnSearch está establecido en false.

Para navegar de decir /tareas/bandeja de entrada a /tareas/bandeja de entrada/122 sin recargar la ruta, configure el parámetro de ruta de ID de tarea antes de cambiar la ruta de $ubicación de la siguiente manera

$scope.showTask = function(task) {
    // set taskId to prevent route reloading
    $route.current.pathParams ['taskId'] = task.id;
    $location.path('/tasks/'+$route.current.pathParams ['section']+'/'+task.id);
};

Tenga en cuenta que no es necesario configurar la sección pathParam, ya que la ruta ya la configuró.

Cuando la ruta descubre que los pathParams extraídos de la nueva ruta de $ubicación coinciden con los pathParams de la ruta actual (y reloadOnSearch está establecido en falso), no volverá a cargar la ruta, sino que se activará un evento $routeUpdate.

Para volver de /tasks/inbox/122 a /tasks/inbox sin volver a cargar la ruta, elimine taskId pathParam antes de cambiar la ruta de $ubicación de la siguiente manera

$scope.back = function () {
    // delete taskId to prevent route reloading
    delete $route.current.pathParams ['taskId'];
    $location.path('/tasks/'+$route.current.pathParams ['section']);
};

+1

+:100:

¿Leíste el artículo de Derick Bailey sobre el enrutador Backbone http://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ ? Backbone proporciona esta opción y, según Derick, siempre debe evitar activar una recarga (que parece lo contrario de lo que parece hacer el enrutador de angular aquí).
Hay 48 "+1" en esta publicación ... No creo que todas estas personas estén haciendo algo mal. Tener que almacenar var lastRoute = $route.current; y restaurarlo más tarde en $locationChangeSuccess con $route.current = lastRoute; parece bastante complicado.
@IgorMinar , ¿tiene alguna idea?

OK, soy dueño de este tema durante la próxima semana para que finalmente podamos cerrar esto.

Lo primero es lo primero, no creo que la ruta deba recargarse cuando cambia el valor #hash.

website.com/#/my-route#top

website.com/#/my-route#bottom

Entonces, cuando la parte superior cambia a la parte inferior, no debería recargarse, pero si el valor de mi ruta cambia, debería hacerlo. Todos podemos estar de acuerdo en esto, ¿verdad?

En segundo lugar, lo que es más importante, necesitamos una manera fácil de señalar que la ruta de la ruta no se volverá a cargar cuando se cambie, pero esto puede ser problemático si cambia un fragmento anterior de la ruta. Entonces, ¿por qué no tenemos una notación diferente para un parámetro de ruta usando dos puntos :: para señalar que actúa como un valor comodín?

// this will reload the page when the id changes like normal
$routeProvider.register('/users/:id')

// this will reload the page when the id changes, but not the section (since two colons are used)
$routeProvider.register('/users/:id/::section')

¿Qué piensan ustedes?

Esto realmente no ayuda con la situación desde la que desea pasar:

/users/new

a

/users/123

una vez creado el nuevo usuario y asignado un id, sin recargar el usuario, ya que ya se encuentra allí.

Porque de alguna manera uno tiene que asignar el nuevo objeto de usuario al objeto de usuario actual con id 123. Esto es más bien específico de la aplicación, ¿no?

Por lo que vale, Ember Router no admite cambiar la URL sin hacer la transición tampoco:

http://stackoverflow.com/questions/19076834/cambiar-url-sin-transición

@matsko ¿Podemos empoderar al ingeniero y dejar que él o ella decida si la ruta debe provocar un cambio o no?
Estoy bien con buenos valores predeterminados.
Cree una forma para que el ingeniero pueda cambiar la URL sin ejecutar toda la cadena de eventos de recarga. Personalmente, estoy de acuerdo con agregar un parámetro adicional opcional con opciones (por ejemplo: {recargar: falso}) en ".path()", ".search()", etc.

@ChrisCinelli sí, pero ¿qué sucede cuando hay dos parámetros en una definición de ruta? ¿Cómo puedes evitar que uno recargue mientras permites que el otro lo haga? La bandera sería una característica de todo o nada.

@matsko Entonces, en el extremo de la implementación, ¿se está considerando que podríamos elegir qué parámetros causan recargas y cuáles no? Y si es así, ¿ya tiene un enfoque en mente o es ahí donde nos encontramos con un problema?

@cgross cherry-pick sí (usando los dos puntos dobles). Esto es solo una idea por ahora, así que nada concreto. Si esto está en línea con los planes para angular 2.0, entonces podemos usarlo o intentar usar algo que 2.0 planea tener o algo que Dart ya tiene (para mantener la API consistente).

Creo que sería perfecto y se vería bien (al menos en mi opinión) como parte de la ruta.

$routeProvider,when("/:categoryName/:id[reload=no]", { ... })

Y luego, en el controlador

$scope.$on("$routeChangeSuccess", function () {
  if ($routeParams.id !== $scope.activeId) {
    // React to ID update without reload
  }
});

Sí, algo así, pero los dos puntos dobles '::id' se utilizarían en lugar de :id[reload=no] .

Tenga en cuenta que esto es solo una idea, pero mucha gente aquí quiere esta función.

Estaba pensando en términos de quizás eventualmente agregar otras opciones, pero supongo que realmente no hay otras opciones que puedan existir. También es bueno ver que esta solicitud se está tomando en serio. Las URL bonitas son un gran problema para muchos de nosotros.

Matsko +1 para dos puntos dobles, pero creo que también sería útil poder pasar opciones a path() también para algunos casos extremos mencionados anteriormente

+1 por dos puntos dobles

@matsko me pidió que

El nuevo enrutador tiene un método navigate que puede pasar una _url_ y un objeto _options_ opcional. Esto se basa en las capacidades de historial del enrutador Backbone, por lo que admite dos opciones replace y trigger Si desea pasar de /users/new a /users/123 sin provocando una navegación de pantalla, simplemente especifique trigger:false . Si también desea que reemplace la entrada del historial /users/new con la nueva URL basada en ID, también debe especificar replace:true .

La actualización de rutas de una ruta _nueva_ a una ruta _basada en id_ sin causar transformaciones DOM, etc. es un caso de uso clave para esta API. Otro ejemplo común es la paginación. Por ejemplo, si tiene una cuadrícula y desea que el usuario recorra los datos, puede usar esto para actualizar el fragmento con la información de la página actual sin provocar la navegación. Estoy seguro de que hay otros usos, pero estos son los que he visto repetidamente en mi consultoría a lo largo de los años.

Espero que esta información ayude. ¿Quizás el enrutador 1.x pueda adoptar esta técnica?

@EisenbergEffect para confirmar. Para 1.x, el método para hacer esto sin cambiar la ruta se vería así:

$location.path(value, { trigger : false })

Y esto reemplazaría efectivamente la ruta sin hacer que la nueva ruta + controlador se dé cuenta. Esto podría funcionar por ahora, pero ¿cómo te sientes acerca de la idea de dos puntos :: ?

Para todos los demás aquí, ¿preferirían que el cambio de URL (sin recargar la ruta) sea facilitado a través de la ruta o por el desarrollador cuando se cambia la URL?

Debe haber una fuente central de información para la ubicación, como un servicio que
las rutas y los navegadores pueden referirse e influir, pero sigue siendo el único
verdad
El 1 de julio de 2014 a las 23:28, "Matias Niemelä" [email protected] escribió:

@EisenbergEffect https://github.com/EisenbergEffect para confirmar. Para
1.x, el método para hacer esto sin cambiar la ruta se vería así:

$ubicación.ruta(valor, { disparador : falso })

Y esto reemplazaría efectivamente la ruta sin causar la nueva ruta

  • controlador para tomar conciencia. Esto podría funcionar por ahora, pero ¿cómo te sientes?
    sobre la idea del doble :: colon?

Para todos los demás aquí, ¿preferirían cambiar la URL (sin
recargar la ruta) ser facilitado a través de la ruta o por el desarrollador
cuando se cambia la url?


Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/angular/angular.js/issues/1699#issuecomment-47741350 .

Aquí hay otra idea... Permita dos nuevos métodos en cada definición de ruta, digamos entering(transitionInfo) y leaving(transitionInfo) o algo así. Si existen, entonces se llamaría al método leaving en la ruta anterior antes de salir y luego se llamaría al método entering() en la ruta nueva. El transitionInfo sería un objeto similar al siguiente:

{
  newUrl: ...
  newRoute: ...
  oldUrl: ...
  oldRoute: ...
  cancelRouteChange: false,
  ignoreRouteChange: false
}

En este método, podemos verificar las rutas/URL antiguas y nuevas y luego, si desea que la URL cambie pero no vuelva a ejecutar la transición de la ruta, configure ignoreRouteChange en true . Si desea cancelar el cambio de ruta y volver a la ruta anterior, establezca cancelRouteChange en verdadero.

Esto le da al desarrollador control completo sobre cuándo ocurren las transiciones de ruta, funcionará si la URL se modifica directamente en la barra de direcciones y también ubica la funcionalidad para decidir sobre un cambio de ruta en particular cerca de la definición de las rutas individuales que se preocupan por el cambio. .

Creo que hay algo similar en el enrutador AngularV2 llamado activate() . @EisenbergEffect ¿puedes confirmarlo?

¿Qué piensas?

@matsko Sí, esa API podría verse así. También es posible que desee considerar implementar la opción replace , ya que también se necesita con frecuencia. En este caso particular de /users/new a /users/123 , creo que aprovecharía ambas opciones, ya que en realidad no desea que /users/new permanezcan en la pila posterior después de que el nuevo usuario sea creado.

Con respecto a :: , no estoy seguro de que sea del todo útil. La razón es que, en el ejemplo de usuarios anterior, no es el patrón de URL lo que importa, sino el contexto específico de la aplicación. Por ejemplo, si pasa de customers a users/123 entonces desea notificar. Además, si abandonas users/new y vas a users/456 entonces también querrás notificar. Solo cuando está en users/new y ha finalizado la creación del nuevo usuario, desea simplemente actualizar el fragmento para representar la identificación del usuario recién creado sin causar ninguna transición. Esto depende en gran medida de lo que el usuario esté haciendo realmente dentro del contexto de esa pantalla de usuario en ese momento.

@petebacondarwin Un problema con esa idea es que el conocimiento de la asignación de la notificación de ruta no es algo que esté disponible globalmente junto con las definiciones de ruta. Más bien, está incrustado dentro del controlador de usuario particular, ya que ese controlador es la pieza de código que sabe si un nuevo usuario se está creando o no en este momento en lugar de simplemente navegar desde la pantalla del nuevo usuario a un usuario preexistente.

Para mí, el camino a seguir es dar control a la persona que llama que quiere cambiar una ruta. Así que definitivamente estoy a favor con dos opciones replace y trigger .

@EisenbergEffect : tienes razón. En general, esta información no está disponible de inmediato, aunque me imagino implementándola de la siguiente manera:

Tener un servicio llamado currentUser , que contiene el usuario con el que estamos trabajando actualmente. Me gusta hacer esto en general, ya que no me siento cómodo sobrecargando mis controladores con código. Si esto parece demasiado específico, entonces podría tener currentEntity , que es la "cosa" actual que está editando/viendo.

Luego, el método route enter() podría inyectar esto y decidir si navegar a esta ruta realmente cambiaría la entidad actual. Si no es así, no nos molestamos en ejecutar la transición, simplemente actualice la URL.

Aprecio que esto podría volverse un poco difícil de manejar en una aplicación grande, pero pensé en lanzarlo como una opción.

¿Cuáles son los argumentos en contra de dar a la persona que llama (es decir, la que solicita el cambio de ruta/url) el control sobre si la transición de ruta realmente ocurre? Recuerdo que a @IgorMinar le preocupaba que esto rompiera la restricción de que un estado de la aplicación se

@petebacondarwin Eso podría funcionar para este escenario específico, pero se vuelve un poco más complicado manejar el ejemplo de paginación que mencioné anteriormente, que querría no activar ni reemplazar, mientras que el nuevo escenario de usuario no quiere activar sino reemplazar.

Abordar el tema estatal es un poco más complicado. La razón es que tenemos dos URL que representan el mismo estado del controlador... pero la diferencia está en el modelo. Entonces, en realidad tenemos dos estados diferentes en realidad... pero desde una perspectiva de UX, no queremos sacudir al usuario al hacer la transición. Desde una perspectiva de rendimiento, no queremos desechar innecesariamente DOM y recrear todo. Entonces, en el caso del nuevo usuario => transición de usuario guardada, realmente queremos evitar la activación y también reemplazar el fragmento para que el botón Atrás _no_ lo lleve de regreso a la pantalla del nuevo usuario. Internamente, el enrutador o el mecanismo de ubicación solo necesitarían rastrear cuál es el fragmento actual. (Estoy suponiendo un poco allí ya que no sé cómo funciona ng1.x en este sentido. Con 2.x nos aseguramos de rastrear cuidadosamente el fragmento actual/anterior independientemente de cómo se haya cambiado). En el escenario de paginación, no queremos desencadenar porque queremos un buen UX para paginar los datos y no queremos la eliminación innecesaria de DOM. Pero SÍ queremos que se mantenga el fragmento anterior para que el usuario pueda usar el botón Atrás para retroceder a través de las páginas de datos.

Tenga en cuenta que en ambos casos, no se trata solo de la URL y el estado de la aplicación, sino también de cómo hacemos la transición del estado actual al nuevo estado. Nada de esto importa si establece un vínculo profundo directamente a la nueva vista. Sí importa cuando se está pasando de uno a otro dentro del contexto de estos casos tan específicos.

+1

+1

+1

+1
Y también función para cambiar la URL sin cambiar la ruta

+1: ¡es bueno ver que no estoy solo queriendo esto!

+1, aún no he agregado (

+1

+1

+1

+1

+1

+1

+1

:+1:

¿Tú también @basarat? :sonrisa:

@johnnyreilly solo tiene un servicio en el que inyecta $route y $location y puede tener esta función:

/** navigate */
    public navigate(url: string, reload = true) {
        this.$location.url(url);

        // Don't do a controller reload on this navigation. Note: *you* need to be careful not to actually *need* a controller reload
        if (!reload) {
            var lastRoute = this.$route.current;
            var unsub = this.$rootScope.$on('$locationChangeSuccess', () => {
                this.$route.current = lastRoute; // fake this to make the route logic think nothing changed
                unsub();
            });
        }
    }

Buen @basarat!

+1

+1

Una cosa que estoy notando con las soluciones actuales es que mi bloque de resolución del enrutador todavía se está ejecutando. ¿Alguien más notó esto? Fue bastante fácil pasar algo en el objeto $route.current, pero eso parece complicado. Además, ¿parece que este problema nunca se solucionará? :llorar:

+1

+1

Para aquellos de ustedes que no quieren tener que retroceder... por lo que puedo decir, esto es lo que tenemos hasta ahora (lo siento, coffeescript). Desafortunadamente, esto no funciona al 100% para mí. Todavía estoy ejecutando mi resolve y los botones atrás/adelante parecen estar rotos ahora.

#
# Special $location.path function that allows you to change the url, but not
# send things through the router.
# <strong i="7">@see</strong> https://github.com/angular/angular.js/issues/1699
#
@app.run(['$rootScope', '$location', '$route', '$timeout', ($rootScope, $location, $route, $timeout) ->

    originalPath = $location.path

    $location.path = (url, reload=true) ->
        if ! reload
            lastRoute = $route.current
            if lastRoute
                unsub = $rootScope.$on('$locationChangeSuccess', ->
                    $route.current = lastRoute # fake this to make the route logic think nothing changed
                    $route.current.ignore = true
                    unsub()
                )
                # make sure to clean up and unregister the unsub function
                $timeout( ->
                    unsub()
                , 500)

        originalPath.apply($location, [url])

])

@lookfirst está llamando a unsub dos veces. Preferir: https://github.com/angular/angular.js/issues/1699#issuecomment -54931571

@basarat encogiéndose de hombros...

unsubbed = false
unsub = ... ->
    if ! unsubbed
        ...
        unsubbed = true
        unsub()

$timeout(unsub, 500)

De todos modos, esta solución es un desastre total. Rompe todo tipo de cosas. Realmente necesitamos una solución angular aprobada.

porque descubrí que unsub() no siempre se llamaba en su ejemplo preferido

@lookfirst estás usando location.path Estoy usando location.url . Eso podría estar impactándolo.

Simplemente quisquilloso (me gusta ser útil): en lugar de originalPath.apply($location, [url]) puedes hacer originalPath.call($location, url) (es posible que ya lo sepas)

Interesante... location.url funciona mucho mejor (¡el botón Atrás funciona de nuevo!), pero parece estar causando otro problema por mi parte. Tendré que profundizar más en esto cuando no haya estado codificando durante 10 horas seguidas. ¡Gracias por los consejos y la discusión, de verdad!

+1

+1

+1

@kuchumovn Tu solución es genial. Un problema que tengo es que $routeParams no cambia después de cambiar la ruta. Lo resolví reemplazando el valor de $routeParams dentro de los detectores de eventos. No estoy seguro de si esta es la forma correcta, pero funciona para mí.

app.factory('Location', [
    '$location',
    '$route',
    '$rootScope',
    '$routeParams',
    function ($location, $route, $rootScope, $routeParams) {
        var page_route = $route.current;

        $location.skipReload = function () {
            //var lastRoute = $route.current;
            var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                angular.copy($route.current.params, $routeParams);
                $route.current = page_route;
                unbind();
            });
            return $location;
        };

        if ($location.intercept) {
            throw '$location.intercept is already defined';
        }

        $location.intercept = function(url_pattern, load_url) {

            function parse_path() {
                var match = $location.path().match(url_pattern);
                if (match) {
                    match.shift();
                    return match;
                }
            }

            var unbind = $rootScope.$on("$locationChangeSuccess", function() {
                var matched = parse_path();
                if (!matched || load_url(matched) === false) {
                  return unbind();
                }
                angular.copy($route.current.params, $routeParams);
                $route.current = page_route;
            });
        };

        return $location;
    }
]);

+1

+1

Ayer hablé en persona con

¿Por qué quieres simplemente fusionar ui-router en angular?
El enrutador uui parece estar resolviendo bastante cualquier cosa que se pueda necesitar.

de acuerdo con @elennaro. aunque ui-router requiere algo de creatividad y estudio para que funcione, funciona en todos los escenarios con los que me he encontrado. curioso ¿cuáles son las desventajas de adoptar ui-router y arrojarle recursos de Google?

Parece que ninguna de las soluciones no ejecutará la resolución, ¿alguna idea?

+1

+1

+1 por $location.path(value, { trigger : false })
¿Algún progreso en esto?

+1

+1

+1 ¿qué pasa con un método completamente separado? dejando path() como está, y simplemente agregando un método $ location.hash que simplemente cambia la URL del navegador sin recargar el controlador. esto nos permite controlar la navegación hacia atrás del navegador y es un escenario de caso de uso válido en aplicaciones ricas.

+1

Actualmente usando el fragmento aquí en esta página: http://joelsaupe.com/programming/angularjs-change-path-without-reloading/

+1

+1

+1

Una solución que encontré fue agregar un estado secundario con una URL absoluta.

$stateProvider.state('state.child', {
    url: '^/some-other-url'
});

Luego simplemente haga $state.go('.child') y eso debería cambiar la URL a la que desea sin recargar y mantener a todos contentos. Tenga en cuenta que el símbolo de intercalación, que indica que la URL no se heredará del padre... me tomó un tiempo darme cuenta de esto.

+1

+1

+1

+1

+1

+1

+1

@btford, ¿cómo se trata esto (si es que se trata) en el componentRouter ?

Gracias @balsarori por una solución simple y funcional .

Elaborado además para permitir la actualización de los parámetros de la ruta $route , sin recargar el controlador.

Utiliza console.log lugar de $log ya que simplemente podemos eliminar todos los registros para compilaciones de producción con uglifyjs.

Editar: corregir cuando se usa en un proyecto:

/**
 * parameter handling
 */

'use strict';

angular.module('route-params', [
  'ng',
  'ngRoute',
])
.service('routeParams', function(
  $route,
  $location
) {
  var R = {};

  /**
   * <strong i="15">@ngdoc</strong> method
   * <strong i="16">@name</strong> routeParams#replace
   *
   * <strong i="17">@description</strong>
   * Replace route params, including path params, without reloading controller.
   *
   * Causes `$route` service to update the current URL, replacing
   * current route parameters with those specified in `params`.
   *
   * Provided property names that match the route's path segment
   * definitions will be interpolated into the location's path, while
   * remaining properties will be treated as query params.
   *
   * If `options.reload` is truthy, the current controller is reloaded.
   *
   * If `options.history` is truthy, the browser history is updated.
   * If `options.history` is undefined, the browser history is only updated
   * when changing value of any existing `$route` parameter,
   * ie. not when adding a previously undefined `$route` parameter.
   *
   * <strong i="18">@param</strong> {!Object<string, string>} params mapping of URL parameter names to values
   * <strong i="19">@param</strong> {!Object<string, boolean>} options `history` and `reload` options.
   *
   * <strong i="20">@returns</strong> {Object} self
   */
  R.replace = function(params, options) {
    var key, value, updateRequired;
    options = options || {};

    // Convert params to strings, and check if they differ from current route params.
    // If `options.history` is undefined, and passed params update any current route params, then set it to true.
    var currentParams = $route.current.params;

    console.log('route: params: replace', {last: angular.copy(currentParams), next: params,
      route: $route.current, options: options});

    if (!$route.current.$$route) {
      // Can't change location.
      return;
    }
    var currentPath = $route.current.$$route.originalPath;
    var history = options.history;

    for (key in params) {
      // jshint eqnull: true
      value = params[key] = (params[key] == null ? undefined : params[key] + '');

      if (value !== currentParams[key]) {
        updateRequired = true;

        console.log('route: params: replace: ' + (currentPath.search(':\\b' + key + '\\b') ? 'path.' : 'search.') +
          key + ' = ' + (key in $route.current.params ? $route.current.params[key] + ' -> ' : '') + params[key]);

        if (history === undefined && key in currentParams) {
          console.log('route: params: replace: update history: ' + key + ' = ' + currentParams[key] + ' -> ' + value);
          history = true;
        }
      }
    }

    if (updateRequired) {
      // If `options.reload` is falsey, then set current route `reloadOnSearch` to false,
      // and make passed path params equal with current route path params,
      // so that `$route` treats the change as update only, and does not broadcast `$routeChangeStart` event.
      //
      // See https://github.com/angular/angular.js/issues/1699#issuecomment-45048054
      // and https://github.com/angular/angular.js/tree/v1.3.x/src/ngRoute/route.js#L484
      // and https://github.com/angular/angular.js/tree/v1.3.x/src/ngRoute/route.js#L539
      if (!options.reload) {
        // Set current route `reloadOnSearch` to false.
        $route.current.$$route.reloadOnSearch = false;

        // Add any passed path params to current route path params, and convert them to strings.
        // Path params are detected by searching for respective name group `:key[*?]` in current route path.
        for (key in params) {
          if (currentPath.search(':\\b' + key + '\\b') !== -1) {
            $route.current.pathParams[key] = params[key];
          }
        }

        // Add any current route path params to passed params, if not set there already.
        for (key in $route.current.pathParams) {
          if (!(key in params)) {
            params[key] = $route.current.pathParams[key];
          }
        }

        // TODO: push state if `options.history` is truthy.
      }
      // If `options.history` is falsey, and controller is reloaded,
      // then make `$location` replace current history state, instead of pushing a new one.
      else if (!history) {
        $location.replace();
      }

      $route.updateParams(params);

      // Update current route params, so the change is reflected immediatelly,
      // and nearby replace() call work correctly.
      for (key in params) {
        $route.current.params[key] = params[key];
      }
    }

    return R;
  };

  return R;
});

¿Cuál es el estado actual de este problema? ¿Se solucionará en el nuevo router? ¿A tiempo para el lanzamiento de 1.4? ¿Pronto?

Tengo el mismo problema pero con parámetros de cadena de consulta. No quiero una recarga al cambiarlos. ¡Pero no puedo deshabilitar reloadOnSearch porque necesito saber si el usuario cambia la cadena de consulta manualmente! :)

¡Gracias!

@marcalj fwiw, pirateamos esta solución con un servicio que mantuvo la URL sincronizada con la barra de direcciones para que pudiéramos deshabilitar reloadOnSearch y tomar los parámetros de consulta. Además, los usuarios pueden navegar por la URL directamente a través de un marcador o enlace.

¿Con ng-router o ui-router? ¿Puedes proporcionarme un enlace con el código o algo?

Tal vez esto podría resolverse usando un nuevo enrutador, puedo esperar ya que mi proyecto aún no está terminado.

¡Gracias!

@marcalj nuestro ejemplo es un poco intrincado y posiblemente desactualizado, pero mire https://github.com/irthos/mngr/blob/master/app.js y verifique la línea 10. usa nuestro método api.loadState que identifica parámetros en el url para construir el estado apropiado.

NgSilent fue la solución para mí.
Mire https://github.com/garakh/ngSilent
Probado con ngRoute 1.3.15

Utilicé las soluciones @sergey-buturlakin y @kuchumovn , y continuamente me encuentro con el mismo problema.

En la página en cuestión (pagex), uso location.skipReload y funciona muy bien: puedo actualizar la ruta una y otra vez sin cambiar la ruta. Si luego uso $ubicación.ruta real para cambiar la ruta de pagex a un nuevo controlador, funciona, pero si presiono el botón Atrás para volver a pagex y luego trato de navegar a otra página, ya no puedo cambiar mi ruta nunca más sin actualizar el navegador. ¿Hay alguna forma de evitar esto?

+1

+1. Me gusta el enfoque de doble colon que sugirió Matías

+1

Es un caso de uso común que la aplicación de una sola página no vuelva a cargar la página en el cambio de ruta,
muy extraño por qué aún no está incrustado en angular.
Complemento creado basado en la solución
https://github.com/anglibs/angular-ubicación-actualización

Repasemos esto en 1.5. Hay un PR relacionado en #5860

¡Necesito la misma función para actualizar la cadena de consulta sin activar el controlador de ruta! :)

Algo como: $location.searchSilent('sf', SearchFilter.getForRouteParams());

$ubicación.ruta(href);
ámbito.$aplicar();
es la solucion!!
necesitamos informar para angular el cambio con $apply()

Este hilo no ha estado abierto durante 2 años porque angular no había sido notificado: queremos cambiar una parte de la ruta sin invocar todo.

@petebacondarwin , sería bueno tener no solo la actualización de la ruta de ubicación sin recargar, sino también la actualización de los parámetros de la ruta sin recargar ... como se implementó anteriormente https://github.com/angular/angular.js/issues/1699#issuecomment -96126887

¡La solución de @jicasada me funcionó perfectamente! ¡Gracias!

@jicasada ¿funciona esto con ui-router? He planteado un error con ui-router donde reloa dOnSearch:false no funciona si el estado principal y el secundario comparten la misma URL. Si hago la transición al estado secundario con un parámetro de solicitud actualizado, llamará al controlador principal dos veces.

+1

+1

Confirmo que ngSilent está haciendo perfectamente el trabajo. Aun así, una solución oficial sería muy apreciada.

La solución es esencialmente agregar un parámetro adicional a $ubicación.ruta().

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}]);

Y luego solo usa así

$location.path('/path/that/you/need', false);

Debería poder hacer $ubicación.reemplazar() al igual que la función window.location.replace. Esto es una tontería porque cada creación en una operación CRUD siempre tendrá este problema.

(y) +1

+1

Te aconsejo que uses "parámetro de búsqueda" así:
/thing?id=1234
No configure el parámetro en el proveedor del enrutador, no recargará la vista y el controlador.
Si quieres obtener una identificación
$location.search().id //1234
@cgross

Demostración http://codepen.io/dolymood/details/jrEQBx/
Usa decorator para decorar ngViewDirective

+1

+1

+1

+1

+1

+1

+!

Ya basta de +1 :-)
Recibimos el mensaje. ¡Vamos a ver esto antes de Navidad!

Cerrado por #15002.

¿Fue útil esta página
0 / 5 - 0 calificaciones