web-dev-qa-db-fra.com

Pouvez-vous modifier un chemin sans recharger le contrôleur dans AngularJS?

Cela a déjà été demandé, et d'après les réponses, ça n'a pas l'air bien. Je voudrais demander avec cet exemple de code en considération ...

Mon application charge l'élément en cours dans le service qui le fournit. Il existe plusieurs contrôleurs qui manipulent les données d'élément sans que l'élément ne soit rechargé.

Mes contrôleurs rechargeront l'élément s'il n'est pas encore défini. Sinon, il utilisera l'élément actuellement chargé du service entre les contrôleurs.

Problème : Je voudrais utiliser des chemins différents pour chaque contrôleur sans recharger Item.html.

1) Est-ce possible?

2) Si cela n’est pas possible, existe-t-il une meilleure approche pour avoir un chemin par contrôleur par rapport à ce que j’ai proposé ici?

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

Résolution.

Status : Utiliser la requête de recherche comme recommandé dans la réponse acceptée me permettait de fournir différentes URL sans recharger le contrôleur principal.

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

Le contenu de mes partiels est entouré de ng-controller=...

182
Coder1

Si vous ne devez pas utiliser d'URL telles que #/item/{{item.id}}/foo et #/item/{{item.id}}/bar mais plutôt #/item/{{item.id}}/?foo et #/item/{{item.id}}/?bar, vous pouvez configurer votre route pour /item/{{item.id}}/ afin que reloadOnSearch soit défini sur false ( docs.angularjs.org/api/ngRoute.$routeProvider ). Cela indique à AngularJS de ne pas recharger la vue si la partie de recherche de l’URL change.

109
Anders Ekdahl

Si vous devez modifier le chemin, ajoutez-le après votre fichier .config dans votre fichier d'application . Ensuite, vous pouvez faire $location.path('/sampleurl', false); pour empêcher le rechargement

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

Le crédit va à https://www.consolelog.io/angularjs-change-path-without-reloading pour la solution la plus élégante que j'ai trouvée.

90
Vigrond

pourquoi ne pas simplement placer le contrôleur ng à un niveau supérieur, 

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

Et ne pas mettre le contrôleur dans la route,

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

ça marche pour moi.

17
windmaomao

Pour ceux qui ont besoin de path () change sans contrôleurs reload -Here est plugin: https://github.com/anglibs/angular-location-update

Usage:

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

Basé sur https://stackoverflow.com/a/24102139/1751321

P.S. Cette solution https://stackoverflow.com/a/24102139/1751321 contient le bogue Après le chemin (, false) appelé - la navigation dans le navigateur sera interrompue jusqu'à ce que le chemin (, true) soit appelé. 

12
Dan Key

Bien que ce message soit ancien et qu’une réponse ait été acceptée, l’utilisation de reloadOnSeach = false ne résout pas le problème pour ceux d’entre nous qui ont besoin de changer de chemin réel et pas seulement les paramètres. Voici une solution simple à considérer:

Utilisez ng-include au lieu de ng-view et assignez votre contrôleur dans le modèle.

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

Seul inconvénient, vous ne pouvez pas utiliser l'attribut resol, mais c'est assez facile à utiliser. Vous devez également gérer l’état du contrôleur, comme une logique basée sur $ routeParams lorsque la route change dans le contrôleur lorsque l’URL correspondante change.

Voici un exemple: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview

10
Lukus

J'utilise cette solution 

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

Cette approche préserve la fonctionnalité du bouton Précédent et vous avez toujours les routeParams actuels dans le modèle, contrairement à d'autres approches que j'ai déjà vues.

2
parliament

Voici ma solution plus complète qui résout quelques problèmes que @Vigrond et @rahilwazir ont manqués:

  • Lorsque les paramètres de recherche étaient modifiés, cela empêchait la diffusion d'un $routeUpdate.
  • Lorsque la route reste réellement inchangée, $locationChangeSuccess n'est jamais déclenché, ce qui empêche la mise à jour de la route next route.
  • Si, dans le même cycle de résumé, il y avait une autre demande de mise à jour, cette fois pour recharger, le gestionnaire d'événements annulerait ce rechargement.

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

Ajouter la balise suivante dans la tête

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

Cela empêchera le rechargement.

0
Vivek

Les réponses ci-dessus, y compris celle de GitHub, présentaient quelques problèmes pour mon scénario et le bouton de retour ou le changement direct d'URL à partir du navigateur rechargeait le contrôleur, ce que je n'aimais pas. Je suis finalement allé avec l'approche suivante:

1. Définissez une propriété dans les définitions de route, appelée "noReload" pour les routes pour lesquelles vous ne souhaitez pas que le contrôleur se recharge lors du changement de route.

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

2. Dans la fonction d'exécution de votre module, insérez la logique qui vérifie ces itinéraires. Cela empêchera le rechargement uniquement si noReload est true et que le contrôleur de route précédent est identique.

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. Enfin, dans le contrôleur, écoutez l'événement $ routeUpdate afin que vous puissiez faire ce que vous devez faire lorsque les paramètres de route changent.

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

    //(...)
}

Gardez à l'esprit qu'avec cette approche, vous ne modifiez pas les choses dans le contrôleur, puis mettez à jour le chemin en conséquence. C'est l'inverse. Vous modifiez d’abord le chemin, puis écoutez l’événement $ routeUpdate pour modifier les paramètres du contrôleur lorsque les paramètres de route changent.

Cela permet de garder les choses simples et cohérentes, car vous pouvez utiliser la même logique lorsque vous changez simplement de chemin (mais sans demandes coûteuses $ http si vous le souhaitez) et lorsque vous rechargez complètement le navigateur.

0
CGodo

Il y a un moyen simple de changer de chemin sans recharger 

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

Utilisez ce code pour changer l'URL, la page ne sera pas redirigée

Le deuxième paramètre "false" est très important.

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

Depuis la version 1.2, vous pouvez utiliser $ location.replace () :

$location.path('/items');
$location.replace();
0
Brent Washburne