Можете ли вы изменить путь без перезагрузки контроллера в AngularJS?

Его спрашивали раньше, и из ответов он выглядит не очень хорошо. Я бы хотел спросить с этим примером кода...

Мое приложение загружает текущий элемент в службу, которая его предоставляет. Существует несколько контроллеров, которые манипулируют данными элемента без перезагрузки элемента.

Мои контроллеры перезагружают элемент, если он еще не установлен, в противном случае он будет использовать загруженный в данный момент элемент из службы между контроллерами.

Проблема. Я хотел бы использовать разные пути для каждого контроллера без перезагрузки Item.html.

1) Возможно ли это?

2) Если это невозможно, есть ли лучший подход к тому, чтобы иметь путь на контроллер по сравнению с тем, что я здесь придумал?

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

Разрешение.

Статус. Использование поискового запроса, как рекомендовано в принятом ответе, позволило мне предоставить разные URL-адреса без перезагрузки основного контроллера.

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

Содержимое внутри моих партикулов завернуто ng-controller=...

Ответ 1

Если вам не нужно использовать URL-адреса, такие как #/item/{{item.id}}/foo и #/item/{{item.id}}/bar но #/item/{{item.id}}/?foo и #/item/{{item.id}}/?bar вместо этого, вы можете настроить свой маршрут для /item/{{item.id}}/ иметь reloadOnSearch установлен в false (docs.angularjs.org/api/ngRoute. $ routeProvider). Это говорит AngularJS не перезагружать представление, если изменяется поисковая часть URL.

Ответ 2

Если вам нужно изменить путь, добавьте его после вашего .config в файл приложения. Затем вы можете сделать $location.path('/sampleurl', false);, чтобы предотвратить перезагрузку

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

Кредит отправляется https://www.consolelog.io/angularjs-change-path-without-reloading для самого элегантного решения, которое я нашел.

Ответ 3

почему бы просто не поставить ng-контроллер на один уровень выше,

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

И не устанавливайте контроллер на маршруте,

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

он работает для меня.

Ответ 4

Для тех, кому нужно изменить path() без перезагрузки контроллеров - вот плагин: https://github.com/anglibs/angular-location-update

Использование:

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

Основано на fooobar.com/questions/38224/...

PS Это решение fooobar.com/questions/38224/... содержит ошибку после вызова пути (, false) - оно будет нарушать навигацию браузера вперед/назад до вызова пути (, true)

Ответ 5

Хотя это сообщение устарело и приняло ответ, использование reloadOnSeach = false не решает проблему для тех из нас, кому нужно изменить фактический путь, а не только параметры. Здесь простое решение:

Используйте ng-include вместо ng-view и назначьте свой контроллер в шаблоне.

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

Только сторонняя сторона заключается в том, что вы не можете использовать атрибут разрешения, но это довольно легко обойти. Также вам нужно управлять состоянием контроллера, например, логикой на основе $routeParams, поскольку маршрут изменяется в контроллере по мере изменения соответствующего URL-адреса.

Вот пример: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview

Ответ 6

Я использую это решение

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

Этот подход сохраняет функциональность кнопки "Назад", и в шаблоне всегда есть текущие параметры маршрута, в отличие от некоторых других подходов, которые я видел.

Ответ 7

Здесь мое более полное решение, которое решает несколько вещей, которые @Vigrond и @rahilwazir пропустили:

  • Когда параметры поиска были изменены, это предотвратит трансляцию $routeUpdate.
  • Когда маршрут фактически оставлен без изменений, $locationChangeSuccess никогда не запускается, что приводит к предотвращению следующего обновления маршрута.
  • Если в том же цикле дайджеста был еще один запрос на обновление, на этот раз, желая перезагрузить, обработчик события отменит перезагрузку.

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

Ответ 8

В ответах выше, включая GitHub, были некоторые проблемы для моего сценария, а также кнопка возврата или прямого изменения URL-адреса в браузере - это перезагрузка контроллера, что мне не понравилось. Наконец я пошел со следующим подходом:

1. Определите свойство в определениях маршрутов, называемое noReload, для тех маршрутов, где вы не хотите, чтобы контроллер перезагружался при изменении маршрута.

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

2. В функции запуска вашего модуля поместите логику, которая проверяет эти маршруты. Это предотвратит перезагрузку, только если noReload - true, а предыдущий маршрут - тот же.

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. Наконец, в контроллере прослушайте событие $routeUpdate, чтобы вы могли делать все, что вам нужно, когда изменяются параметры маршрута.

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

    //(...)
}

Имейте в виду, что при таком подходе вы не меняете вещи в контроллере, а затем обновляете путь соответствующим образом. Это наоборот. Сначала вы меняете путь, а затем слушаете событие $routeUpdate, чтобы изменить ситуацию в контроллере, когда изменяются параметры маршрута.

Это упрощает и согласовывает вещи, поскольку вы можете использовать ту же логику, когда вы просто меняете путь (но без дорогостоящих $http-запросов, если хотите) и когда вы полностью перезагружаете браузер.

Ответ 9

Добавьте следующий тег заголовка

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

Это предотвратит перезагрузку.

Ответ 10

Существует простой способ изменить путь без перезагрузки

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

Используйте этот код для изменения URL, страница не будет перенаправлена

Второй параметр "ложь" очень важен.

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

Ответ 11

Начиная с версии 1.2, вы можете использовать $ location.replace():

$location.path('/items');
$location.replace();