您可以在不重新加载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];
}

指令.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)
罗伯特·金

Answers:


115

如果你没有使用像的URL #/item/{{item.id}}/foo#/item/{{item.id}}/bar#/item/{{item.id}}/?foo#/item/{{item.id}}/?bar,而不是,你可以设置你的路线/item/{{item.id}}/reloadOnSearch设置为falsehttps://docs.angularjs.org/api/ngRoute/provider/$routeProvider)。这告诉AngularJS如果URL的搜索部分发生更改,则不要重新加载视图。


感谢那。我试图弄清楚我将在哪里更改控制器。
Coder1

得到它了。在我的视图数组中添加了控制器参数,然后添加ng-controller="view.controller"ng-include指令中。
Coder1

您将在itemCtrlInit的$ location.search()上创建一个监视,并根据搜索参数更新$ scope.view.template。然后这些模板可以用包裹<div ng-controller="itemFooCtrl">... your template content...</div>。编辑:很高兴看到您解决了问题,如果您用解决方法(也许是jsFiddle)更新了问题,那就太好了。
Anders Ekdahl

当我100%在那里时,我会对其进行更新。我在子控制器和ng-click中有一些关于范围的怪癖。如果我直接从foo加载,则ng-click在foo范围内执行。然后,我单击bar,然后在父模板中执行该模板中的ng-clicks。
Coder1

1
应该注意的是,您实际上并不需要重新格式化URL。发布响应时可能是这种情况,但是文档(docs.angularjs.org/api/ngRoute.$routeProvider)现在显示: [reloadOnSearch = true]-{boolean =}-仅在$ location时重新加载路由。 search()或$ location.hash()更改。
Rhys van der Waerden 2014年

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
很好的解决方案。无论如何,有没有让浏览器的后退按钮“荣誉”这一点?
Mark B

6
请注意,此解决方案保留了旧路径,如果您要求$ route.current,则将获得先前路径,而不是当前路径。否则,这是一个很棒的小技巧。
jornare 2014年

4
只是想说原始解决方案是由“ EvanWinstanley”在此处发布的:github.com/angular/angular.js/issues/1699#issuecomment-34841248
chipit24

2
我使用这种解决方案已经有一段时间了,只是注意到在某些路径更改时,即使传入了真值,重载寄存器也为false。其他人有这样的问题吗?
希区柯克将于2015年

2
我不得不$timeout(un, 200);在return语句之前添加,因为有时之后$locationChangeSuccess根本不触发apply(并且在真正需要时也未更改路由)。我的解决方案在调用path(...,false)之后清除了所有内容
snowindy 2015年

17

为什么不只将ng-controller提升一级,

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

而且不要在路线中设置控制器,

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

这个对我有用。


很好的提示,它也非常适合我的情况!非常感谢你!:)
daveoncode 2015年

4
这是一个很棒的“
俄罗斯

1
我说得太早了。此解决方法的问题在于,如果您需要从控制器中的$ routeParams加载值,则将不会加载它们,默认值为{}
inolasco 2015年

@inolasco:如果您在$ routeChangeSuccess处理程序中读取$ routeParams,则可以使用。通常,这可能总是您想要的。仅在控制器中读取内容只会为您获取最初加载的URL的值。
plong0

@ plong0好点,应该可以。就我而言,我只需要在控制器加载页面加载时获取URL值即可初始化某些值。我最终使用了vigrond通过$ locationChangeSuccess提出的解决方案,但是您的也是另一个有效的选择。
inolasco 2015年


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

唯一的缺点是您不能使用resolve属性,但这很容易解决。另外,您还必须管理控制器的状态,例如基于$ 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.在模块的运行功能中,放置检查那些路由的逻辑。仅当noReloadis 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请求)和完全重新加载浏览器时都可以使用相同的逻辑。



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,这也不适用于散列,并且地址栏中的网址也不会更新
deltree

固定。嗯,很奇怪,尽管它适用于html5 URL。可能仍会帮助我猜到的人...
Oded Niv

0

在head标签内添加以下内容

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

这样可以防止重新加载。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.