Чи можете ви змінити шлях, не завантажуючи контролер в 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];
}

direktives.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=...


знайшов цю відповідь: stackoverflow.com/a/18551525/632088 - він використовує reloadOnSearch брехня, але і перевіряє наявність оновлень на URL в SearchBar (наприклад , якщо користувач натискає кнопку назад і зміни 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 і залежно від параметра пошуку оновіть $ template.view.template. І тоді ці шаблони можна було б обгорнути чимось подібним <div ng-controller="itemFooCtrl">... your template content...</div>. EDIT: Приємно бачити, що ви вирішили, було б чудово, якби ви оновили своє запитання про те, як ви вирішили, можливо, jsFiddle.
Андерс Екдал

Я оновлю його, коли буду на 100%. У мене є деякі химерності із сферою застосування у дочірніх контролерах та ng-click. Якщо я завантажую безпосередньо з foo, ng-click виконується в межах foo. Потім я натискаю на панель, і ng-клацання в цьому шаблоні виконуються в батьківській області.
Coder1

1
Слід зазначити, що фактично не потрібно переформатувати URL-адреси. Можливо, це було у випадку, коли відповідь була розміщена, але в документації ( docs.angularjs.org/api/ngRoute.$routeProvider ) тепер сказано: [reloadOnSearch = true] - {boolean =} - перезавантажити маршрут лише за $ location. пошук () або $ location.hash () зміни.
Rhys van der Waerden

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 для найелегантнішого рішення, яке я знайшов.


6
Прекрасне рішення. Чи все-таки є кнопка повернення браузера, щоб "вшанувати" це?
Марк Б

6
Майте на увазі, що це рішення зберігає старий маршрут, і якщо ви попросите $ route.current, ви отримаєте попередній шлях, а не поточний. Інакше це чудовий маленький хак.
jornare

4
Просто хотів сказати, що оригінальне рішення гостро розміщено "EvanWinstanley" тут: github.com/angular/angular.js/isissue/1699#issuecomment-34841248
chipit24

2
Я деякий час використовував це рішення і просто помітив, що при деяких змінах шляху перезавантаження реєструється як хибне, навіть коли передається справжнє значення. У кого-небудь ще були такі проблеми, як це?
Буде Хічкок

2
Мені довелося додати $timeout(un, 200);перед поверненням заяву, оскільки іноді $locationChangeSuccessвзагалі не стріляли apply(а маршрут не змінювався, коли дійсно потрібно). Моє рішення очищає речі після виклику шляху (..., false)
snowindy

17

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

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

І не встановлюйте контролер у маршруті,

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

це працює для мене.


чудова порада, він прекрасно працює і для мого сценарію! дуже тобі дякую! :)
daveoncode

4
Це чудова відповідь "росіяни використовували олівці", працює для мене :)
inolasco

1
Я говорив занадто рано. У цьому вирішенні проблеми є те, що якщо вам потрібно завантажити значення з $ routeParams в контролер, вони не завантажуватимуться, за замовчуванням до {}
inolasco

@inolasco: працює, якщо ви читаєте ваш $ routeParams в обробці $ routeChangeSuccess. Найчастіше це, мабуть, те, чого ви хочете в будь-якому випадку. читання просто в контролері отримає лише значення для початково завантаженої URL-адреси.
plong0

@ plong0 Добрий момент, має працювати. Однак у моєму випадку мені потрібно було отримати значення URL лише тоді, коли контролер завантажується під час завантаження сторінки, щоб ініціалізувати певні значення. Я в кінцевому підсумку використовував рішення, запропоноване vigrond, використовуючи $ locationChangeSuccess, але ваш також інший дійсний варіант.
inolasco

12

Для тих, хто потребує зміни шляху () без перезавантаження контролерів - ось плагін: 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 містить помилку після виклику шляху (, помилково) - воно порушить навігацію браузера назад / вперед до тих пір, поки шлях (справжній) не буде викликаний


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


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 запитів, якщо вам подобається), і коли ви повністю перезавантажуєте браузер.


1

Існує простий спосіб змінити шлях без перезавантаження

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

Використовуйте цей код для зміни URL-адреси, Сторінка не буде переспрямована

Другий параметр "false" є дуже важливим.

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


0

Ось моє більш повне рішення, яке вирішує кілька речей @Vigrond і @rahilwazir пропустили:

  • Коли параметри пошуку були змінені, це перешкоджатиме трансляції a $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>

Це запобіжить перезавантаження.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.