你可以在不重新加载AngularJS控制器的情况下改变路径吗?

时间:2013-02-20 07:15:25

标签: angularjs

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

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

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

问题:我想为每个控制器使用不同的路径而不重新加载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);
}

解决。

状态:使用已接受答案中建议的搜索查询,我可以提供不同的网址,而无需重新加载主控制器。

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

12 个答案:

答案 0 :(得分:110)

如果您不必使用#/item/{{item.id}}/foo#/item/{{item.id}}/bar#/item/{{item.id}}/?foo#/item/{{item.id}}/?bar等网址,则可以设置{{1将/item/{{item.id}}/设置为reloadOnSearchhttps://docs.angularjs.org/api/ngRoute/provider/$routeProvider)。这告诉AngularJS如果网址的搜索部分发生更改,则不会重新加载视图。

答案 1 :(得分:90)

如果您需要更改路径,请在您的应用文件中的.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,我找到了最优雅的解决方案。

答案 2 :(得分:17)

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

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

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

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

它对我有用。

答案 3 :(得分:12)

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

用法:

getCart()

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

P.S。此解决方案https://stackoverflow.com/a/24102139/1751321包含错误 在调用path(,false)之后 - 它将向后/向前打破浏览器导航,直到名为

的path(,true)

答案 4 :(得分:10)

虽然这篇文章很老并且已经接受了答案,但使用reloadOnSeach = false并不能解决我们这些需要改变实际路径而不仅仅是params的问题。这是一个需要考虑的简单解决方案:

使用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

答案 5 :(得分: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,这与我见过的其他方法不同。

答案 6 :(得分:0)

这是我更全面的解决方案,解决了@Vigrond和@rahilwazir错过的一些事情:

  • 当搜索参数更改时,会阻止广播$routeUpdate
  • 当路由实际保持不变时,永远不会触发$locationChangeSuccess,这会导致阻止 next 路由更新。
  • 如果在同一个摘要周期中有另一个更新请求,这次希望重新加载,事件处理程序将取消重新加载。

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

答案 7 :(得分:0)

上面的答案,包括GitHub,我的方案有一些问题,而且后退按钮或浏览器的直接网址更改正在重新加载控制器,我不喜欢。我终于采用了以下方法:

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

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

2. 在模块的运行功能中,输入检查这些路由的逻辑。仅当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 :(得分:0)

在头部标签内添加以下内容

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

这将阻止重新加载。

答案 9 :(得分:0)

有一种简单的方法可以在不重新加载的情况下更改路径

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

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

第二个参数“false”非常重要。

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

答案 10 :(得分:0)

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

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

答案 11 :(得分:0)

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

在控制器中:

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

我使用了一个时间戳而不是一个布尔值,以防万一代码在被中断之前有机会重新初始化在更改路由之前存储的值。标签之间发生碰撞的风险非常低。