AngularJS UI Router: cambie la URL sin recargar el estado

134

Actualmente nuestro proyecto está usando el predeterminado $routeProvider, y estoy usando este "hack", para cambiar urlsin volver a cargar la página:

services.service('$locationEx', ['$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;
}]);

y en controller

$locationEx.skipReload().path("/category/" + $scope.model.id).replace();

Estoy pensando en reemplazar routeProvidercon ui-routerpara las rutas de anidación, pero no puedo encontrar esto en ui-router.

¿Es posible, hacer lo mismo con angular-ui-router?

¿Por qué necesito esto? Permítanme explicarlo con un ejemplo: la
ruta para crear una nueva categoría es /category/new después clickingde GUARDAR que muestro success-alerty quiero cambiar la ruta /category/newa /caterogy/23(23 - es la identificación del nuevo elemento almacenado en db)

vuliad
fuente
1
en ui-router no tiene que definir una URL para cada estado, puede navegar de un estado a otro sin cambiar la URL
Jonathan de M.
¿Desea actualizar la URL completa o solo la ruta de búsqueda? Estaba buscando una solución que actualizara la ruta de búsqueda y la encontré aquí: stackoverflow.com/questions/21425378/…
Florian Loch
@johnathan enserio? Me encantaría hacer eso, mostrando solo una URL, pero $urlRouterProvider.otherwiseparece funcionar en una URL, no en un estado. Hmmm, tal vez podría vivir con 2 URL, o encontrar alguna otra forma de indicar que es una URL no válida.
Mawg dice que reinstale a Mónica el

Respuestas:

164

Simplemente puede usar en $state.transitionTo lugar de $state.go . $state.go llama $state.transitionTo internamente pero establece automáticamente las opciones en { location: true, inherit: true, relative: $state.$current, notify: true } . Puedes llamar $state.transitionTo y configurar notify: false . Por ejemplo:

$state.go('.detail', {id: newId}) 

puede ser reemplazado por

$state.transitionTo('.detail', {id: newId}, {
    location: true,
    inherit: true,
    relative: $state.$current,
    notify: false
})

Editar: como lo sugiere fracz, simplemente puede ser:

$state.go('.detail', {id: newId}, {notify: false}) 
rezKesh
fuente
16
¿No es "mantener url al recargar estado" en lugar de "cambiar url sin recargar estado"?
Peter Hedberg
25
para mí funcionó con lo siguiente: $state.transitionTo('.detail', {id: newId}, { location: true, inherit: true, relative: $state.$current, notify: false }) básicamente, establezca la notificación a falso y la ubicación a verdadero
Arjen de Vries
66
@ArjendeVries Sí, funciona como se esperaba pero encontré un comportamiento inesperado. Después de jugar con muchas llamadas de método de transición a (sin recargar) cuando finalmente se mueve a un nuevo estado (es decir, url), reinicia el controlador anterior.
Premchandra Singh
2
@Premchandra Singh: teniendo el mismo problema aquí. Al salir del estado, el controlador anterior se inicializa nuevamente. Tengo el mismo problema con la solución aceptada de wiherek. Ver también aquí github.com/angular-ui/ui-router/issues/64
martinoss
15
Aún más simple: $state.go('.detail', {id: newId}, {notify: false}).
fracz
48

Ok, resuelto :) El enrutador de IU angular tiene este nuevo método, $ urlRouterProvider.deferIntercept () https://github.com/angular-ui/ui-router/issues/64

básicamente se reduce a esto:

angular.module('myApp', [ui.router])
  .config(['$urlRouterProvider', function ($urlRouterProvider) {
    $urlRouterProvider.deferIntercept();
  }])
  // then define the interception
  .run(['$rootScope', '$urlRouter', '$location', '$state', function ($rootScope, $urlRouter, $location, $state) {
    $rootScope.$on('$locationChangeSuccess', function(e, newUrl, oldUrl) {
      // Prevent $urlRouter's default handler from firing
      e.preventDefault();

      /** 
       * provide conditions on when to 
       * sync change in $location.path() with state reload.
       * I use $location and $state as examples, but
       * You can do any logic
       * before syncing OR stop syncing all together.
       */

      if ($state.current.name !== 'main.exampleState' || newUrl === 'http://some.url' || oldUrl !=='https://another.url') {
        // your stuff
        $urlRouter.sync();
      } else {
        // don't sync
      }
    });
    // Configures $urlRouter's listener *after* your custom listener
    $urlRouter.listen();
  }]);

Creo que este método actualmente solo se incluye en la versión maestra del enrutador angular ui, el que tiene parámetros opcionales (que también son buenos, por cierto). Necesita ser clonado y construido desde la fuente con

grunt build

Los documentos también son accesibles desde la fuente, a través de

grunt ngdocs

(se integran en el directorio / sitio) // más información en README.MD

Parece que hay otra forma de hacerlo, mediante parámetros dinámicos (que no he usado). Muchos créditos a nateabele.


Como nota al margen, aquí hay parámetros opcionales en $ stateProvider del enrutador de IU angular, que utilicé en combinación con lo anterior:

angular.module('myApp').config(['$stateProvider', function ($stateProvider) {    

  $stateProvider
    .state('main.doorsList', {
      url: 'doors',
      controller: DoorsListCtrl,
      resolve: DoorsListCtrl.resolve,
      templateUrl: '/modules/doors/doors-list.html'
    })
    .state('main.doorsSingle', {
      url: 'doors/:doorsSingle/:doorsDetail',
      params: {
        // as of today, it was unclear how to define a required parameter (more below)
        doorsSingle: {value: null},
        doorsDetail: {value: null}
      },
      controller: DoorsSingleCtrl,
      resolve: DoorsSingleCtrl.resolve,
      templateUrl: '/modules/doors/doors-single.html'
    });

}]);

lo que hace es que permite resolver un estado, incluso si falta uno de los parámetros. SEO es un propósito, legibilidad otro.

En el ejemplo anterior, quería que doorsSingle fuera un parámetro obligatorio. No está claro cómo definirlos. Sin embargo, funciona bien con múltiples parámetros opcionales, por lo que no es realmente un problema. La discusión está aquí https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090

wiherek
fuente
Parece que su parámetro opcional no funciona. Error: Both params and url specicified in state 'state'. Dice en los documentos que este es un uso no válido también. Un poco decepcionante.
Rhys van der Waerden
1
¿Construiste desde el maestro? Tenga en cuenta que en el momento en que agregué la solución, los parámetros opcionales solo se incluyeron en la versión que tuvo que construirse manualmente desde la fuente. esto no se incluirá en la versión hasta que salga la v.0.3.
wiherek
1
Solo una nota rapida. Gracias a nateabele, los parámetros opcionales están disponibles en v0.2.11, que se lanzó hace unos días.
rich97
Esto es muy confuso: ¿Cómo uso $ urlRouterProvider.deferIntercept (); para poder actualizar los parámetros sin volver a cargar el controlador? Esto realmente no me muestra eso. Dentro de la función de ejecución, puede evaluar una declaración if para sincronizar o no, pero todo con lo que tengo que trabajar es con la URL antigua y la nueva. ¿Cómo sé que esta es una instancia en la que todo lo que quiero hacer es actualizar los parámetros con solo dos URL para trabajar? ¿Sería la lógica ... (si el estado antiguo y el nuevo son los mismos, entonces no vuelva a cargar el controlador?) Estoy confundido.
btm1
bien, creo que este caso de uso fue con estados anidados, no quería volver a cargar el estado secundario, por lo tanto, fue interceptar. Creo que ahora preferiría hacerlo con vistas de orientación absoluta y definir la vista sobre el estado que sé que no cambiaría. De todos modos, esto sigue siendo bueno. Obtiene URL completas, es decir, puede adivinar el estado de la URL. Y también los parámetros de consulta y ruta, etc. Si realiza su aplicación en torno a estados y URL, es una gran cantidad de información. En el bloque de ejecución también puede acceder a servicios, etc. ¿Responde eso a su pregunta?
wiherek
16

Después de pasar mucho tiempo con este problema, esto es lo que conseguí trabajando

$state.go('stateName',params,{
    // prevent the events onStart and onSuccess from firing
    notify:false,
    // prevent reload of the current state
    reload:false, 
    // replace the last record when changing the params so you don't hit the back button and get old params
    location:'replace', 
    // inherit the current params on the url
    inherit:true
});
Pankaj Parkar
fuente
Las otras soluciones están relacionadas con el proveedor de ruta. Esta solución funciona para el caso, como el mío, donde estoy usando $ stateProvider y no uso $ routeProvider.
eeejay
@eeejay básicamente la pregunta se ha hecho para ui-routersolamente, ¿cómo se puede decir que otras soluciones, estaban trabajando para $routerProvider, $routeProvidery $stateProvideres totalmente diferente en la arquitectura ..
Pankaj Parkar
¿Qué pasa si no queremos que el botón Atrás funcione con esto? Quiero decir, al presionar hacia atrás, ir al estado / url anterior, no al mismo estado con diferentes parámetros
gaurav5430
Supongo que con esta solución, el navegador no funcionaría, ya que estamos cambiando un estado sin
avisarlo al
2
Intenté esto, y parece que $onInit()se llama a mi componente cada vez que uso el $state.go. No me parece 100% correcto.
Carrm
8

Vocación

$state.go($state.current, {myParam: newValue}, {notify: false});

aún recargará el controlador.

Para evitarlo, debe declarar el parámetro como dinámico:

$stateProvider.state({
    name: 'myState',
    url: '/my_state?myParam',
    params: {
        myParam: {
          dynamic: true,
        }
    },
    ...
});

Entonces ni siquiera necesitas el notify, solo llamando

$state.go($state.current, {myParam: newValue})

es suficiente Neato!

De la documentación :

Cuando dynamices así true, los cambios en el valor del parámetro no harán que se ingrese / salga el estado. Las resoluciones no se volverán a buscar ni las vistas se recargarán.

[...]

Esto puede ser útil para crear una interfaz de usuario donde el componente se actualiza solo cuando cambian los valores de los parámetros.

Moritz Ringler
fuente
7

Esta configuración resolvió los siguientes problemas para mí:

  • El controlador de entrenamiento no se llama dos veces al actualizar la url de .../a.../123
  • El controlador de entrenamiento no se invoca nuevamente cuando se navega a otro estado

Configuración del estado

state('training', {
    abstract: true,
    url: '/training',
    templateUrl: 'partials/training.html',
    controller: 'TrainingController'
}).
state('training.edit', {
    url: '/:trainingId'
}).
state('training.new', {
    url: '/{trainingId}',
    // Optional Parameter
    params: {
        trainingId: null
    }
})

Invocar los estados (desde cualquier otro controlador)

$scope.editTraining = function (training) {
    $state.go('training.edit', { trainingId: training.id });
};

$scope.newTraining = function () {
    $state.go('training.new', { });
};

Controlador de entrenamiento

var newTraining;

if (!!!$state.params.trainingId) {

    // new      

    newTraining = // create new training ...

    // Update the URL without reloading the controller
    $state.go('training.edit',
        {
            trainingId : newTraining.id
        },
        {
            location: 'replace', //  update url and replace
            inherit: false,
            notify: false
        });     

} else {

    // edit

    // load existing training ...
}   
martinoss
fuente
Estaba tratando de usar una estrategia similar, pero mi controlador nunca obtiene el valor de trainigId de la página de edición, ¿hay algo que pueda estar perdiendo allí? Intenté acceder a la página de edición directamente desde la URL y usando ui-sref. Mi código es exactamente como el tuyo.
Nikhil Bhandari
Esto funcionó para mí y es, con mucho, la solución más clara
OoDeLally
3

Si solo necesita cambiar la URL pero evitar el cambio de estado:

Cambie la ubicación con (agregue .replace si desea reemplazar en el historial):

this.$location.path([Your path]).replace();

Prevenir la redirección a su estado:

$transitions.onBefore({}, function($transition$) {
 if ($transition$.$to().name === '[state name]') {
   return false;
 }
});
egor.xyz
fuente
2

Hice esto pero hace mucho tiempo en la versión: v0.2.10 de UI-router como algo así ::

$stateProvider
  .state(
    'home', {
      url: '/home',
      views: {
        '': {
          templateUrl: Url.resolveTemplateUrl('shared/partial/main.html'),
          controller: 'mainCtrl'
        },
      }
    })
  .state('home.login', {
    url: '/login',
    templateUrl: Url.resolveTemplateUrl('authentication/partial/login.html'),
    controller: 'authenticationCtrl'
  })
  .state('home.logout', {
    url: '/logout/:state',
    controller: 'authenticationCtrl'
  })
  .state('home.reservationChart', {
    url: '/reservations/?vw',
    views: {
      '': {
        templateUrl: Url.resolveTemplateUrl('reservationChart/partial/reservationChartContainer.html'),
        controller: 'reservationChartCtrl',
        reloadOnSearch: false
      },
      '[email protected]': {
        templateUrl: Url.resolveTemplateUrl('voucher/partial/viewVoucherContainer.html'),
        controller: 'viewVoucherCtrl',
        reloadOnSearch: false
      },
      '[email protected]': {
        templateUrl: Url.resolveTemplateUrl('voucher/partial/voucherContainer.html'),
        controller: 'voucherCtrl',
        reloadOnSearch: false
      }
    },
    reloadOnSearch: false
  })
Sheelpriy
fuente
0

Intenta algo como esto

$state.go($state.$current.name, {... $state.params, 'key': newValue}, {notify: false})
Eyad Farra
fuente
-6

No creo que necesites un enrutador ui para esto. La documentación disponible para el servicio $ location dice en el primer párrafo, "... los cambios en $ location se reflejan en la barra de direcciones del navegador". Más adelante continúa diciendo: "¿Qué no hace? No causa una recarga completa de la página cuando se cambia la URL del navegador".

Entonces, con eso en mente, ¿por qué no simplemente cambiar $ location.path (ya que el método es getter y setter) con algo como lo siguiente:

var newPath = IdFromService;
$location.path(newPath);

La documentación señala que la ruta siempre debe comenzar con una barra diagonal, pero esto la agregará si falta.

Xaniff
fuente
Cuando uso ui-router, y uso $location.path(URL_PATH), la página se vuelve a representar automáticamente.
Kousha
Sí, se vuelve a representar. Intenté con event.preventDefault () en $ locationChangeStart, eso tampoco funciona, es decir, evita que el estado se vuelva a representar, pero también evita que la URL se actualice.
wiherek