AngularJS UI Router - changer l'URL sans recharger l'état

134

Actuellement, notre projet utilise par défaut $routeProvider, et j'utilise ce "hack", pour changer urlsans recharger la page:

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;
}]);

et en controller

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

Je pense remplacer routeProviderpar ui-routerdes itinéraires de nidification, mais je ne trouve pas cela dans ui-router.

Est-ce possible - faire la même chose avec angular-ui-router?

Pourquoi ai-je besoin de ça? Permettez-moi de vous expliquer avec un exemple: la
route pour créer une nouvelle catégorie est /category/new après clickingsur SAVE je montre success-alertet je veux changer la route /category/newen /caterogy/23(23 - est l'id du nouvel élément stocké dans la base de données)

vuliade
la source
1
dans ui-router, vous n'avez pas à définir une URL pour chaque état, vous pouvez naviguer d'un état à l'autre sans changer d'URL
Jonathan de M.
voulez-vous mettre à jour l'URL entière ou simplement le chemin de recherche? Je cherchais une solution en mettant à jour le chemin de recherche et je l'ai trouvée ici: stackoverflow.com/questions/21425378/…
Florian Loch
@johnathan Vraiment? J'adorerais faire cela, n'afficher qu'une seule URL, mais $urlRouterProvider.otherwisesemble fonctionner sur une URL, pas sur un état. Hmmm, je pourrais peut-être vivre avec 2 URL, ou trouver un autre moyen d'indiquer qu'il s'agit d'une URL invalide.
Mawg dit de réintégrer Monica

Réponses:

164

Vous pouvez simplement utiliser à la $state.transitionTo place de $state.go . $state.go appelle en $state.transitionTo interne mais définit automatiquement les options sur { location: true, inherit: true, relative: $state.$current, notify: true } . Vous pouvez appeler $state.transitionTo et régler notify: false . Par exemple:

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

peut être remplacé par

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

Edit: Comme suggéré par fracz, cela peut simplement être:

$state.go('.detail', {id: newId}, {notify: false}) 
rezKesh
la source
16
N'est-ce pas "conserver l'URL lors du rechargement de l'état" au lieu de "modifier l'URL sans recharger l'état"?
Peter Hedberg le
25
pour moi, cela a fonctionné avec les éléments suivants: $state.transitionTo('.detail', {id: newId}, { location: true, inherit: true, relative: $state.$current, notify: false }) donc, fondamentalement, définissez notifier sur false et l'emplacement sur true
Arjen de Vries
6
@ArjendeVries Oui, cela fonctionne comme prévu mais j'ai trouvé un comportement inattendu. Après avoir joué avec de nombreux appels de méthode transitionTo (sans rechargement) lorsque vous passez enfin à un nouvel état (ex. Url), il relance l'ancien contrôleur.
Premchandra Singh
2
@Premchandra Singh: J'ai le même problème ici. En quittant l'état, l'ancien contrôleur est à nouveau initialisé. J'obtiens le même problème avec la solution acceptée de wiherek. Voir aussi ici github.com/angular-ui/ui-router/issues/64
martinoss
15
Encore plus simple: $state.go('.detail', {id: newId}, {notify: false}).
fracz
48

Ok, résolu :) Angular UI Router a cette nouvelle méthode, $ urlRouterProvider.deferIntercept () https://github.com/angular-ui/ui-router/issues/64

en gros, cela se résume à ceci:

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();
  }]);

Je pense que cette méthode n'est actuellement inclus dans le maître version du routeur angulaire ui, celui avec des paramètres facultatifs (qui sont bien aussi, d' ailleurs). Il doit être cloné et construit à partir de la source avec

grunt build

Les documents sont également accessibles depuis la source, via

grunt ngdocs

(ils sont intégrés dans le répertoire / site) // plus d'informations dans README.MD

Il semble y avoir une autre façon de faire cela, par des paramètres dynamiques (que je n'ai pas utilisés). De nombreux crédits à nateabele.


En guise de remarque, voici les paramètres facultatifs dans $ stateProvider de Angular UI Router, que j'ai utilisés en combinaison avec ce qui précède:

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'
    });

}]);

cela permet de résoudre un état, même si l'un des paramètres est manquant. Le référencement est un objectif, la lisibilité en est un autre.

Dans l'exemple ci-dessus, je voulais que doorSingle soit un paramètre obligatoire. On ne sait pas comment les définir. Cela fonctionne bien avec plusieurs paramètres optionnels, donc pas vraiment de problème. La discussion est ici https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090

wiherek
la source
Il semble que vos paramètres optionnels ne fonctionnent pas. Error: Both params and url specicified in state 'state'. Il est dit dans la documentation qu'il s'agit également d'une utilisation invalide. Un peu décevant.
Rhys van der Waerden
1
Avez-vous construit du maître? Veuillez noter qu'au moment où j'ai ajouté la solution, les paramètres facultatifs n'étaient inclus que dans la version qui devait être construite manuellement à partir des sources. cela ne sera pas inclus dans la version jusqu'à ce que la v.0.3 soit sortie.
wiherek
1
Juste un petit mot. Grâce à nateabele les paramètres optionnels sont disponibles dans la v0.2.11 qui vient de sortir il y a quelques jours.
rich97
ceci est très déroutant: comment utiliser $ urlRouterProvider.deferIntercept (); afin que je puisse mettre à jour les paramètres sans recharger le contrôleur? Cela ne me montre pas vraiment cela. Dans la fonction d'exécution, vous pouvez évaluer une instruction if pour synchroniser ou ne pas synchroniser, mais tout ce que j'ai à travailler est l'ancienne et la nouvelle URL. Comment puis-je savoir qu'il s'agit d'une instance où tout ce que je veux faire est de mettre à jour les paramètres avec seulement deux URL avec lesquelles travailler? La logique serait-elle .... (si l'ancien et le nouvel état sont les mêmes, ne rechargez pas le contrôleur?) Je suis confus.
btm1
à droite, je pense que ce cas d'utilisation était avec des états imbriqués, je ne voulais pas recharger l'état enfant, donc interceptait. Je pense que maintenant je préférerais faire cela avec des vues de ciblage absolu, et définir la vue sur l'état qui, je le sais, ne changerait pas. Quoi qu'il en soit, c'est toujours bon. Vous obtenez des URL complètes, c'est-à-dire que vous pouvez deviner l'état à partir de l'url. Et aussi les paramètres de requête et de chemin, etc. Si vous créez votre application autour d'états et d'urls, c'est beaucoup d'informations. Dans le bloc d'exécution, vous pouvez également accéder aux services, etc. Cela répond-il à votre question?
wiherek
16

Après avoir passé beaucoup de temps avec ce problème, voici ce que j'ai travaillé

$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
la source
Les autres solutions sont liées au fournisseur d'itinéraire. Cette solution fonctionne pour le cas, comme le mien, où j'utilise $ stateProvider et non $ routeProvider.
eeejay
@eeejay essentiellement la question a été posée pour ui-routerque, comment pouvez - vous dire que, d' autres solutions travaillaient pour $routerProvider, $routeProvideret $stateProvidersont totalement différents dans l' architecture ..
Pankaj Parkar
et si nous ne voulons pas que le bouton de retour fonctionne avec cela? Je veux dire, en arrière, appuyez sur pour revenir à l'état / url précédent, pas au même état avec des paramètres différents
gaurav5430
Je suppose que, avec cette solution, le navigateur ne fonctionnerait pas, car nous changeons un état sans le faire savoir à ui-router
Pankaj Parkar
2
J'ai essayé ceci, et il semble que $onInit()mon composant soit appelé à chaque fois que j'utilise le $state.go. Cela ne me semble pas 100% ok.
Carrm
8

Appel

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

rechargera toujours le contrôleur.

Pour éviter cela, vous devez déclarer le paramètre comme dynamique:

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

Alors tu n'as même pas besoin d' notifyappeler

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

suffit. Neato!

De la documentation :

Quand dynamicest-cetrue , les modifications de la valeur du paramètre n'entraîneront pas l'entrée / la sortie de l'état. Les résolutions ne seront pas récupérées à nouveau et les vues ne seront pas rechargées.

[...]

Cela peut être utile pour créer une interface utilisateur dans laquelle le composant se met à jour lorsque les valeurs des paramètres changent.

Moritz Ringler
la source
7

Cette configuration a résolu les problèmes suivants pour moi:

  • Le contrôleur d'entraînement n'est pas appelé deux fois lors de la mise à jour de l'URL de .../à.../123
  • Le contrôleur d'entraînement n'est plus appelé lors de la navigation vers un autre état

Configuration de l'état

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
    }
})

Appel des états (depuis n'importe quel autre contrôleur)

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

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

Contrôleur de formation

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
la source
J'essayais d'utiliser une stratégie similaire mais mon contrôleur n'obtient jamais la valeur de trainigId de la page d'édition, quelque chose qui me manque peut-être? J'ai essayé d'accéder à la page d'édition directement à partir de l'URL et en utilisant ui-sref. Mon code est exactement comme le vôtre.
Nikhil Bhandari
Cela a fonctionné pour moi et est de loin la solution la plus claire
OoDeLally
3

Si vous avez seulement besoin de modifier l'URL mais que vous évitez le changement d'état:

Changez d'emplacement avec (ajoutez .replace si vous souhaitez remplacer dans l'historique):

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

Empêcher la redirection vers votre état:

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

je l'ai fait mais il y a longtemps dans la version: v0.2.10 de UI-router comme quelque chose comme ceci:

$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
la source
0

Essayez quelque chose comme ça

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

Je ne pense pas que vous ayez besoin du tout d'un ui-router pour cela. La documentation disponible pour le service $ location indique dans le premier paragraphe, "... les modifications apportées à $ location sont reflétées dans la barre d'adresse du navigateur." Il continue plus tard en disant: "Qu'est-ce que cela ne fait pas? Cela ne provoque pas un rechargement complet de la page lorsque l'URL du navigateur est modifiée."

Donc, dans cet esprit, pourquoi ne pas simplement changer le $ location.path (car la méthode est à la fois un getter et un setter) avec quelque chose comme ce qui suit:

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

La documentation note que le chemin doit toujours commencer par une barre oblique, mais cela l'ajoutera s'il est manquant.

Xaniff
la source
Lorsque j'utilise ui-routeret utilise $location.path(URL_PATH), la page est automatiquement restituée?!
Kousha
ouais, il re-rend. J'ai essayé avec event.preventDefault () sur $ locationChangeStart, cela ne fonctionne pas non plus - c'est-à-dire qu'il empêche l'état de re-rendu, mais cela empêche également l'url de se mettre à jour.
wiherek le