Können Sie einen Pfad ändern, ohne den Controller in AngularJS neu zu laden?

191

Es wurde schon einmal gefragt und nach den Antworten sieht es nicht gut aus. Ich möchte mit diesem Beispielcode in Betracht ziehen ...

Meine App lädt das aktuelle Element in den Dienst, der es bereitstellt. Es gibt mehrere Controller, die die Artikeldaten bearbeiten, ohne dass der Artikel neu geladen wird.

Meine Controller laden das Element neu, wenn es noch nicht festgelegt ist. Andernfalls wird das aktuell geladene Element aus dem Dienst zwischen den Controllern verwendet.

Problem : Ich möchte für jeden Controller unterschiedliche Pfade verwenden, ohne Item.html neu zu laden.

1) Ist das möglich?

2) Wenn dies nicht möglich ist, gibt es einen besseren Ansatz für einen Pfad pro Controller als den, den ich hier gefunden habe?

app.js.

var app = angular.module('myModule', []).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/items', {templateUrl: 'partials/items.html',   controller: ItemsCtrl}).
      when('/items/:itemId/foo', {templateUrl: 'partials/item.html', controller: ItemFooCtrl}).
      when('/items/:itemId/bar', {templateUrl: 'partials/item.html', controller: ItemBarCtrl}).
      otherwise({redirectTo: '/items'});
    }]);

Item.html

<!-- Menu -->
<a id="fooTab" my-active-directive="view.name" href="#/item/{{item.id}}/foo">Foo</a>
<a id="barTab" my-active-directive="view.name" href="#/item/{{item.id}}/bar">Bar</a>
<!-- Content -->
<div class="content" ng-include="" src="view.template"></div>

controller.js

// Helper function to load $scope.item if refresh or directly linked
function itemCtrlInit($scope, $routeParams, MyService) {
  $scope.item = MyService.currentItem;
  if (!$scope.item) {
    MyService.currentItem = MyService.get({itemId: $routeParams.itemId});
    $scope.item = MyService.currentItem;
  }
}
function itemFooCtrl($scope, $routeParams, MyService) {
  $scope.view = {name: 'foo', template: 'partials/itemFoo.html'};
  itemCtrlInit($scope, $routeParams, MyService);
}
function itemBarCtrl($scope, $routeParams, MyService) {
  $scope.view = {name: 'bar', template: 'partials/itemBar.html'};
  itemCtrlInit($scope, $routeParams, MyService);
}

Auflösung.

Status : Durch die Verwendung der Suchabfrage, wie in der akzeptierten Antwort empfohlen, konnte ich verschiedene URLs angeben, ohne den Hauptcontroller neu laden zu müssen.

app.js.

var app = angular.module('myModule', []).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/items', {templateUrl: 'partials/items.html',   controller: ItemsCtrl}).
      when('/item/:itemId/', {templateUrl: 'partials/item.html', controller: ItemCtrl, reloadOnSearch: false}).
      otherwise({redirectTo: '/items'});
    }]);

Item.html

<!-- Menu -->
<dd id="fooTab" item-tab="view.name" ng-click="view = views.foo;"><a href="#/item/{{item.id}}/?view=foo">Foo</a></dd>
<dd id="barTab" item-tab="view.name" ng-click="view = views.bar;"><a href="#/item/{{item.id}}/?view=foo">Bar</a></dd>

<!-- Content -->
<div class="content" ng-include="" src="view.template"></div>

controller.js

function ItemCtrl($scope, $routeParams, Appts) {
  $scope.views = {
    foo: {name: 'foo', template: 'partials/itemFoo.html'},
    bar: {name: 'bar', template: 'partials/itemBar.html'},
  }
  $scope.view = $scope.views[$routeParams.view];
}

directives.js

app.directive('itemTab', function(){
  return function(scope, elem, attrs) {
    scope.$watch(attrs.itemTab, function(val) {
      if (val+'Tab' == attrs.id) {
        elem.addClass('active');
      } else {
        elem.removeClass('active');
      }
    });
  }
});

Der Inhalt in meinen Teilbereichen ist mit verpackt ng-controller=...

Coder1
quelle
fand diese Antwort: stackoverflow.com/a/18551525/632088 - es verwendet reloadOnSearch: false, sucht aber auch nach Aktualisierungen der URL in der Suchleiste (z. B. wenn der Benutzer auf die Schaltfläche "Zurück" klickt und die URL ändert)
robert king

Antworten:

115

Wenn Sie auf URLs nicht verwenden wie #/item/{{item.id}}/foound #/item/{{item.id}}/baraber #/item/{{item.id}}/?foound #/item/{{item.id}}/?barstattdessen können Sie Ihre Route einrichten für /item/{{item.id}}/haben reloadOnSearchSatz false( https://docs.angularjs.org/api/ngRoute/provider/$routeProvider ). Dadurch wird AngularJS angewiesen, die Ansicht nicht neu zu laden, wenn sich der Suchteil der URL ändert.

Anders Ekdahl
quelle
Dank dafür. Ich versuche herauszufinden, wo ich dann den Controller wechseln würde.
Coder1
Verstanden. Ein Controller-Parameter wurde zu meinem Ansichtsarray hinzugefügt und dann ng-controller="view.controller"zur ng-includeDirektive hinzugefügt .
Coder1
Sie würden in itemCtrlInit eine Überwachung für $ location.search () erstellen und je nach Suchparameter $ scope.view.template aktualisieren. Und dann könnte diese Vorlage mit so etwas umwickelt werden <div ng-controller="itemFooCtrl">... your template content...</div>. EDIT: Schön zu sehen, dass Sie gelöst wurden. Es wäre großartig, wenn Sie Ihre Frage mit der Art und Weise, wie Sie sie gelöst haben, aktualisieren würden, vielleicht mit einer jsFiddle.
Anders Ekdahl
Ich werde es aktualisieren, wenn ich zu 100% da bin. Ich habe einige Macken mit Umfang in den untergeordneten Controllern und ng-click. Wenn ich direkt von foo lade, wird ng-click im foo-Bereich ausgeführt. Ich klicke dann auf die Leiste und ng-Klicks in dieser Vorlage werden im übergeordneten Bereich ausgeführt.
Coder1
1
Beachten Sie, dass Sie Ihre URLs nicht neu formatieren müssen. Dies war möglicherweise der Fall, als die Antwort veröffentlicht wurde, aber in der Dokumentation ( docs.angularjs.org/api/ngRoute.$routeProvider ) heißt es jetzt: [reloadOnSearch = true] - {boolean =} - Route neu laden, wenn nur $ location. search () oder $ location.hash () ändern sich.
Rhys van der Waerden
93

Wenn Sie den Pfad ändern müssen, fügen Sie diesen nach Ihrer .config in Ihre App-Datei ein. Dann können Sie $location.path('/sampleurl', false);das Nachladen verhindern

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

Die eleganteste Lösung, die ich gefunden habe, finden Sie unter https://www.consolelog.io/angularjs-change-path-without-reloading .

Vigrond
quelle
6
Tolle Lösung. Gibt es überhaupt eine Möglichkeit, den Zurück-Button des Browsers dazu zu bringen, dies zu "ehren"?
Mark B
6
Beachten Sie, dass diese Lösung die alte Route beibehält. Wenn Sie nach $ route.current fragen, erhalten Sie den vorherigen und nicht den aktuellen Pfad. Ansonsten ist es ein toller kleiner Hack.
Jornare
4
Ich wollte nur sagen, dass die ursprüngliche Lösung von "EvanWinstanley" hier akut gepostet wurde: github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24
2
Ich verwende diese Lösung seit einiger Zeit und habe gerade festgestellt, dass bei einigen Pfadänderungen die Register neu geladen werden, auch wenn ein wahrer Wert übergeben wird. Hat jemand andere Probleme wie dieses gehabt?
Will Hitchcock
2
Ich musste $timeout(un, 200);vor der Rückgabe eine Anweisung hinzufügen , da diese manchmal $locationChangeSuccessüberhaupt nicht ausgelöst wurde apply(und die Route nicht geändert wurde, wenn sie wirklich benötigt wurde). Meine Lösung klärt die Dinge nach dem Aufrufen des Pfades (..., false)
snowindy
17

warum nicht einfach den ng-controller eine ebene höher stellen,

<body ng-controller="ProjectController">
    <div ng-view><div>

Und stellen Sie den Controller nicht in die Route ein,

.when('/', { templateUrl: "abc.html" })

Für mich geht das.

Windmaomao
quelle
toller Tipp, es funktioniert auch perfekt für mein Szenario! vielen Dank! :)
Daveoncode
4
Dies ist eine großartige Antwort "Die Russen haben Bleistifte benutzt", funktioniert für mich :)
inolasco
1
Ich habe zu früh gesprochen. Diese Problemumgehung hat das Problem, dass, wenn Sie Werte von $ routeParams in den Controller laden müssen, diese nicht geladen werden, standardmäßig {}
inolasco
@inolasco: Funktioniert, wenn Sie Ihre $ routeParams im $ routeChangeSuccess-Handler lesen. Meistens ist dies wahrscheinlich das, was Sie sowieso wollen. Wenn Sie nur den Controller lesen, erhalten Sie nur die Werte für die URL, die ursprünglich geladen wurde.
plong0
@ plong0 Guter Punkt, sollte funktionieren. In meinem Fall musste ich die URL-Werte jedoch nur beim Laden des Controllers beim Laden der Seite abrufen, um bestimmte Werte zu initialisieren. Am Ende habe ich die von vigrond vorgeschlagene Lösung mit $ locationChangeSuccess verwendet, aber Ihre ist auch eine andere gültige Option.
Inolasco
12

Für diejenigen, die eine Änderung von path () ohne erneutes Laden der Controller benötigen - Hier ist das Plugin: https://github.com/anglibs/angular-location-update

Verwendung:

$location.update_path('/notes/1');

Basierend auf https://stackoverflow.com/a/24102139/1751321

PS Diese Lösung https://stackoverflow.com/a/24102139/1751321 enthält einen Fehler nach dem Aufruf von path (, false) - sie unterbricht die Browsernavigation vorwärts / rückwärts, bis path (, true) aufgerufen wird

Daniel Garmoshka
quelle
10

Obwohl dieser Beitrag alt ist und eine Antwort akzeptiert wurde, löst die Verwendung von reloadOnSeach = false das Problem nicht für diejenigen von uns, die den tatsächlichen Pfad und nicht nur die Parameter ändern müssen. Hier ist eine einfache Lösung:

Verwenden Sie ng-include anstelle von ng-view und weisen Sie Ihren Controller in der Vorlage zu.

<!-- In your index.html - instead of using ng-view -->
<div ng-include="templateUrl"></div>

<!-- In your template specified by app.config -->
<div ng-controller="MyController">{{variableInMyController}}</div>

//in config
$routeProvider
  .when('/my/page/route/:id', { 
    templateUrl: 'myPage.html', 
  })

//in top level controller with $route injected
$scope.templateUrl = ''

$scope.$on('$routeChangeSuccess',function(){
  $scope.templateUrl = $route.current.templateUrl;
})

//in controller that doesn't reload
$scope.$on('$routeChangeSuccess',function(){
  //update your scope based on new $routeParams
})

Der einzige Nachteil ist, dass Sie das Auflösungsattribut nicht verwenden können, aber das ist ziemlich einfach zu umgehen. Außerdem müssen Sie den Status des Controllers verwalten, z. B. eine Logik, die auf $ routeParams basiert, wenn sich die Route innerhalb des Controllers ändert, wenn sich die entsprechende URL ändert.

Hier ist ein Beispiel: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview

Lukus
quelle
1
Ich bin mir nicht sicher, ob sich die Zurück-Schaltfläche in plnkr korrekt verhält ... Wie ich bereits erwähnt habe, müssen Sie einige Dinge selbst verwalten, wenn sich die Route ändert. Es ist nicht die Code-würdigste Lösung, aber es funktioniert
Lukus
2

Ich benutze diese Lösung

angular.module('reload-service.module', [])
.factory('reloadService', function($route,$timeout, $location) {
  return {
     preventReload: function($scope, navigateCallback) {
        var lastRoute = $route.current;

        $scope.$on('$locationChangeSuccess', function() {
           if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
              var routeParams = angular.copy($route.current.params);
              $route.current = lastRoute;
              navigateCallback(routeParams);
           }
        });
     }
  };
})

//usage
.controller('noReloadController', function($scope, $routeParams, reloadService) {
     $scope.routeParams = $routeParams;

     reloadService.preventReload($scope, function(newParams) {
        $scope.routeParams = newParams;
     });
});

Bei diesem Ansatz bleibt die Funktionalität der Zurück-Schaltfläche erhalten, und Sie haben im Gegensatz zu einigen anderen Ansätzen, die ich gesehen habe, immer die aktuellen routeParams in der Vorlage.

Parlament
quelle
1

Die obigen Antworten, einschließlich des GitHub, hatten einige Probleme für mein Szenario und auch das Zurück-Button oder die direkte URL-Änderung vom Browser luden den Controller neu, was mir nicht gefiel. Ich habe mich schließlich für folgenden Ansatz entschieden:

1. Definieren Sie eine Eigenschaft in Routendefinitionen mit dem Namen 'noReload' für Routen, bei denen der Controller bei Routenänderungen nicht neu geladen werden soll.

.when('/:param1/:param2?/:param3?', {
    templateUrl: 'home.html',
    controller: 'HomeController',
    controllerAs: 'vm',
    noReload: true
})

2. Fügen Sie in die Ausführungsfunktion Ihres Moduls die Logik ein, die nach diesen Routen sucht. Das Neuladen wird nur dann verhindert, wenn dies der Fall noReloadist trueund der vorherige Routencontroller identisch ist.

fooRun.$inject = ['$rootScope', '$route', '$routeParams'];

function fooRun($rootScope, $route, $routeParams) {
    $rootScope.$on('$routeChangeStart', function (event, nextRoute, lastRoute) {
        if (lastRoute && nextRoute.noReload 
         && lastRoute.controller === nextRoute.controller) {
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                un();
                // Broadcast routeUpdate if params changed. Also update
                // $routeParams accordingly
                if (!angular.equals($route.current.params, lastRoute.params)) {
                    lastRoute.params = nextRoute.params;
                    angular.copy(lastRoute.params, $routeParams);
                    $rootScope.$broadcast('$routeUpdate', lastRoute);
                }
                // Prevent reload of controller by setting current
                // route to the previous one.
                $route.current = lastRoute;
            });
        }
    });
}

3. Hören Sie abschließend im Controller das Ereignis $ routeUpdate ab, damit Sie alles tun können, was Sie tun müssen, wenn sich die Routenparameter ändern.

HomeController.$inject = ['$scope', '$routeParams'];

function HomeController($scope, $routeParams) {
    //(...)

    $scope.$on("$routeUpdate", function handler(route) {
        // Do whatever you need to do with new $routeParams
        // You can also access the route from the parameter passed
        // to the event
    });

    //(...)
}

Beachten Sie, dass Sie mit diesem Ansatz keine Änderungen am Controller vornehmen und den Pfad dann entsprechend aktualisieren. Es ist anders herum. Sie ändern zuerst den Pfad und hören dann das Ereignis $ routeUpdate ab, um Änderungen im Controller vorzunehmen, wenn sich die Routenparameter ändern.

Dies hält die Dinge einfach und konsistent, da Sie dieselbe Logik verwenden können, sowohl wenn Sie einfach den Pfad ändern (aber ohne teure $ http-Anforderungen, wenn Sie möchten) als auch wenn Sie den Browser vollständig neu laden.

CGodo
quelle
1

Es gibt eine einfache Möglichkeit, den Pfad zu ändern, ohne ihn neu zu laden

URL is - http://localhost:9000/#/edit_draft_inbox/1457

Verwenden Sie diesen Code, um die URL zu ändern. Die Seite wird nicht umgeleitet

Der zweite Parameter "false" ist sehr wichtig.

$location.path('/edit_draft_inbox/'+id, false);
Dinesh Vaitage
quelle
Dies ist nicht korrekt für AngularJS docs.angularjs.org/api/ng/service/$location
Steampowered
0

Hier ist meine umfassendere Lösung, die einige Probleme löst, die @Vigrond und @rahilwazir übersehen haben:

  • Wenn Suchparameter geändert werden, wird das Senden von a verhindert $routeUpdate.
  • Wenn die Route tatsächlich unverändert bleibt, $locationChangeSuccesswird sie nie ausgelöst, wodurch die nächste Routenaktualisierung verhindert wird.
  • Wenn im selben Digest-Zyklus eine weitere Aktualisierungsanforderung vorhanden war, die dieses Mal neu geladen werden soll, bricht der Ereignishandler dieses Neuladen ab.

    app.run(['$rootScope', '$route', '$location', '$timeout', function ($rootScope, $route, $location, $timeout) {
        ['url', 'path'].forEach(function (method) {
            var original = $location[method];
            var requestId = 0;
            $location[method] = function (param, reload) {
                // getter
                if (!param) return original.call($location);
    
                # only last call allowed to do things in one digest cycle
                var currentRequestId = ++requestId;
                if (reload === false) {
                    var lastRoute = $route.current;
                    // intercept ONLY the next $locateChangeSuccess
                    var un = $rootScope.$on('$locationChangeSuccess', function () {
                        un();
                        if (requestId !== currentRequestId) return;
    
                        if (!angular.equals($route.current.params, lastRoute.params)) {
                            // this should always be broadcast when params change
                            $rootScope.$broadcast('$routeUpdate');
                        }
                        var current = $route.current;
                        $route.current = lastRoute;
                        // make a route change to the previous route work
                        $timeout(function() {
                            if (requestId !== currentRequestId) return;
                            $route.current = current;
                        });
                    });
                    // if it didn't fire for some reason, don't intercept the next one
                    $timeout(un);
                }
                return original.call($location, param);
            };
        });
    }]);
Oded Niv
quelle
Tippfehler patham Ende sollte sein param, auch dies funktioniert nicht mit Hashes, und die URL in meiner Adressleiste wird nicht aktualisiert
Deltree
Fest. Hmm komisch, es funktioniert bei mir allerdings unter HTML5-URLs. Kann immer noch jemandem helfen, denke ich ...
Oded Niv
0

Fügen Sie das folgende innere Kopf-Tag hinzu

  <script type="text/javascript">
    angular.element(document.getElementsByTagName('head')).append(angular.element('<base href="' + window.location.pathname + '" />'));
  </script>

Dies verhindert das Nachladen.

Vivek
quelle