È possibile modificare un percorso senza ricaricare il controller in AngularJS?


191

È stato chiesto prima e dalle risposte non sembra buono. Vorrei chiedere con questo codice di esempio in considerazione ...

La mia app carica l'elemento corrente nel servizio che lo fornisce. Esistono diversi controller che manipolano i dati dell'articolo senza che l'articolo venga ricaricato.

I miei controller ricaricheranno l'articolo se non è stato ancora impostato, altrimenti utilizzerà l'elemento attualmente caricato dal servizio tra controller.

Problema : vorrei utilizzare percorsi diversi per ciascun controller senza ricaricare Item.html.

1) È possibile?

2) Se ciò non è possibile, esiste un approccio migliore per avere un percorso per controller rispetto a quello che ho trovato qui?

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

Risoluzione.

Stato : l'utilizzo della query di ricerca come raccomandato nella risposta accettata mi ha permesso di fornire diversi URL senza ricaricare il controller principale.

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

Il contenuto all'interno dei miei parziali è racchiuso ng-controller=...


trovato questa risposta: stackoverflow.com/a/18551525/632088 - utilizza reloadOnSearch: false, ma anche che verifica la presenza di aggiornamenti l'URL nella barra di ricerca (ad esempio, se l'utente fa clic pulsante Indietro & url modifiche)
Robert King

Risposte:


115

Se non si dispone di utilizzare gli URL come #/item/{{item.id}}/fooe #/item/{{item.id}}/barma #/item/{{item.id}}/?fooe #/item/{{item.id}}/?barinvece, è possibile impostare il percorso per /item/{{item.id}}/avere reloadOnSearchimpostato su false( https://docs.angularjs.org/api/ngRoute/provider/$routeProvider ). Ciò indica ad AngularJS di non ricaricare la vista se la parte di ricerca dell'URL cambia.


Grazie per quello Sto cercando di capire dove avrei quindi cambiato il controller.
Coder1,

Fatto. Aggiunto un parametro controller al mio array di visualizzazione, quindi aggiunto ng-controller="view.controller"alla ng-includedirettiva.
Coder1,

Dovresti creare un controllo su $ location.search () in itemCtrlInit e, in base al parametro di ricerca, aggiorna $ scope.view.template. E poi quel modello potrebbe essere avvolto con qualcosa di simile <div ng-controller="itemFooCtrl">... your template content...</div>. EDIT: Piacere di vederti risolto, sarebbe bello se aggiorni la tua domanda su come hai risolto, forse con un jsFiddle.
Anders Ekdahl,

Lo aggiornerò quando ci sono al 100% lì. Sto riscontrando alcune stranezze con ambito nei controller figlio e ng-click. Se carico direttamente da foo, ng-click viene eseguito nell'ambito di foo. Quindi faccio clic sulla barra e gli ng-clic in quel modello vengono eseguiti nell'ambito genitore.
Coder1,

1
Va notato che in realtà non è necessario riformattare gli URL. Questo potrebbe essere stato il caso in cui la risposta è stata pubblicata, ma la documentazione ( docs.angularjs.org/api/ngRoute.$routeProvider ) ora dice: [reloadOnSearch = true] - {boolean =} - ricarica route quando solo $ location. modifiche di ricerca () o $ location.hash ().
Rhys van der Waerden,

93

Se è necessario modificare il percorso, aggiungerlo dopo il .config nel file dell'app. Quindi puoi fare $location.path('/sampleurl', false);per evitare il ricaricamento

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

Il merito va a https://www.consolelog.io/angularjs-change-path-without-reloading per la soluzione più elegante che ho trovato.


6
Ottima soluzione Esiste un modo per ottenere il pulsante Indietro del browser per "onorare" questo?
Mark B,

6
Tieni presente che questa soluzione mantiene il vecchio percorso e, se chiedi $ route.current, otterrai il percorso precedente, non quello corrente. Altrimenti è un piccolo trucco.
jornare,

4
Volevo solo dire che la soluzione originale è stata pubblicata di seguito da "EvanWinstanley" qui: github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24

2
Sto usando questa soluzione da un po 'e ho appena notato che su alcuni cambi di percorso, ricaricare i registri come falso anche quando viene passato un valore vero. Qualcun altro ha avuto problemi come questo?
Will Hitchcock,

2
Ho dovuto aggiungere $timeout(un, 200);prima la dichiarazione di $locationChangeSuccessreso poiché a volte non si attivava affatto dopo apply(e il percorso non era cambiato quando era veramente necessario). La mia soluzione chiarisce le cose dopo l'invocazione del percorso (..., falso)
snowindy,

17

perché non portare il ng-controller a un livello superiore,

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

E non impostare il controller nel percorso,

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

per me funziona.


ottimo consiglio, funziona perfettamente anche per il mio scenario! Grazie mille! :)
daveoncode

4
Questa è un'ottima risposta "i russi hanno usato le matite", funziona per me :)
Inolasco,

1
Ho parlato troppo presto. Questa soluzione alternativa ha il problema che se è necessario caricare valori da $ routeParams nel controller, questi non verranno caricati, per impostazione predefinita su {}
inolasco

@inolasco: funziona se leggi $ routeParams nel gestore $ routeChangeSuccess. Più spesso, questo è probabilmente quello che vuoi comunque. leggendo solo nel controller otterrai solo i valori per l'URL che è stato originariamente caricato.
pl00

@ plong0 Un buon punto, dovrebbe funzionare. Nel mio caso, però, avevo solo bisogno di ottenere i valori URL quando il controller carica al caricamento della pagina, per inizializzare determinati valori. Ho finito per usare la soluzione proposta da Vigrond usando $ locationChangeSuccess, ma anche la tua è un'altra valida opzione.
Inolasco,


10

Sebbene questo post sia vecchio e abbia avuto una risposta accettata, l'uso di reloadOnSeach = false non risolve il problema per quelli di noi che hanno bisogno di cambiare il percorso effettivo e non solo i parametri. Ecco una soluzione semplice da considerare:

Utilizzare ng-include anziché ng-view e assegnare il controller nel modello.

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

L'unico lato negativo è che non è possibile utilizzare l'attributo di risoluzione, ma è abbastanza facile aggirare. Inoltre, è necessario gestire lo stato del controller, come la logica basata su $ routeParams quando la route cambia all'interno del controller mentre cambia l'URL corrispondente.

Ecco un esempio: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview


1
Non sono sicuro che il pulsante Indietro si comporterebbe correttamente in plnkr ... Come ho già detto, devi gestire alcune cose da solo quando il percorso cambia ... non è la soluzione più degna di codice, ma funziona
Lukus

2

Uso questa soluzione

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

Questo approccio preserva la funzionalità del pulsante Indietro e hai sempre il routeParams corrente nel modello, a differenza di altri approcci che ho visto.


1

Le risposte sopra, incluso GitHub, avevano alcuni problemi per il mio scenario e anche il pulsante Indietro o la modifica diretta dell'URL dal browser stavano ricaricando il controller, cosa che non mi piaceva. Alla fine sono andato con il seguente approccio:

1. Definire una proprietà nelle definizioni del percorso, denominata "noReload" per quei percorsi in cui non si desidera ricaricare il controller al cambio di percorso.

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

2. Nella funzione di esecuzione del modulo, inserire la logica che controlla tali percorsi. Impedirà il ricaricamento solo se lo noReloadè truee il controller di percorso precedente è lo stesso.

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. Infine, nel controller, ascolta l'evento $ routeUpdate in modo da poter fare tutto ciò che devi fare quando cambiano i parametri del percorso.

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

    //(...)
}

Tieni presente che con questo approccio, non cambi le cose nel controller e quindi aggiorni il percorso di conseguenza. È il contrario. Prima cambi il percorso, quindi ascolti l'evento $ routeUpdate per cambiare le cose nel controller quando cambiano i parametri del percorso.

Ciò mantiene le cose semplici e coerenti in quanto è possibile utilizzare la stessa logica sia quando si cambia semplicemente percorso (ma senza costose richieste $ http se lo si desidera) sia quando si ricarica completamente il browser.


1

C'è un modo semplice per cambiare percorso senza ricaricare

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

Utilizza questo codice per modificare l'URL, la pagina non verrà reindirizzata

Il secondo parametro "falso" è molto importante.

$location.path('/edit_draft_inbox/'+id, false);

questo non è corretto per AngularJS docs.angularjs.org/api/ng/service/$location
steampowered

0

Ecco la mia soluzione più completa che risolve alcune cose che @Vigrond e @rahilwazir hanno perso:

  • Quando i parametri di ricerca fossero cambiati, si impedirebbe la trasmissione a $routeUpdate.
  • Quando la rotta viene effettivamente lasciata invariata, $locationChangeSuccessnon viene mai attivata, impedendo il successivo aggiornamento della rotta.
  • Se nello stesso ciclo digest fosse presente un'altra richiesta di aggiornamento, questa volta desiderando ricaricare, il gestore eventi annullerebbe tale ricarica.

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

l'errore pathdi battitura alla fine dovrebbe essere param, anche questo non funziona con gli hash e l'URL nella mia barra degli indirizzi non si aggiorna
deltree

Fisso. Hmm strano funziona per me anche se su URL HTML5. Può ancora aiutare qualcuno che immagino ...
Oded Niv,

0

Aggiungi il seguente tag head interno

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

Ciò impedirà il ricaricamento.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.