Angular.js: $location 上的选项以允许在不重新加载路由的情况下更改哈希/路径

创建于 2012-12-12  ·  194评论  ·  资料来源: angular/angular.js

我们的应用程序中有一个范例,用户可以在他们查看事物的同一页面上创建新事物。 所以我们的路线就像/thing/:id。 当创建一个新事物时,他们会去 /thing/new。 成功保存事物后,我们希望将路由更改为 /thing/1234 (或任何它的新 ID)。 现在部分不需要重新加载,因为数据都是一样的。 我们只想更新路径,以便用户现在可以为正确的链接添加书签等。

在 $location (不在路由定义上)有一个选项来启用/禁用路由加载会起作用,但我确信还有其他方法可以实现该功能。

Lots of comments $location ngRoute high confusing feature

最有用的评论

解决方案本质上是在 $location.path() 中添加一个额外的参数。

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

然后像这样使用

$location.path('/path/that/you/need', false);

所有194条评论

+1,有同样的问题。

@cgross由于 $location 服务使用 JQuery 样式的方法链接,并且所有命令都推迟到 $digest 无论如何,我建议添加一个新的链接方法,例如 reload(boolean):

$location.path("/thing/"+thing.id).replace().reload(false)

PS 这是启动 Eclipse Nebula 项目的 Chris Gross 吗?

对 :)

+1 :+1:

+1

现在,我们正在使用一种肮脏的技巧来解决这个问题。

    $scope.$on('$locationChangeSuccess', function(event) {

        // Want to prevent re-loading when going from /dataEntry/1 to some other dataEntry path
        if ($route && $route.current && $route.current.$route.templateUrl.indexOf('dataEntry') > 0) {
            $route.current = lastRoute; //Does the actual prevention of routing
        }
});

+1

+1

+1

+1

+1

这是我通过为$routeChangeStart支持preventDefault()来解决此问题的尝试。 你怎么看?
提交: https :

几天来我一直在研究@qualidafial方法,现在 PR 已经准备好了。 我创建了一种新的notify方法,它允许跳过一次位置更改通知。 这应该避免触发任何路由更改或重新加载。

这不会触及路由系统,因此您不必更改路由定义。 即使您使用ui-router而不是标准的 AngularJS 路由系统,它仍然可以工作。

示例: $location.path('/client/2').replace().notify(false);

我还进行了一些测试和文档。

+1

另一个 +1
已经使用@lrlopez的拉取请求。 奇迹般有效。

+1

+1

不幸的是 PR https://github.com/angular/angular.js/pull/2398已被 Angular 团队拒绝,因为跳过通知可能会导致实际 URL 和当前路由不一致。 讨论结束时有很长的解释。

我的任何项目都不需要此功能,但我会将其保存在我的存储库中,以便您可以根据需要进行合并。 如果它与主分支不同步,请告诉我,以便我可以重新调整更改。 谢谢!

+1

+1,需要这个:(

+1

编辑:这里有更好的方法: https :

我创建了一个可重用的工厂来绕过这个(基于@shahmirn 的想法):

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
app.factory('DoNotReloadCurrentTemplate', ['$route', function($route) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate not reloading template: ' + $route.current.$$route.templateUrl);
        $route.current = lastRoute;
      }
    });
  };
}]);

如何使用:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);
}]);

来源: http :

如果您在路由上将“reloadOnSearch”设置为 false,那么它似乎可以修复重新加载整个页面的哈希更改。

+1

这对于 ng-view create/destroy 生命周期不合适的移动应用程序来说非常有用(会影响性能和可用性)。

@tkrotoff感谢您的示例。 由于您的解决方法是取消事件通知,因此我在 rootscope 上添加了一个广播,因此其他控制器仍然可以收到路由更改的通知:

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
app.factory('DoNotReloadCurrentTemplate', ['$route', '$rootScope', function($route, $rootScope) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate not reloading template: ' + $route.current.$$route.templateUrl);
        $rootScope.$broadcast('locationChangedWithoutReload', $route.current);
        $route.current = lastRoute;        
      }
    });
  };
}]);

然后:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);

  $rootScope.$on('locationChangedWithoutReload', function(event, route) {
    // set location specific breadcrumbs
    $scope.breadcrumbs = assembleBreadCrumbs();

    // do something for specific route
    if (route.$$route.action == 'item') {
      $scope.item = $scope.items[params.itemId];
      $rootScope.title = $scope.item.name;
    }
  });
}]);

我有与主题作者相同的问题,并创建了上述解决方案的修改版本。

app.factory('skipReload', [
    '$route',
    '$rootScope',
    function ($route, $rootScope) {
        return function () {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        };
    }
]);

用法:

app.controller('ThingCtrl', ['$scope', '$http', '$location', 'skipReload', function ($scope, $http, $location, skipReload) {
    ...
    $scope.submit = function () {
        ...
        $http.post('thing', thing).success(function (id) {
            skipReload();
            $location.path('/thing/' + id).replace();
        });
    };
}]);

使用测试 templateUrl 的永久事件侦听器来决定是否允许重新加载路由,可能会中断路由到使用相同 templateUrl 的其他视图。 例如,我路由到 /thing/new 并且视图显示到最后添加的东西 /thing/5 的链接,我将无法导航到东西 5 和任何其他东西(甚至在浏览器中显式更改 url)。 templateUrl 测试的另一个问题是模板可以通过使用模板属性(作为字符串或函数)来指定。

此 hack 允许恰好跳过一个路由重新加载并且不会破坏路由。

我也尝试过不那么冗长(用于控制器)的解决方案:

app.factory('location', [
    '$location',
    '$route',
    '$rootScope',
    function ($location, $route, $rootScope) {
        $location.skipReload = function () {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
            return $location;
        };
        return $location;
    }
]);
app.controller('ThingCtrl', ['$scope', '$http', 'location', function ($scope, $http, location) {
    ...
    $scope.submit = function () {
        ...
        $http.post('thing', thing).success(function (id) {
            location.skipReload().path('/thing/' + id).replace();
        });
    };
}]);

似乎工作正常。

+1

@sergey-buturlakin 你的 $location.skipReload 太棒了! 我用它代替 DoNotReloadCurrentTemplate

@sergey-buturlakin 您的解决方案似乎对我有用。 谢谢!

https://github.com/angular/angular.js/issues/1699#issuecomment -22511464 运行skipReload() ,它一直禁用重新加载
看起来应该稍后调用un()

它可以防止任何下一次重新加载,并且应该在路径更改之前立即调用。
路径更改完成后 - 将调用“un”。

是的,但是我只想跳过一次重新加载,但是建议的方法可以防止任何进一步的重新加载

+1

+1

+1

PR https://github.com/angular/angular.js/pull/2398只跳过重新加载一次。 我不知道它是否与当前主服务器有合并冲突。 如果是这种情况,请给我留言,我会解决它。

我使用谢尔盖的解决方案,同时放弃无用的(根据我)“un”变量并调用。

这将导致:

  angular.module('services.locationChanger', [])
    .service('locationChanger', ['$location', '$route', '$rootScope', function ($location, $route, $rootScope) {

        this.skipReload = function () {
            var lastRoute = $route.current;
            $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
            });
            return this;
        };

        this.withoutRefresh = function (url, doesReplace) {
            if(doesReplace){
                $location.path(url).replace();
            }
            else {
                $location.path(url || '/');
            }
        };
    }]);

和任何来电者:

 locationChanger.skipReload().withoutRefresh("/", true);

+1

+1

+1

+1

@mica16 un变量不是用来解绑事件吗?

我创建了两个用于测试的 plunkrs,没有un变量来取消绑定事件,似乎没有它,事件会一遍又一遍地触发

确实, un变量听起来很奇怪。
根据原作者的说法,它会被认为是有用的......但我不相信这是完美的解决方案。

我更喜欢重写它,但我放弃了这个功能,因为我不再期待它了。

因此,如果您的单元测试可以确保它按您期望的那样工作,那就去吧:)

你们是对的人
我的解决方案是

app.factory 'location', ['$location', '$route', '$rootScope', '$timeout', ($location, $route, $rootScope, $timeout) ->
  $location.skipReload = ->
    lastRoute = $route.current
    un = $rootScope.$on('$locationChangeSuccess', ->
      $route.current = lastRoute
    )
    $timeout((->
      un()
    ), 1000)
    $location

  $location
]

然后在控制器中

location.skipReload().path('/new/path').replace()

+1

+1

+1

+1

另一种方法是将其从当前的 javascript“步骤”中删除,以便路由不知道对 window.location 所做的更改:

setTimeout(function() {
    window.location.hash = "";
    if (typeof (history.pushState) !== "undefined") {
        history.pushState("", document.title, location.pathname);
    }
}, 0);

+1

+1

+1

+1

使用 $anchorScroll 怎么样? 我在网上闲逛,发现有一些工具可以做到这一点。 它至少对我有用。

你应该检查一下: http :

+1

这是一个坏主意吗? 我承认我没有对它进行广泛的测试。 我不喜欢在设置路径之前调用单独的方法的想法。 从使用的角度来看,至少这看起来更干净。

我使用 sergey 的技术扩展了内置的 $location.path 函数。

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

用法:

$location.path('/my/path', false);

+1

@EvanWinstanley解决方案对我

+1

@EvanWinstanley/sergey 的修复看起来不错,它不会重新加载控制器,但是在更改路由时,会再次请求解析。

我的主要用例是手动更改 $location.path 而不重新加载解析/控制器,这样我就可以进行缓存。

例如:我有一个允许用户在多个应用程序之间进行选择的选择,这也会更改 $location.path 以获得更好的 url。当用户选择应用程序时,API 调用的结果将被缓存。即使有修复,它总是会再次解析,这是不必要的,因为结果已经被缓存了。

+1

@sergey-buturlakin 解决方案对我来说很好,它不会重新加载控制器,但仍会重新加载 reslove 函数并向服务器发送请求。

+1

@EvanWinstanley的解决方案对我不起作用。 另一方面, @mica16的解决方案效果很好......(后退按钮不会刷新页面)。 我试图编写自己的版本但没有成功:(。

+1

+1

+1

+1
请支持:$location.reload();

@jvmvik你试过$route.current.reload()吗?

7.1.1

时隔一年多没有消息?

+1

+1

我对 Sergey 的解决方案做了一些改进。
现在它支持,例如,标签(导航标签)的自定义 url,而无需重新加载整个页面。

// Original: https://github.com/angular/angular.js/issues/1699#issuecomment-22511464
//
// Usage:
//
// (interception is needed for Back/Forward buttons to work)
//
// location.intercept($scope._url_pattern, function(matched) {
//   // can return false to abort interception
//   var type = matched[1]
//   if (!type) {
//     return;
//   }
//   $scope.safeApply(function() {
//     $scope.data_type = type; 
//     $scope.params.page = 1; 
//     $scope.get_data(); 
//   });
// });
// 
// anywhere in your controller: 
// location.skipReload().path(url);
//
// to replace in history stack:
// location.skipReload().path(url).replace();

app.factory('location', [
    '$location',
    '$route',
    '$rootScope',
    function ($location, $route, $rootScope) {
        var page_route = $route.current;

        $location.skipReload = function () {
            //var lastRoute = $route.current;
            var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = page_route;
                unbind();
            });
            return $location;
        };

        if ($location.intercept) {
            throw '$location.intercept is already defined';
        }

        $location.intercept = function(url_pattern, load_url) {

            function parse_path() {
                var match = $location.path().match(url_pattern)
                if (match) {
                    match.shift();
                    return match;
                }
            }

            var unbind = $rootScope.$on("$locationChangeSuccess", function() {
                var matched = parse_path();
                if (!matched || load_url(matched) === false) {
                  return unbind();
                }
                $route.current = page_route;
            });
        };

        return $location;
    }
]);

+1

+1

更改 $location 路径时防止重新加载路由的另一种解决方法是设置当前路由 pathParams 以匹配新的位置路径。

在我的 $routeProvider 配置中,我有一条到 /tasks 的路由,如下所示

when('/tasks/:section/:taskId?', {
      templateUrl: 'partials/tasks.html',
      controller: 'TaskCtrl',
      reloadOnSearch: false
      })

上面的路由有两个 pathParams 部分和 taskId(taskId 是可选的)并且 reloadOnSearch 设置为 false。

要在不重新加载路由的情况下从 /tasks/inbox 导航到 /tasks/inbox/122,请在更改 $location 路径之前设置 taskId pathParam,如下所示

$scope.showTask = function(task) {
    // set taskId to prevent route reloading
    $route.current.pathParams ['taskId'] = task.id;
    $location.path('/tasks/'+$route.current.pathParams ['section']+'/'+task.id);
};

请注意,不需要设置部分 pathParam,因为它已经由路由设置。

当路由发现从新 $location 路径中提取的 pathParams 与当前路由 pathParams 匹配(并且 reloadOnSearch 设置为 false)时,它不会重新加载路由,而是会触发 $routeUpdate 事件。

要在不重新加载路由的情况下从 /tasks/inbox/122 导航回 /tasks/inbox,请在更改 $location 路径之前删除 taskId pathParam,如下所示

$scope.back = function () {
    // delete taskId to prevent route reloading
    delete $route.current.pathParams ['taskId'];
    $location.path('/tasks/'+$route.current.pathParams ['section']);
};

+1

+:100:

你读过 Derick Bailey 关于 Backbone 路由器的文章http://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/吗? Backbone 提供了这个选项,根据 Derick 的说法,您应该始终避免触发重新加载(这似乎与 Angular 的路由器在这里所​​做的相反)。
这篇文章有 48 个“+1”……我不认为所有这些人都做错了什么。 必须存储var lastRoute = $route.current;并稍后在$locationChangeSuccess$route.current = lastRoute;恢复它似乎很 hacky。
@IgorMinar你有什么想法吗?

好的,我将在下周拥有这个话题,所以我们终于可以结束这个话题了。

首先,我认为当#hash 值改变时路由不应该重新加载。

website.com/#/my-route#top

website.com/#/my-route#bottom

因此,当 top 更改为 bottom 时,它不应该重新加载,但如果 my-route 值发生更改,则应该重新加载。 我们都可以同意这一点吗?

其次,更重要的是,我们需要一种简单的方法来指出路由路径在更改时不会重新加载,但是如果路由的早期片段发生更改,这就会出现问题。 那么为什么我们不使用两个::冒号来为路由参数提供不同的表示法来指出它的作用类似于通配符值。

// this will reload the page when the id changes like normal
$routeProvider.register('/users/:id')

// this will reload the page when the id changes, but not the section (since two colons are used)
$routeProvider.register('/users/:id/::section')

你们有什么感想?

这对于您想要离开的情况并没有真正的帮助:

/users/new

/users/123

一旦新用户被创建并分配了一个 id,无需重新加载用户,因为它已经存在。

因为必须以某种方式将新用户对象映射到 id 为 123 的当前用户对象。这是特定于应用程序的,不是吗?

值得一提的是,Ember 路由器不支持在不转换的情况下更改 URL:

http://stackoverflow.com/questions/19076834/change-url-without-transition

@matsko我们可以授权工程师并让他或她决定路线是否应该触发更改吗?
我可以接受良好的默认设置。
创建一种方法,以便工程师可以在不运行整个重新加载事件链的情况下更改 url。 就我个人而言,我可以在“.path()”、“.search()”等上添加一个带有选项的可选额外参数(例如:{reload: false})。

@ChrisCinelli是的,但是当路由定义中有两个参数时会发生什么? 如何防止一个重新加载而允许另一个重新加载? 该标志将是一个全有或全无的功能。

@matsko所以在实施结束时,是否认为我们能够挑选出哪些参数会导致重新加载,哪些不会? 如果是这样,您是否已经想到了一种方法,还是我们遇到了障碍?

@cgross cherry-pick 是(使用双冒号)。 这只是现在的一个想法,所以没有什么具体的。 如果这符合 Angular 2.0 的计划,那么我们可以使用它或尝试使用 2.0 计划拥有的东西或 Dart 已经拥有的东西(以保持 API 一致)。

我认为那将是完美的,并且作为路线的一部分看起来不错(至少 IMO)。

$routeProvider,when("/:categoryName/:id[reload=no]", { ... })

然后,在控制器中

$scope.$on("$routeChangeSuccess", function () {
  if ($routeParams.id !== $scope.activeId) {
    // React to ID update without reload
  }
});

是的,类似的东西,但是将使用双冒号 '::id' 而不是:id[reload=no]

请记住,这只是一个想法,但这里的很多人确实想要这个功能。

我在考虑可能最终添加其他选项,但我想实际上没有任何其他选项可能存在。 很高兴看到这个请求得到了认真对待。 漂亮的 URL 对我们很多人来说都是一件大事。

Matsko +1 表示双冒号,但我认为能够将选项传递给 path() 以及对于上面提到的某些边缘情况也很有用

+1 双冒号

@matsko让我权衡一下我们如何在 Angular 2.0 的路由器中处理这个问题。 我知道这对那些需要 ng1.x 的人没有帮助,但我认为这对你来说可能很有趣。

新路由器有一个navigate方法,您可以将 _url_ 和可选的 _options_ 对象传递给该方法。 这是基于 Backbone 路由器的历史功能,所以它支持两个选项replacetrigger如果你想从/users/new/users/123没有导致屏幕导航,您只需指定trigger:false 。 如果您还希望它用新的基于 id 的 url 替换/users/new历史条目,您还可以指定replace:true

将路由从 _new_ 路由更新到 _id-based_ 路由而不导致 DOM 转换等是此 API 的关键用例。 另一个常见的例子是分页。 例如,如果您有一个网格并且您希望用户翻阅数据,您可以使用它来使用当前页面信息更新片段,而不会导致导航。 我确信还有其他用途,但这些是我多年来在咨询中反复看到的用途。

希望这些信息有所帮助。 也许 1.x 路由器可以采用这种技术?

@EisenbergEffect以便确认。 对于 1.x,在不更改路径的情况下执行此操作的方法如下所示:

$location.path(value, { trigger : false })

这将有效地替换路径,而不会导致新的路由+控制器意识到。 这暂时可行,但您如何看待双::冒号的想法?

对于这里的其他所有人,您希望通过路由或在更改 URL 时由开发人员促进 URL 更改(无需重新加载路由)?

应该有一个位置信息的中心来源,比如一个服务
路由和浏览器都可以引用和影响,但仍然是唯一的
真相
2014 年 7 月 1 日晚上 11:28,“Matias Niemelä” [email protected]写道:

@EisenbergEffect https://github.com/EisenbergEffect以便确认。 为了
在 1.x 中,在不更改路径的情况下执行此操作的方法如下所示:

$location.path(value, { trigger : false })

这将有效地替换路径而不会导致新路径

  • 控制器意识到。 这现在可以工作,但你感觉如何
    关于双 :: 冒号的想法?

对于这里的其他人,您是否愿意更改 URL(不
重新加载路线)通过路线或开发人员提供便利
当 URL 改变时?


直接回复此邮件或在 GitHub 上查看
https://github.com/angular/angular.js/issues/1699#issuecomment -47741350。

这是另一个想法......在每个路由定义上允许两个新方法,比如entering(transitionInfo)leaving(transitionInfo)或其他东西。 如果它们存在,那么leaving方法将在离开之前在旧路由上调用,然后entering()方法将在新路由上调用。 transitionInfo将是一个如下所示的对象:

{
  newUrl: ...
  newRoute: ...
  oldUrl: ...
  oldRoute: ...
  cancelRouteChange: false,
  ignoreRouteChange: false
}

在这种方法中,我们可以检查旧的和新的路由/url,然后如果您希望 URL 更改但不重新运行路由转换,您可以将ignoreRouteChangetrue 。 如果要取消路由更改并回滚到上一个路由,请将cancelRouteChange为 true。

然后,这使开发人员可以完全控制何时发生路由转换,如果直接在地址栏中修改 URL,它将起作用,并且还定位了用于决定特定路由更改的功能,靠近关心更改的各个路由的定义.

我相信 AngularV2 路由器中有类似的东西叫做activate()@EisenbergEffect你能确认一下吗?

你怎么认为?

@matsko是的,那个 api 可能看起来像那样。 您可能还需要考虑实施replace选项,因为这也是经常需要的。 在/users/new/users/123这种特殊情况下,我认为您会利用这两个选项,因为您实际上并不希望/users/new在新用户之后保留在后台堆栈中创建的。

关于:: ,我不确定这是否完全有用。 原因是,在上面的用户示例中,重要的不是 url 模式,而是特定的应用程序上下文。 例如,如果您从customersusers/123那么您想要通知。 此外,如果您放弃users/new并转到users/456那么您也想通知。 只有当您在users/new并完成了新用户的创建时,您才想简单地更新片段以表示新创建的用户的 id 而不会导致任何转换。 这高度依赖于用户在该时间点在该用户屏幕的上下文中实际执行的操作。

@petebacondarwin这个想法的一个问题是,路由通知的允许知识并不是与路由定义一起在全球范围内可用的东西。 相反,它嵌入在特定的用户控制器中,因为该控制器是一段代码,它知道此时是否实际创建了新用户,而不是简单地从新用户屏幕导航到预先存在的用户。

对我来说,要走的路是将控制权交给想要改变路线的呼叫者。 所以我绝对赞成两个选项replacetrigger

@EisenbergEffect - 你是对的。 一般来说,这些信息不是立即可用的,尽管我想通过以下方式实现它:

有一个名为currentUser ,其中包含我们当前正在使用的用户。 我通常喜欢这样做,因为我不喜欢用代码重载我的控制器。 如果这感觉太具体,那么您可以拥有currentEntity ,这是您正在编辑/查看的当前“事物”。

然后路由enter()方法将能够注入并决定导航到此路由是否会实际更改当前实体。 如果没有,那么我们就不必费心运行转换,只需更新 URL。

我很欣赏这在大型应用程序中可能会变得有点笨拙,但我想我会把它作为一个选项扔掉。

反对让调用者(即请求路由/url 更改的那个)控制路由转换是否实际发生的论点是什么? 我记得@IgorMinar担心这会打破应用程序的一种状态直接映射到一个 URL 的约束,并且允许 URL 更改而不重新运行路由可能会使应用程序处于不同的状态,具体取决于你到达那里的方式。

@petebacondarwin这可能适用于这个特定的场景,但是处理我上面提到的分页示例有点棘手,它不想触发,也不想替换,而新用户场景想要不触发而是替换。

解决状态问题有点复杂。 原因是我们有两个 URL 代表相同的控制器状态......但不同之处在于模型。 所以,我们实际上有两种不同的状态......但从用户体验的角度来看,我们不想让用户在进行转换时受到震动。 从性能的角度来看,我们不想不必要地丢弃 DOM 并重新创建所有内容。 因此,在新用户 => 保存用户转换的情况下,我们确实希望避免触发并替换片段,以便后退按钮_不_将您带回新用户屏幕。 在内部,路由器或定位机制只需要跟踪当前片段是什么。 (我在这里做了一些假设,因为我不知道 ng1.x 在这方面是如何工作的。对于 2.x,我们确保仔细跟踪当前/前一个片段,无论它是如何更改的。)在分页场景中,我们不想触发,因为我们想要一个好的用户体验来分页数据,并且不想要不必要的 DOM 处理。 但是我们确实希望保留前一个片段,以便用户可以使用后退按钮向后移动数据页面。

请注意,在这两种情况下,它不仅与 url 和应用程序的状态有关,还与我们如何从当前状态转换到新状态有关。 如果您直接链接到新视图,这些都不重要。 当您在这些非常具体的情况下从一个转移到另一个时,这确实很重要。

+1

+1

+1

+1
并且还具有更改 URL 而不更改路由的功能

+1 - 很高兴看到我并不孤单想要这个!

+1,尚未添加(

+1

+1

+1

+1

+1

+1

+1

:+1:

你也是@basarat? :微笑:

@johnnyreilly只是有一个服务,您可以在其中注入$route$location并且您可以拥有此功能:

/** navigate */
    public navigate(url: string, reload = true) {
        this.$location.url(url);

        // Don't do a controller reload on this navigation. Note: *you* need to be careful not to actually *need* a controller reload
        if (!reload) {
            var lastRoute = this.$route.current;
            var unsub = this.$rootScope.$on('$locationChangeSuccess', () => {
                this.$route.current = lastRoute; // fake this to make the route logic think nothing changed
                unsub();
            });
        }
    }

不错@basarat!

+1

+1

我注意到当前解决方案的一件事是我的路由器解析块仍在执行。 还有人注意到这一点吗? 在 $route.current 对象中传递一些东西很容易,但这似乎很老套。 另外,这个问题似乎永远无法解决? :哭:

+1

+1

对于那些不想向后滚动的人……据我所知,这就是我们目前所拥有的(抱歉,coffeescript)。 不幸的是,这对我来说并不是 100% 有效。 我的resolve仍在执行,并且后退/前进按钮现在似乎坏了。

#
# Special $location.path function that allows you to change the url, but not
# send things through the router.
# <strong i="7">@see</strong> https://github.com/angular/angular.js/issues/1699
#
@app.run(['$rootScope', '$location', '$route', '$timeout', ($rootScope, $location, $route, $timeout) ->

    originalPath = $location.path

    $location.path = (url, reload=true) ->
        if ! reload
            lastRoute = $route.current
            if lastRoute
                unsub = $rootScope.$on('$locationChangeSuccess', ->
                    $route.current = lastRoute # fake this to make the route logic think nothing changed
                    $route.current.ignore = true
                    unsub()
                )
                # make sure to clean up and unregister the unsub function
                $timeout( ->
                    unsub()
                , 500)

        originalPath.apply($location, [url])

])

@lookfirst你调用unsub两次。 首选: https :

@basarat耸耸肩...我补充说,因为我发现 unsub() 在您的首选示例中并不总是被调用。 也许最好的办法是在 unsub 函数中添加一个守卫。

unsubbed = false
unsub = ... ->
    if ! unsubbed
        ...
        unsubbed = true
        unsub()

$timeout(unsub, 500)

无论如何,这个解决方案是一团糟。 它破坏了各种各样的东西。 我们真的需要一个角度认可的修复。

因为我发现在您的首选示例中并不总是调用 unsub()

@lookfirst您正在使用location.path我正在使用location.url 。 那可能会影响它。

只是吹毛求疵(我喜欢乐于助人):而不是originalPath.apply($location, [url])你可以做originalPath.call($location, url) (你可能已经知道了)

有趣...... location.url 工作得更好(后退按钮再次工作!),但似乎导致我的另一个问题。 当我连续 10 个小时没有编码时,我将不得不深入研究这一点。 谢谢指点和讨论,真的!

+1

+1

+1

@kuchumovn您的解决方案很棒。 我遇到的一个问题是路径更改后$routeParams不会更改。 我通过替换事件侦听器中的$routeParams的值来解决它。 我不确定这是否是正确的方法,但它对我有用。

app.factory('Location', [
    '$location',
    '$route',
    '$rootScope',
    '$routeParams',
    function ($location, $route, $rootScope, $routeParams) {
        var page_route = $route.current;

        $location.skipReload = function () {
            //var lastRoute = $route.current;
            var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                angular.copy($route.current.params, $routeParams);
                $route.current = page_route;
                unbind();
            });
            return $location;
        };

        if ($location.intercept) {
            throw '$location.intercept is already defined';
        }

        $location.intercept = function(url_pattern, load_url) {

            function parse_path() {
                var match = $location.path().match(url_pattern);
                if (match) {
                    match.shift();
                    return match;
                }
            }

            var unbind = $rootScope.$on("$locationChangeSuccess", function() {
                var matched = parse_path();
                if (!matched || load_url(matched) === false) {
                  return unbind();
                }
                angular.copy($route.current.params, $routeParams);
                $route.current = page_route;
            });
        };

        return $location;
    }
]);

+1

+1

我昨天亲自与@IgorMinar交谈过......他说未来是Angular 2.0 中的路由器,它将很快移植到1.3。 有什么要考虑的...

为什么要在 Angular 中合并 ui-router?
uui 路由器似乎可以很好地解决任何可能需要的问题。

同意@elennaro。 尽管 ui-router 需要一些创造力和研究才能使其工作,但它在我遇到的每种情况下都能奏效。 好奇采用 ui-router 并投入谷歌资源的缺点是什么?

似乎没有一个解决方案不会执行解决方案,有什么想法吗?

+1

+1

+1 $location.path(value, { trigger : false })
这方面有什么进展吗?

+1

+1

+1 完全独立的方法怎么样? 保留 path() 原样,只需添加一个 $location.hash 方法,该方法只需更改浏览器的 url 而无需重新加载控制器。 这使我们能够控制浏览器的后退导航,并且是富应用程序中的有效用例场景。

+1

目前在此页面中使用代码段: http :

+1

+1

+1

我发现的一种解决方法是添加一个带有绝对 url 的子状态。

$stateProvider.state('state.child', {
    url: '^/some-other-url'
});

然后只需执行 $state.go('.child') 即可将网址更改为您想要的网址,而无需重新加载并让每个人都开心。 请注意插入符号,它表示 url 不会从父级继承。我花了一段时间才弄清楚这一点。

+1

+1

+1

+1

+1

+1

+1

@btfordcomponentRouter如何处理(如果有的话)?

感谢@balsarori提供了一个简单有效的解决方案

在此基础上进行了详细说明,以允许更新$route路径参数,而无需重新加载控制器。

它使用console.log而不是$log因为我们可以简单地使用 uglifyjs 剥离生产构建的所有日志。

编辑:在项目中使用时修复:

/**
 * parameter handling
 */

'use strict';

angular.module('route-params', [
  'ng',
  'ngRoute',
])
.service('routeParams', function(
  $route,
  $location
) {
  var R = {};

  /**
   * <strong i="15">@ngdoc</strong> method
   * <strong i="16">@name</strong> routeParams#replace
   *
   * <strong i="17">@description</strong>
   * Replace route params, including path params, without reloading controller.
   *
   * Causes `$route` service to update the current URL, replacing
   * current route parameters with those specified in `params`.
   *
   * Provided property names that match the route's path segment
   * definitions will be interpolated into the location's path, while
   * remaining properties will be treated as query params.
   *
   * If `options.reload` is truthy, the current controller is reloaded.
   *
   * If `options.history` is truthy, the browser history is updated.
   * If `options.history` is undefined, the browser history is only updated
   * when changing value of any existing `$route` parameter,
   * ie. not when adding a previously undefined `$route` parameter.
   *
   * <strong i="18">@param</strong> {!Object<string, string>} params mapping of URL parameter names to values
   * <strong i="19">@param</strong> {!Object<string, boolean>} options `history` and `reload` options.
   *
   * <strong i="20">@returns</strong> {Object} self
   */
  R.replace = function(params, options) {
    var key, value, updateRequired;
    options = options || {};

    // Convert params to strings, and check if they differ from current route params.
    // If `options.history` is undefined, and passed params update any current route params, then set it to true.
    var currentParams = $route.current.params;

    console.log('route: params: replace', {last: angular.copy(currentParams), next: params,
      route: $route.current, options: options});

    if (!$route.current.$$route) {
      // Can't change location.
      return;
    }
    var currentPath = $route.current.$$route.originalPath;
    var history = options.history;

    for (key in params) {
      // jshint eqnull: true
      value = params[key] = (params[key] == null ? undefined : params[key] + '');

      if (value !== currentParams[key]) {
        updateRequired = true;

        console.log('route: params: replace: ' + (currentPath.search(':\\b' + key + '\\b') ? 'path.' : 'search.') +
          key + ' = ' + (key in $route.current.params ? $route.current.params[key] + ' -> ' : '') + params[key]);

        if (history === undefined && key in currentParams) {
          console.log('route: params: replace: update history: ' + key + ' = ' + currentParams[key] + ' -> ' + value);
          history = true;
        }
      }
    }

    if (updateRequired) {
      // If `options.reload` is falsey, then set current route `reloadOnSearch` to false,
      // and make passed path params equal with current route path params,
      // so that `$route` treats the change as update only, and does not broadcast `$routeChangeStart` event.
      //
      // See https://github.com/angular/angular.js/issues/1699#issuecomment-45048054
      // and https://github.com/angular/angular.js/tree/v1.3.x/src/ngRoute/route.js#L484
      // and https://github.com/angular/angular.js/tree/v1.3.x/src/ngRoute/route.js#L539
      if (!options.reload) {
        // Set current route `reloadOnSearch` to false.
        $route.current.$$route.reloadOnSearch = false;

        // Add any passed path params to current route path params, and convert them to strings.
        // Path params are detected by searching for respective name group `:key[*?]` in current route path.
        for (key in params) {
          if (currentPath.search(':\\b' + key + '\\b') !== -1) {
            $route.current.pathParams[key] = params[key];
          }
        }

        // Add any current route path params to passed params, if not set there already.
        for (key in $route.current.pathParams) {
          if (!(key in params)) {
            params[key] = $route.current.pathParams[key];
          }
        }

        // TODO: push state if `options.history` is truthy.
      }
      // If `options.history` is falsey, and controller is reloaded,
      // then make `$location` replace current history state, instead of pushing a new one.
      else if (!history) {
        $location.replace();
      }

      $route.updateParams(params);

      // Update current route params, so the change is reflected immediatelly,
      // and nearby replace() call work correctly.
      for (key in params) {
        $route.current.params[key] = params[key];
      }
    }

    return R;
  };

  return R;
});

这个问题的现状如何? 新路由器会解决吗? 准时发布 1.4? 很快?

我有同样的问题,但查询字符串参数。 更改它们时我不想重新加载。 但是我不能禁用reloadOnSearch因为我需要知道用户是否手动更改了查询字符串! :)

谢谢!

@marcalj fwiw,我们使用一项服务破解了这个解决方案,该服务使 url 与地址栏保持同步,以便我们可以禁用reloadOnSearch并获取查询参数。 此外,用户可以通过书签或链接直接导航 url。

使用 ng-router 还是 ui-router? 你能给我一个带有代码或其他东西的链接吗?

也许这可以使用新路由器来解决,我可以等待它,因为我的项目还没有完成。

谢谢!

@marcalj我们的示例有点复杂,可能已经过时,但请查看https://github.com/irthos/mngr/blob/master/app.js并检查第 10 行。它使用我们的 api.loadState 方法来识别url 来构造正确的状态。

NgSilent 是我的解决方案。
看看https://github.com/garakh/ngSilent
使用 ngRoute 1.3.15 测试

我同时使用了@sergey-buturlakin 和@kuchumovn解决方案,但我不断遇到同样的问题。

在有问题的页面(pagex)上,我使用 location.skipReload 并且效果很好 - 我可以一遍又一遍地更新路径而无需更改路线。 如果我然后使用实际的 $location.path 将路由从 pagex 更改为新控制器,它可以工作,但是如果我点击后退按钮返回到 pagex,然后尝试导航到另一个页面,我就不能再在不刷新浏览器的情况下更改我的路线。 有没有解决的办法?

+1

+1。 我喜欢 Matias 建议的双冒号方法

+1

单页应用程序在路径更改时不重新加载页面是常见的用例,
很奇怪为什么它还没有嵌入到角度。
基于@EvanWinstanley解决方案制作插件:
https://github.com/anglibs/angular-location-update

让我们在 1.5 中重新审视这一点。 #5860 有一个相关的 PR

我需要相同的功能来更新查询字符串而不触发路由控制器! :)

像: $location.searchSilent('sf', SearchFilter.getForRouteParams());

$location.path(href);
范围.$apply();
是解决方案!
我们需要通知以使用 $apply() 调整变化

这个线程已经有 2 年没有开放了,因为 angular 没有被通知——我们想在不调用所有内容的情况下更改部分路线。

@petebacondarwin不仅可以在不重新加载的情况下更新位置路径,还可以在不重新加载的情况下更新路径参数......就像上面实现的那样https://github.com/angular/angular.js/issues/1699#issuecomment -96126887

@jicasada的解决方案对我来说效果很好! 谢谢!

@jicasada这适用于 ui-router 吗? 我用 ui-router 提出了一个错误,如果父状态和子状态共享相同的 URL,reloa dOnSearch:false不起作用。 如果我使用更新的请求参数转换到子状态,它将调用父控制器两次。

+1

+1

我确认 ngSilent 做得很好。 即便如此,官方解决方案也将受到高度赞赏。

解决方案本质上是在 $location.path() 中添加一个额外的参数。

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

然后像这样使用

$location.path('/path/that/you/need', false);

我应该能够做到 $location.replace() 就像 window.location.replace 函数一样。 这很愚蠢,因为 CRUD 操作中的每个 Create 都会有这个问题。

(和)+1

+1

我建议你像这样使用“搜索参数”:
/thing?id=1234
不要在路由器提供程序中配置参数,它不会重新加载视图和控制器。
如果你想获得身份证
$location.search().id //1234
@cgross

演示http://codepen.io/dolymood/details/jrEQBx/
使用decorator来装饰ngViewDirective

+1

+1

+1

+1

+1

+1

+!

+1 已经够多了 :-)
我们得到消息。 我们会在圣诞节前看看这个!

由#15002 关闭。

此页面是否有帮助?
0 / 5 - 0 等级