【问题标题】:Can you change a path without reloading the controller in AngularJS?您可以在不重新加载 AngularJS 中的控制器的情况下更改路径吗?
【发布时间】:2013-02-05 02:59:50
【问题描述】:

以前有人问过,从答案看起来不太好。我想问一下这个示例代码考虑...

我的应用会在提供它的服务中加载当前项目。有几个控制器可以在不重新加载项目的情况下操作项目数据。

如果尚未设置,我的控制器将重新加载项目,否则,它将在控制器之间使用服务中当前加载的项目。

问题:我想在不重新加载 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=...包裹

【问题讨论】:

  • 找到了这个答案:stackoverflow.com/a/18551525/632088 - 它使用 reloadOnSearch: false,但它也会检查搜索栏中 url 的更新(例如,如果用户单击返回按钮和 url 更改)

标签: angularjs


【解决方案1】:

如果您不必使用#/item/{{item.id}}/foo#/item/{{item.id}}/bar 之类的URL,而是使用#/item/{{item.id}}/?foo#/item/{{item.id}}/?bar,则可以将/item/{{item.id}}/ 的路由设置为将reloadOnSearch 设置为false (https://docs.angularjs.org/api/ngRoute/provider/$routeProvider)。如果 url 的搜索部分发生变化,这会告诉 AngularJS 不要重新加载视图。

【讨论】:

  • 谢谢。我试图弄清楚我会在哪里更改控制器。
  • 知道了。在我的视图数组中添加了一个控制器参数,然后将 ng-controller="view.controller" 添加到 ng-include 指令中。
  • 您将在 itemCtrlInit 中的 $location.search() 上创建一个监视,并根据搜索参数更新 $scope.view.template。然后这些模板可以用&lt;div ng-controller="itemFooCtrl"&gt;... your template content...&lt;/div&gt; 之类的东西包裹起来。编辑:很高兴看到你解决了,如果你用你的解决方法更新你的问题会很棒,也许用 jsFiddle。
  • 当我 100% 到达那里时,我会更新它。我在子控制器和 ng-click 的范围内有一些怪癖。如果我直接从 foo 加载,ng-click 会在 foo 范围内执行。然后我单击栏,该模板中的 ng-clicks 将在父范围内执行。
  • 应该注意的是,您实际上并不需要重新格式化您的 URL。发布响应时可能就是这种情况,但文档 (docs.angularjs.org/api/ngRoute.$routeProvider) 现在说:[reloadOnSearch=true] - {boolean=} - 仅在 $location.search() 或 $location 时重新加载路由.hash() 更改。
【解决方案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,我找到了最优雅的解决方案。

【讨论】:

  • 很好的解决方案。有没有办法让浏览器的后退按钮“尊重”这个?
  • 请注意,此解决方案保留旧路线,如果您要求 $route.current,您将得到以前的路径,而不是当前的。否则,这是一个很棒的小技巧。
  • 只是想说原始解决方案是由“EvanWinstanley”在这里发布的:github.com/angular/angular.js/issues/1699#issuecomment-34841248
  • 我使用这个解决方案已经有一段时间了,只是注意到在某些路径更改时,即使传入了 true 值,也会将寄存器重新加载为 false。还有其他人遇到过这样的问题吗?
  • 我不得不在 return 语句之前添加$timeout(un, 200);,因为有时$locationChangeSuccessapply 之后根本没有触发(并且路由在真正需要时没有改变)。我的解决方案在调用 path(..., false) 后清除了问题
【解决方案3】:

为什么不把 ng-controller 提高一级呢,

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

并且不要在路由中设置控制器,

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

它对我有用。

【讨论】:

  • 很好的提示,它也非常适合我的场景!非常感谢! :)
  • 这是一个很棒的“俄罗斯人用铅笔”的答案,对我有用 :)
  • 我说得太早了。此解决方法的问题是,如果您需要从控制器中的 $routeParams 加载值,它们将不会加载,默认为 {}
  • @inolasco:如果您在 $routeChangeSuccess 处理程序中读取您的 $routeParams 则有效。通常情况下,这可能就是您想要的。仅在控制器中读取只会为您获取最初加载的 URL 的值。
  • @plong0 好点,应该可以。不过在我的情况下,我只需要在控制器在页面加载时加载时获取 URL 值,以初始化某些值。我最终使用了 vigrond 提出的使用 $locationChangeSuccess 的解决方案,但你的也是另一个有效的选择。
【解决方案4】:

对于那些需要在不重新加载控制器的情况下更改路径()的人 - 这是插件:https://github.com/anglibs/angular-location-update

用法:

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

基于https://stackoverflow.com/a/24102139/1751321

附:此解决方案https://stackoverflow.com/a/24102139/1751321 包含错误 在调用 path(, false) 之后 - 它会中断浏览器导航向后/向前直到调用 path(, 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
    })
    

    唯一的缺点是您不能使用 resolve 属性,但这很容易解决。此外,您还必须管理控制器的状态,例如基于 $routeParams 的逻辑,因为控制器内的路由会随着相应 url 的变化而变化。

    这是一个例子:http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview

    【讨论】:

    • 不确定后退按钮在 plnkr 中的行为是否正确...正如我所提到的,随着路线的变化,您必须自己管理一些事情。这不是最值得代码的解决方案,但它确实有效
    【解决方案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;
         });
    });
    

    这种方法保留了后退按钮的功能,并且您始终在模板中拥有当前的 routeParams,这与我见过的其他一些方法不同。

    【讨论】:

      【解决方案7】:

      上面的答案,包括 GitHub 的答案,在我的场景中存在一些问题,并且浏览器的后退按钮或直接 url 更改正在重新加载控制器,这是我不喜欢的。我最终采用了以下方法:

      1. 在路由定义中定义一个属性,称为“noReload”,用于那些您不希望控制器在路由更改时重新加载的路由。

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

      2. 在模块的 run 函数中,放置检查这些路由的逻辑。只有当noReloadtrue 并且之前的路由控制器相同时,它才会阻止重新加载。

      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 请求)和完全重新加载浏览器时使用相同的逻辑。

      【讨论】:

        【解决方案8】:

        这是我的更完整的解决方案,它解决了@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,这也不适用于哈希,并且我的地址栏中的网址不会更新
        • 已修复。嗯,很奇怪,它在 html5 URL 上对我有用。可能仍然可以帮助我猜想的人......
        【解决方案9】:

        在head标签内添加以下内容

          <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
          

          使用此代码更改网址,页面不会被重定向

          第二个参数“false”很重要。

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

          【讨论】:

          【解决方案11】:

          从1.2版本开始,可以使用$location.replace()

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

          【讨论】:

            【解决方案12】:

            我无法在这里给出任何答案。作为一个可怕的黑客,我在更改路由时将时间戳存储在本地存储中,并在页面初始化时检查此时间戳是否已设置且最近,在这种情况下我不会触发一些初始化操作。

            在控制器中:

            window.localStorage['routeChangeWithoutReloadTimestamp'] = new Date().getTime();
            $location.path(myURL);
            

            在配置中:

            .when(myURL, {
                        templateUrl: 'main.html',
                        controller:'MainCtrl',
                        controllerAs: 'vm',
                        reloadOnSearch: false,
                        resolve:
                        {
                            var routeChangeWithoutReloadTimestamp =
                                window.localStorage['routeChangeWithoutReloadTimestamp'];
                            var currentTimestamp = new Date().getTime();
                            if (!routeChangeWithoutReloadTimestamp ||
                                    currentTimestamp - routeChangeWithoutReloadTimestamp >= 5000) {
                                //initialization code here
                            }
                            //reset the timestamp to trigger initialization when needed
                            window.localStorage['routeChangeWithoutReloadTimestamp'] = 0;
                        }
            });
            

            我使用了时间戳而不是布尔值,以防代码在有机会在更改路线之前重新初始化存储的值之前被中断。选项卡之间发生冲突的风险非常低。

            【讨论】:

              猜你喜欢
              • 2012-08-20
              • 2014-06-28
              • 1970-01-01
              • 1970-01-01
              • 2014-11-22
              • 2020-04-29
              • 1970-01-01
              • 2015-10-30
              • 1970-01-01
              相关资源
              最近更新 更多