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

191

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

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

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

Проблема : я хотел бы использовать разные пути для каждого контроллера без перезагрузки 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=...

Coder1
источник
нашел ответ: stackoverflow.com/a/18551525/632088 - он использует reloadOnSearch: false, но также проверяет наличие обновлений URL-адреса в строке поиска (например, если пользователь нажимает кнопку «Назад» и изменения URL-адреса)
Роберт Кинг,

Ответы:

115

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

Андерс Экдаль
источник
Спасибо за это. Я пытаюсь выяснить, где я бы сменил контроллер.
Coder1
Понял. Добавил параметр контроллера в мой массив просмотра, затем добавил ng-controller="view.controller"в ng-includeдирективу.
Coder1
Вы должны создать часы для $ location.search () в itemCtrlInit и, в зависимости от параметра поиска, обновить $ scope.view.template. И тогда эти шаблоны могут быть обернуты чем-то вроде <div ng-controller="itemFooCtrl">... your template content...</div>. РЕДАКТИРОВАТЬ: Рад видеть, что вы решили, было бы здорово, если бы вы обновили свой вопрос, как вы решили, возможно, с помощью jsFiddle.
Андерс Экдаль
Я обновлю его, когда буду на 100%. У меня есть некоторые причуды с областью действия в дочерних контроллерах и ng-click. Если я загружаю напрямую из foo, ng-click выполняется в области foo. Затем я нажимаю на панель, и ng-клики в этом шаблоне выполняются в родительской области.
Coder1
1
Следует отметить, что вам не нужно переформатировать ваши URL-адреса. Возможно, это имело место, когда ответ был опубликован, но в документации ( docs.angularjs.org/api/ngRoute.$routeProvider ) теперь говорится: [reloadOnSearch = true] - {boolean =} - перезагрузить маршрут, когда только $ location. search () или $ location.hash () изменяется.
Рис ван дер Варден
93

Если вам нужно изменить путь, добавьте его после .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 за самое элегантное решение, которое я нашел.

Vigrond
источник
6
Отличное решение. Есть ли способ заставить кнопку браузера вернуться к «чести»?
Марк Б
6
Помните, что это решение сохраняет старый маршрут, и если вы попросите $ route.current, вы получите предыдущий путь, а не текущий. В противном случае это отличный маленький взломать.
Jornare
4
Просто хотел сказать, что оригинальное решение было опубликовано "EvanWinstanley" здесь: github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24
2
Я использовал это решение некоторое время и просто заметил, что при некоторых изменениях пути перезагрузите регистры как ложные, даже когда передается истинное значение. У кого-нибудь еще были такие проблемы?
Уилл Хичкок
2
Мне пришлось добавить $timeout(un, 200);до оператора return, так как иногда $locationChangeSuccessвообще не срабатывал после apply(и маршрут не менялся, когда это действительно необходимо). Мое решение проясняет ситуацию после вызова пути (..., false)
snowindy
17

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

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

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

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

меня устраивает.

windmaomao
источник
отличный совет, он отлично работает и для моего сценария! Большое спасибо! :)
daveoncode
4
Это отличный ответ "русские использовали карандаши", у меня работает :)
inolasco
1
Я говорил слишком рано. Этот обходной путь заключается в том, что если вам нужно загрузить значения из $ routeParams в контроллер, они не будут загружаться, по умолчанию {}
inolasco
@inolasco: работает, если вы читаете свои $ routeParams в обработчике $ routeChangeSuccess. Чаще всего это, вероятно, то, что вы хотите в любом случае. чтение только в контроллере даст вам только те значения для URL, который был изначально загружен.
plong0
@ plong0 Хороший вопрос, должно работать. Однако в моем случае мне нужно было получать значения URL только при загрузке контроллера при загрузке страницы, чтобы инициализировать определенные значения. В итоге я использовал решение, предложенное vigrond, с использованием $ locationChangeSuccess, но у вас также есть еще один допустимый вариант.
Иналаско
12

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

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

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

Основано на https://stackoverflow.com/a/24102139/1751321

PS Это решение https://stackoverflow.com/a/24102139/1751321 содержит ошибку после вызова пути (, false) - оно будет нарушать навигацию браузера вперед / назад до вызова пути (, true)

Даниэль Гармошка
источник
10

Хотя этот пост старый и получил ответ, использование 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

Lukus
источник
1
Не уверен, что кнопка возврата будет работать правильно в plnkr ... Как я уже говорил, вам нужно управлять некоторыми вещами самостоятельно при изменении маршрута ... это не самое подходящее для кода решение, но оно работает
Lukus
2

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

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

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

парламент
источник
1

Ответы выше, включая 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, если хотите), так и при полной перезагрузке браузера.

CGodo
источник
1

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

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

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

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

$location.path('/edit_draft_inbox/'+id, false);
Динеш Vaitage
источник
это не правильно для AngularJS docs.angularjs.org/api/ng/service/$location
steampowered
0

Вот мое более полное решение, которое решает несколько проблем, которые пропустили @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);
            };
        });
    }]);
Одед Нив
источник
опечатка pathв конце должна быть param, также это не работает с хешами, и URL в моей адресной строке не обновляется
deltree
Исправлена. Хм странно, это работает для меня, хотя на URL-адрес HTML5. Может все еще помочь кому-то, я думаю ...
Одив Нив
0

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

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

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

Вивек
источник
0

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

$location.path('/items');
$location.replace();
Брент Уошберн
источник