web-dev-qa-db-fra.com

AngularJS: Changez le hachage et acheminez sans recharger complètement le contrôleur

J'ai un contrôleur, avec un itinéraire comme celui-ci:

#/articles/1234

Je souhaite modifier l'itinéraire sans recharger complètement le contrôleur, afin de pouvoir conserver la position des autres éléments dans le contrôleur (listes défilant)

Je peux penser à plusieurs façons de faire cela, mais ils sont tous très laids. Existe-t-il une meilleure pratique pour faire quelque chose comme ça? J'ai essayé d'expérimenter reloadOnSearch: false, mais cela ne semble rien changer.

35
doubledriscoll

Avait le même défi,

J'ai trouvé un hack dans une autre réponse StackOverflow qui a fait l'affaire

Solution assez propre - tout ce que j'ai fait est d'ajouter ces lignes au contrôleur qui définit $ location.path: 

var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function(event) {
    $route.current = lastRoute;
});

..et fait en sorte que $ route soit injectée dans le contrôleur bien sûr.

Mais toujours, se sent comme "DoNotFollowRoutesOnPathChange" est une fonctionnalité manquante dans AngularJS.

/ Jens

Update: Etant donné que l'écoute de cet événement tue efficacement l'utilisation ultérieure de la configuration de $ routeProvider, j'ai dû limiter cette capture au chemin actuel: 

    var lastRoute = $route.current;
    if ($route.current.$route.templateUrl.indexOf('mycurrentpath') > 0) {
        $route.current = lastRoute;         
    }

Devenir laide ...

34
Jens X Augustsson

Réponse brève:

Vous pouvez utiliser la méthode $location.search() comme vous l'avez mentionné. Vous devriez écouter l'événement "$routeUpdate" sur l'étendue plutôt que d'autres événements de route. $ route API .

Explication:

  1. Tout d’abord (vous le savez déjà), ajoutez reloadOnSearch: false à votre $routeProvider:

    $routeProvider.when('/somewhere', {
        controller: 'SomeCtrl',
        reloadOnSearch: false
    })
    
  2. Remplacez votre balise d'ancrage href ou ng-href par href="#/somewhere?param=value". Cela déclenchera l'événement $routeChangeSuccess si la partie du chemin d'accès (/somewhere) est différente de l'emplacement actuel. Sinon, l'événement $routeUpdate sera déclenché.

  3. Écoutez l'événement sur la portée:

    $scope.$on("$routeUpdate", function(event, route) {
        // some code here
    });
    
  4. Si vous souhaitez modifier les paramètres de recherche dans le code, vous pouvez utiliser la méthode $location.search(). $ location.search API .

21
Daiwei

Si vous définissez reloadOnSearch sur false, vous pouvez définir la partie ?a=b&c=d de l'URL sans recharger. Vous ne pouvez pas changer joliment l'emplacement réel, cependant, sans un rechargement.

8
Andrew Joslin

Modifier

Meilleure approche lors de l’utilisation de ngRoute:

/**
 * Add skipReload() to $location service.
 *
 * See https://github.com/angular/angular.js/issues/1699
 */
app.factory('location',
  ['$rootScope', '$route', '$location',
  function($rootScope, $route, $location) {

  $location.skipReload = function() {
    var lastRoute = $route.current;

    var deregister = $rootScope.$on('$locationChangeSuccess',
                                    function(e, absUrl, oldUrl) {
      console.log('location.skipReload', 'absUrl:', absUrl, 'oldUrl:', oldUrl);
      $route.current = lastRoute;
      deregister();
    });

    return $location;
  };

  return $location;
}]);

Comment utiliser:

app.controller('MyCtrl', ['$scope', 'location', function($scope, location) {
  $scope.submit = function() {
    location.skipReload().path(path);
  };
}]);

Ancienne réponse

J'ai écrit une usine réutilisable pour celle basée sur la réponse de Jens X Augustsson:

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',
                    $route.current.$$route.templateUrl);
        $route.current = lastRoute;
      }
    });
  };
}]);

Fonctionne avec AngularJS 1.0.6

Comment utiliser:

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

  DoNotReloadCurrentTemplate($scope);
}]);

AngularJS issue ici: https://github.com/angular/angular.js/issues/1699

7
tanguy_k

Utilisation:

$routeProvider.when('/somewhere', {
    controller: 'SomeCtrl',
    reloadOnSearch: false
})

Cela empêchera le rechargement du contrôleur lors de la modification du paramètre de requête, mais également lors du changement de hachage.

1
ehmicky

définir une fabrique pour gérer window.history de HTML5 comme suit (notez que je garde une pile pour la faire fonctionner sur Android également car elle présente quelques problèmes):

.factory('History', function($rootScope) {
    var StateQueue = [];
    var StatePointer = 0;
    var State = undefined;
    window.onpopstate = function(){
        // called when back button is pressed
        State = StateQueue.pop();
        State = (State)?State:{};
        StatePointer = (StatePointer)?StatePointer-1:0;
        $rootScope.$broadcast('historyStateChanged', State);
        window.onpopstate = window.onpopstate;

    }
    return {
        replaceState : function(data, title, url) {
            // replace current state
            var State = this.state();
            State = {state : data};
            window.history.replaceState(State,title,url);
            StateQueue[StatePointer] = State;
            $rootScope.$broadcast('historyStateChanged', this.state());
        },
        pushState : function(data, title, url){
            // Push state and increase pointer
            var State = this.state();
            State = {state : data};
            window.history.pushState(State,title,url);
            StateQueue.Push(State);
            $rootScope.$broadcast('historyStateChanged', this.state());
            StatePointer ++;
        },
        fakePush : function(data, title, url) {
            // call this when you do location.url(url)
            StateQueue.Push((StateQueue.length - 1 >= 0)?StateQueue[StateQueue.length -1]:{});
            StatePointer ++;
            $rootScope.$broadcast('historyStateChanged', this.state());
        },
        state : function() {
            // get current state
            return (StateQueue[StatePointer])?StateQueue[StatePointer]:{};
        },
        popState : function(data) {
            // TODO manually popState
        },
        back : function(data) {
            // TODO needed for iphone support since iphone doesnt have a back button
        }
    }
})

ajoutez quelques auditeurs sur vos champs dépendants et vous irez bien ainsi:

$scope.$on('historyStateChanged', function(e,v) {
        if(v)
            $scope.state = v;
        else
            $scope.state = {}
    });

c'est comme ça que je le fais. De mon point de vue, l'URL ne devrait changer que lorsqu'une nouvelle vue est chargée. Je pense que c’est ce que l’équipe angulaire avait prévu quand même. Si vous souhaitez mapper le bouton suivant/précédent sur votre modèle, essayez de le faire avec la fenêtre de HTML5.

J'espère que je pourrais aider . À la vôtre, Heinrich

1
Heinrich

Si vous atterrissez ici en 2015: La vraie solution ici est de n’utiliser aucun de ces hacks (j’ose les nommer ainsi, car en utilisant l’une des méthodes énumérées ci-dessus, vous perdrez la possibilité d’utiliser résolution et autres comme ui-router .

Voici une présentation pratique sur les différences. L'implémentation devrait être aussi simple que d'échanger $ route pour $ state et de convertir les états en noms. 

Je suis actuellement en train de passer à une méthode dans laquelle je vais me référer avec un href à une route, avec un paramètre optionnel get qui change l’état sans le recharger. Pour plus d'informations à ce sujet, consultez la section 'params' ici

1
SchizoDuckie

Vous pouvez le faire sans les hacks $locationChange~ et HistoryState en utilisant l'option routes resolve promise.

En supposant que vous ayez un itinéraire article où ce nombre est ce qui a changé, vous pouvez le faire;

$routeProvider.when(
    '/article/:number',
    {
        templateUrl : 'partial.html',
        controller : 'ArticleCtrl',
        resolve : {
            load : ['$q', '$routeParams', function($q, $routeParams) {
                var defer = $q.defer();

                //number was specified in the previous route parameters, means we were already on the article page
                if ($routeParams.number)
                    //so dont reload the view
                    defer.reject('');
                //otherwise, the number argument was missing, we came to this location from another route, load the view
                else
                    defer.resolve();

                return defer.promise;
            }]
        }
    }
);
0
Hashbrown

Cette solution simple (étonnamment) fonctionne pour moi:

$state.params.id = id;  //updating the $state.params somehow prevents reloading the state
$location.path('/articles/' + id);

Cela n'empêche pas le rechargement de l'état sur les boutons Précédent et Suivant cependant ... Note: j'utilise angularjs 1.2.7 et ui-router 0.0.1 (je sais que c'est vieux).

0
elfan

Voici le plugin: https://github.com/anglibs/angular-location-update

Usage:

$location.update_path('/notes/1');
0
Dan Key