Bạn có thể thay đổi đường dẫn mà không cần tải lại bộ điều khiển trong AngularJS không?


191

Nó đã được hỏi trước đó và từ những câu trả lời có vẻ không ổn. Tôi muốn hỏi về mã mẫu này để xem xét ...

Ứng dụng của tôi tải mục hiện tại trong dịch vụ cung cấp nó. Có một số bộ điều khiển thao tác dữ liệu vật phẩm mà không cần tải lại vật phẩm.

Bộ điều khiển của tôi sẽ tải lại vật phẩm nếu nó chưa được đặt, nếu không, nó sẽ sử dụng vật phẩm hiện được tải từ dịch vụ, giữa các bộ điều khiển.

Vấn đề : Tôi muốn sử dụng các đường dẫn khác nhau cho mỗi bộ điều khiển mà không cần tải lại Item.html.

1) Điều đó có thể không?

2) Nếu điều đó là không thể, có cách nào tốt hơn để có một đường dẫn cho mỗi bộ điều khiển so với những gì tôi nghĩ ra ở đây không?

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

Mục.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>

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

Nghị quyết.

Trạng thái : Sử dụng truy vấn tìm kiếm như được đề xuất trong câu trả lời được chấp nhận cho phép tôi cung cấp các url khác nhau mà không cần tải lại bộ điều khiển chính.

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

Mục.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>

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

Nội dung bên trong các phần của tôi được gói bằng ng-controller=...


đã tìm thấy câu trả lời này: stackoverflow.com/a/18551525/632088 - nó sử dụng reloadOnSearch: false, nhưng nó cũng kiểm tra các bản cập nhật cho url trong thanh tìm kiếm (ví dụ: nếu người dùng nhấp vào nút quay lại & thay đổi url)
robert king

Câu trả lời:


115

Nếu bạn không cần phải sử dụng các URL như #/item/{{item.id}}/foo#/item/{{item.id}}/barnhưng #/item/{{item.id}}/?foo#/item/{{item.id}}/?barthay vào đó, bạn có thể thiết lập tuyến đường của bạn để /item/{{item.id}}/reloadOnSearchcác thiết lập để false( https://docs.angularjs.org/api/ngRoute/provider/$routeProvider ). Điều đó nói với AngularJS không tải lại chế độ xem nếu phần tìm kiếm của url thay đổi.


Cảm ơn vì điều đó. Tôi đang cố gắng tìm ra nơi mà sau đó tôi sẽ thay đổi bộ điều khiển.
Coder1

Hiểu rồi. Đã thêm một bộ điều khiển param vào mảng xem của tôi, sau đó thêm ng-controller="view.controller"vào lệnh ng-include.
Coder1

Bạn sẽ tạo một chiếc đồng hồ trên $ location.search () trong itemCtrlInit và tùy thuộc vào tham số tìm kiếm, cập nhật $ scope.view.template. Và sau đó những mẫu đó có thể được bọc bằng một cái gì đó như <div ng-controller="itemFooCtrl">... your template content...</div>. EDIT: Rất vui khi thấy bạn giải quyết, sẽ thật tuyệt nếu bạn cập nhật câu hỏi của mình với cách bạn đã giải quyết, có lẽ với một jsFiddle.
Anders Ekdahl

Tôi sẽ cập nhật nó khi tôi 100% ở đó. Tôi có một số quirks với phạm vi trong bộ điều khiển con và ng-click. Nếu tôi tải trực tiếp từ foo, ng-click thực thi trong phạm vi foo. Sau đó tôi nhấp vào thanh và các lần nhấp ng trong mẫu đó được thực thi trong phạm vi cha.
Coder1

1
Cần lưu ý rằng bạn thực sự không cần định dạng lại URLS của mình. Đây có thể là trường hợp khi phản hồi được đăng, nhưng tài liệu ( docs.angularjs.org/api/ngRoute.$routeProvider ) hiện nói: [reloadOnSearch = true] - {boolean =} - tải lại tuyến đường khi chỉ có vị trí $. thay đổi tìm kiếm () hoặc $ location.hash ().
Rhys van der Waerden

93

Nếu bạn cần thay đổi đường dẫn, hãy thêm đường dẫn này sau .config trong tệp ứng dụng của bạn. Sau đó, bạn có thể làm $location.path('/sampleurl', false);để ngăn chặn tải lại

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

Tín dụng truy cập https://www.consolelog.io/angularjs-change-path-without-reloading cho giải pháp thanh lịch nhất mà tôi đã tìm thấy.


6
Giải pháp tuyệt vời. Có cách nào để có được nút quay lại của trình duyệt để "tôn vinh" điều này không?
Đánh dấu B

6
Xin lưu ý rằng giải pháp này giữ nguyên tuyến đường cũ và nếu bạn yêu cầu $ route.c hiện tại, bạn sẽ nhận được đường dẫn trước đó, không phải là hiện tại. Nếu không, nó là một hack nhỏ tuyệt vời.
jornare

4
Chỉ muốn nói rằng giải pháp ban đầu đã được đăng tải một cách chính thức bởi "EvanWinstanley" tại đây: github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24

2
Tôi đã sử dụng giải pháp này được một thời gian và chỉ nhận thấy rằng trên một số thay đổi đường dẫn, hãy tải lại các thanh ghi là sai ngay cả khi giá trị thực được truyền vào. Có ai khác có vấn đề như thế này không?
Will Hitchcock

2
Tôi đã phải thêm $timeout(un, 200);trước tuyên bố trở lại vì đôi khi $locationChangeSuccesskhông bắn gì cả sau đó apply(và tuyến đường không thay đổi khi thực sự cần thiết). Giải pháp của tôi sẽ dọn sạch mọi thứ sau khi gọi đường dẫn (..., sai)
snowindy

17

Tại sao không đặt trình điều khiển ng một cấp cao hơn,

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

Và không đặt bộ điều khiển trong tuyến đường,

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

nó làm việc cho tôi


mẹo tuyệt vời, nó hoạt động hoàn hảo cho kịch bản của tôi quá! Cảm ơn rât nhiều! :)
mã daveon 17/03/2015

4
Đây là một câu trả lời "người Nga sử dụng bút chì" tuyệt vời, hoạt động với tôi :)
inolasco 19/03/2015

1
Tôi đã nói quá sớm. Giải pháp này có một vấn đề là nếu bạn cần tải các giá trị từ $ routeParams trong bộ điều khiển, chúng sẽ không tải, mặc định là {}
inolasco 19/03/2015

@inolasco: hoạt động nếu bạn đọc $ routeParams của bạn trong trình xử lý $ routeChangeSuccess. Thường xuyên hơn không, đây có lẽ là những gì bạn muốn. chỉ đọc trong bộ điều khiển sẽ chỉ giúp bạn nhận được các giá trị cho URL được tải ban đầu.
plong0

@ plong0 Điểm tốt, nên làm việc. Trong trường hợp của tôi, tôi chỉ cần nhận các giá trị URL khi bộ điều khiển tải khi tải trang, để khởi tạo một số giá trị nhất định. Tôi đã kết thúc bằng cách sử dụng giải pháp được đề xuất bởi vigrond bằng cách sử dụng $ locationChangeSuccess, nhưng bạn cũng là một tùy chọn hợp lệ khác.
inolasco

12

Đối với những người cần thay đổi đường dẫn () mà không cần tải lại bộ điều khiển - Đây là plugin: https://github.com/anglibs/angular-location-update

Sử dụng:

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

Dựa trên https://stackoverflow.com/a/24102139/1751321

PS Giải pháp này https://stackoverflow.com/a/24102139/1751321 chứa lỗi sau khi đường dẫn (, sai) được gọi - nó sẽ phá vỡ điều hướng trình duyệt quay lại / chuyển tiếp cho đến khi đường dẫn (, đúng) được gọi


10

Mặc dù bài đăng này đã cũ và đã có câu trả lời được chấp nhận, nhưng việc sử dụng reloadOnSeach = false không giải quyết được vấn đề cho những người trong chúng ta, những người cần thay đổi đường dẫn thực tế chứ không chỉ các thông số. Đây là một giải pháp đơn giản để xem xét:

Sử dụng ng-include thay vì ng-view và gán bộ điều khiển của bạn trong mẫu.

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

Chỉ có nhược điểm là bạn không thể sử dụng thuộc tính giải quyết, nhưng điều đó khá dễ dàng để đi lại. Ngoài ra, bạn phải quản lý trạng thái của bộ điều khiển, như logic dựa trên $ routeParams khi tuyến thay đổi trong bộ điều khiển khi url tương ứng thay đổi.

Dưới đây là một ví dụ: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview


1
Không chắc chắn rằng nút quay lại sẽ hoạt động chính xác trong plnkr ... Như tôi đã đề cập, bạn phải tự mình quản lý một số thứ khi tuyến đường thay đổi .. đó không phải là giải pháp đáng mã nhất, nhưng nó hoạt động
Lukus

2

Tôi sử dụng giải pháp này

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

Cách tiếp cận này duy trì chức năng nút quay lại và bạn luôn có tuyến đường hiện tại trong mẫu, không giống như một số cách tiếp cận khác mà tôi đã thấy.


1

Các câu trả lời ở trên, bao gồm cả GitHub, có một số vấn đề đối với kịch bản của tôi và cả nút quay lại hoặc thay đổi url trực tiếp từ trình duyệt đang tải lại bộ điều khiển mà tôi không thích. Cuối cùng tôi đã đi với cách tiếp cận sau:

1. Xác định một thuộc tính trong các định nghĩa tuyến đường, được gọi là 'noReload' cho các tuyến đường mà bạn không muốn bộ điều khiển tải lại khi thay đổi tuyến đường.

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

2. Trong chức năng chạy của mô-đun của bạn, đặt logic kiểm tra các tuyến đó. Nó sẽ ngăn chặn tải lại chỉ nếu noReloadtruevà điều khiển tuyến đường trước là như nhau.

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. Cuối cùng, trong bộ điều khiển, hãy nghe sự kiện $ routeUpdate để bạn có thể làm bất cứ điều gì bạn cần làm khi tham số tuyến thay đổi.

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

    //(...)
}

Hãy nhớ rằng với phương pháp này, bạn không thay đổi mọi thứ trong bộ điều khiển và sau đó cập nhật đường dẫn cho phù hợp. Đó là cách khác. Trước tiên, bạn thay đổi đường dẫn, sau đó lắng nghe sự kiện $ routeUpdate để thay đổi mọi thứ trong bộ điều khiển khi tham số tuyến thay đổi.

Điều này giữ cho mọi thứ đơn giản và nhất quán vì bạn có thể sử dụng cùng một logic cả khi bạn chỉ cần thay đổi đường dẫn (nhưng không có yêu cầu $ http đắt tiền nếu bạn muốn) và khi bạn tải lại hoàn toàn trình duyệt.


1

Có một cách đơn giản để thay đổi đường dẫn mà không cần tải lại

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

Sử dụng mã này để thay đổi URL, Trang sẽ không được chuyển hướng

Tham số thứ hai "sai" là rất quan trọng.

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

đây là không chính xác cho AngularJS docs.angularjs.org/api/ng/service/$location
steampowered

0

Đây là giải pháp đầy đủ hơn của tôi giúp giải quyết một số điều @Vigrond và @rahilwazir đã bỏ lỡ:

  • Khi thông số tìm kiếm được thay đổi, nó sẽ ngăn phát sóng a $routeUpdate.
  • Khi tuyến đường thực sự không thay đổi, $locationChangeSuccesskhông bao giờ được kích hoạt khiến cho việc cập nhật tuyến tiếp theo bị ngăn chặn.
  • Nếu trong cùng một chu trình phân loại có một yêu cầu cập nhật khác, thì lần này muốn tải lại, trình xử lý sự kiện sẽ hủy tải lại.

    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ỗi đánh máy pathở cuối nên param, điều này cũng không hoạt động với băm và url trong thanh địa chỉ của tôi không cập nhật
deltree

Đã sửa. Hmm lạ là nó hoạt động với tôi mặc dù trên các URL html5. Tôi vẫn có thể giúp ai đó tôi đoán ...
Oded Niv

0

Thêm thẻ theo dõi bên trong

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

Điều này sẽ ngăn tải lại.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.