it-swarm.com.de

AngularJS: Ändern Sie Hash und Route, ohne den Controller vollständig neu zu laden

Ich habe einen Controller mit einer Route wie folgt:

#/articles/1234

Ich möchte die Route ändern, ohne den Controller vollständig neu zu laden, sodass ich die Position anderer Elemente im Controller konstant halten kann (Listen, die einen Bildlauf durchführen).

Ich kann mir ein paar Möglichkeiten vorstellen, aber das ist alles ziemlich hässlich. Gibt es eine Best Practice, um so etwas zu tun? Ich habe versucht, mit reloadOnSearch zu experimentieren: false, aber es scheint nichts zu ändern.

35
doubledriscoll

Hatte die gleiche Herausforderung,

In einer anderen StackOverflow-Antwort wurde ein Hack gefunden, der den Trick ausgeführt hat

Ziemlich saubere Lösung - alles, was ich tat, war, dem Controller, der $ location.path setzt, diese Zeilen hinzuzufügen: 

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

..und stellte sicher, dass $ route in den Controller eingespritzt wurde.

Fühlt sich jedoch an wie "DoNotFollowRoutesOnPathChange" ein fehlendes Feature in AngularJS ist.

/ Jens

Update: Da das Hören dieses Ereignisses die weitere Verwendung von $ routeProvider-Konfigs effektiv zerstört, musste ich diesen Fang nur auf den aktuellen Pfad beschränken: 

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

Hässlich werden ...

34

Kurze Antwort:

Sie können die $location.search()-Methode wie beschrieben verwenden. Sie sollten das "$routeUpdate" -Ereignis im Bereich statt anderer Routenereignisse abhören. $ route API .

Erläuterung:

  1. Als Erstes (was Sie bereits wissen) fügen Sie reloadOnSearch: false zu Ihrem $routeProvider hinzu:

    $routeProvider.when('/somewhere', {
        controller: 'SomeCtrl',
        reloadOnSearch: false
    })
    
  2. Ändern Sie Ihr Ankertag href oder ng-href in href="#/somewhere?param=value". Dies löst ein $routeChangeSuccess-Ereignis aus, wenn der Pfadteil (/somewhere) nicht mit der aktuellen Position übereinstimmt. Andernfalls wird das $routeUpdate-Ereignis ausgelöst.

  3. Hörereignis zum Umfang:

    $scope.$on("$routeUpdate", function(event, route) {
        // some code here
    });
    
  4. Wenn Sie Suchparameter im Code ändern möchten, können Sie die $location.search()-Methode verwenden. $ location.search API .

21
Daiwei

Wenn Sie reloadOnSearch auf false setzen, können Sie den ?a=b&c=d-Teil der URL ohne erneutes Laden festlegen. Sie können den tatsächlichen Standort nicht schön ändern, ohne nachzuladen.

8
Andrew Joslin

Bearbeiten

Besserer Ansatz bei Verwendung von 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;
}]);

Wie benutzt man:

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

Alte Antwort

Ich habe eine wiederverwendbare Fabrik dafür geschrieben, basierend auf Jens X Augustssons Antwort:

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

Funktioniert mit AngularJS 1.0.6

Wie benutzt man:

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

  DoNotReloadCurrentTemplate($scope);
}]);

AngularJS Ausgabe hier: https://github.com/angular/angular.js/issues/1699

7
tanguy_k

Benutzen:

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

Dadurch wird verhindert, dass der Controller bei einer Änderung der Abfrageparameter, aber auch bei einer Hashänderung neu geladen wird.

1
ehmicky

definiere eine Factory, um HTML5s window.history so zu behandeln (Hinweis: Ich verwende einen eigenen Stack, damit er auch auf Android funktioniert, da er einige Probleme hat):

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

fügen Sie ein paar Hörer zu Ihren abhängigen Bereichen hinzu und es wird Ihnen gut gehen:

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

so mach ich es. .__ Die URL sollte sich aus meiner Sicht nur ändern, wenn eine neue Ansicht geladen wird. Ich denke, das war das Ziel des Angular-Teams. Wenn Sie die Vorwärts-/Zurück-Schaltfläche Ihres Modells zuordnen möchten, versuchen Sie es mit HTML5's window.history

Ich hoffe, ich könnte helfen. Prost, Heinrich

1
Heinrich

Wenn Sie 2015 hier landen: Die eigentliche Antwort hier ist, keinen dieser Hacks zu verwenden (ich wage es so, denn wenn Sie eine der oben genannten Methoden verwenden, verlieren Sie die Möglichkeit, die Verwendung von Auflösungs- und ähnlichen Methoden zu verwenden), aber zu wechseln ui-router .

Hier ist eine praktische Darstellung der Unterschiede. Die Implementierung sollte so einfach sein wie das Austauschen von $ route gegen $ state und das Konvertieren der Status in Namen. 

Ich schalte derzeit auf eine Methode um, bei der ich mich mit einer href auf eine Route bezieht, mit einem optionalen get-Parameter, der den Status ändert, ohne ihn neu zu laden. Weitere Informationen hierzu finden Sie im Abschnitt "params" hier

1
SchizoDuckie

Sie können dies ohne die $locationChange~- und HistoryState-Hacks mit der Option routes resolve promise tun.

Angenommen, Sie hätten eine article-Route, bei der sich diese Zahl geändert hat, könnten Sie dies tun.

$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

Diese einfache Lösung funktioniert (überraschenderweise) für mich:

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

Es verhindert jedoch nicht, dass der Status beim Zurück- und Vorwärts-Button neu geladen wird. Hinweis: Ich verwende eckjs 1.2.7 und ui-router 0.0.1 (ich weiß, es ist alt).

0
elfan

Hier ist das Plugin: https://github.com/anglibs/angular-location-update

Verwendungszweck:

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