【问题标题】:How do I replace my angular location without a controller refresh?如何在不刷新控制器的情况下替换我的角度位置?
【发布时间】:2014-03-04 16:25:59
【问题描述】:

假设我有一个用于编辑电子贺卡的 Angular 应用程序。创建新的 eCard 使用类似 #/ecard/create 的路径,编辑现有 eCard 使用类似 #/ecard/:id 的路径。选项卡系统让我们可以同时打开多张电子贺卡进行编辑。

我们想要一个自动保存功能,就像用户期望的那样,例如现代 webmail 或 wiki 软件(或 StackOverflow 本身)。我们不想在用户打开创建表单的那一刻保存电子贺卡草稿,这会给我们很多空白电子贺卡草稿,因此我们会在用户开始输入后开始自动保存。

我想在我们的控制器中编写这样的代码(这被简化为不包括错误处理或关闭选项卡时停止自动保存等):

$scope.autosave = function () {
    ECardService.autosave($scope.eCard).then(function (response) {
        $location.path('/ecard/' + response.id).replace();
        $timeout($scope.autosave, AUTOSAVE_INTERVAL);
    });
};
$timeout($scope.autosave, AUTOSAVE_INTERVAL);

上面的代码效果很好,除了一件事:当位置改变时,我们的控制器重新加载并且视图重新渲染。因此,如果用户在自动保存完成时正在打字,则会出现短暂的闪烁,然后他们就会失去位置。

我考虑了几种方法来缓解这个问题:

1) 将路径更改为使用搜索路径,并在ngRoute 配置中将reloadOnSearch 设置为false。因此路径将从#/ecard?id=create 更改为例如#/ecard/id=123,因此不会强制重新加载。问题是我可能打开了多个电子贺卡,我确实想从例如更改。 #/ecard/id=123#/ecard/id=321 触发路由更改并重新加载控制器。所以这实际上是不可行的。

2) 在这种情况下,不要费心编辑 URL 并处理后退按钮会产生奇怪的行为。这很诱人,但是如果用户打开他们现有的 eCard 列表并尝试打开已保存的特定 eCard,我们希望选项卡系统能够识别它应该只显示当前存在的选项卡而不是打开一个新选项卡。
从理论上讲,我们可以通过更新我们的标签系统来解决这个问题。它不仅可以检查路径,还可以检查路径和持久 id,我们可以将其存储在某个地方。这将使选项卡系统变得更加复杂,而且对于这个功能来说这似乎有点过头了。

3) 仅在用户不主动编辑时更改 URL,例如编写一个$scope.userIsIdle() 函数返回true,如果自用户进行任何编辑后至少10 秒,则基于此更新路径。其简化版本如下所示:

$scope.updatePathWhenSafe = function (path) {
    if ($scope.userIsIdle()) {
        $location.path(path).replace();
    } else {
        $timeout(function () {
            $scope.updatePathWhenSafe(path);
        }, 1000);
    }
};

我最终选择了选项 #3;它比选项#2 简单得多,但实现和测试比我想要的要复杂得多,尤其是当我考虑到边缘情况时,例如“如果该选项卡在此超时触发时不再是活动选项卡怎么办?”我希望选项 #4 成为可能。

4) 在 Angular 之外编辑当前位置和历史记录,假设这是必要且可能的。这将是我的首选解决方案,但我的研究表明,尝试绕过 $location 服务来更改路径或编辑历史记录是不安全/不明智的。有什么安全的方法可以做到这一点吗?如果我可以说“更改当前路径但不要重新加载控制器”,这会使事情如此变得简单得多。

选项#4 可能/可行吗?如果没有,那么有没有更好的方法?也许是一些神奇的“以角度方式做,但不知何故不刷新控制器”?

【问题讨论】:

  • 对我来说,选项 (2) 听起来很明显。但是如果不了解您的 tabbing system 是如何工作的,就很难进一步研究这个选项。你说tabbing系统可以打开多个eCard进行编辑,如果你说使用路径/ecard/:id进行编辑,如何区分不同的eCard?
  • 为什么在autosavethen 函数中使用$location.path 据我了解,除非选项卡更改,否则位置不应更改。我会假设自动保存功能正在响应当前选项卡的响应,因此responseid 应该与当前的id 相同。
  • @TylerEich:新的电子贺卡没有 ID。理论上,我们可以用一个 id 来开始每个 eCard,但是如果您打开撰写表单然后关闭它而不做任何更改,就会有一个空白的 eCard 草稿。这就是为什么我们需要更改位置,例如/ecard/new/ecard/123.
  • @Beyers:为了澄清,现有电子贺卡的网址是例如/ecard/123/ecard/42 - 我使用 :id 表示我们实际上将 id 放在了路径中,然后我们通过 $routeParams 服务获得。
  • @EliCourtwright 我明白了,我要问的是您说您可以同时打开多个电子贺卡进行编辑,那么当您在同一时间?

标签: javascript angularjs tabs angularjs-routing


【解决方案1】:

这不是有角度的方式,但它可能很有用。收到数据后,您可以检查是否有焦点元素(用户正在输入)。如果是这样,那么您需要定义一个在元素失去焦点时执行一次的函数。如果没有焦点元素,立即改变url。

像这样:

ECardService.autosave($scope.eCard).then(function (response) {
    if($(':focus').length){ //if there is focused element
        $(':focus').one('blur', function(){ //
            $location.path('/ecard/' + response.id).replace(); //perform once
        });
    }
    else{
        $location.path('/ecard/' + response.id).replace();
    }
});

当然这不是最优雅的解决方案,但它似乎解决了你的问题。

【讨论】:

  • 好主意;我必须对此进行测试,看看刷新闪烁是否需要太长时间才能使其可行,但我更喜欢这种方法背后的想法,而不是我当前的超时直到用户空闲策略。
  • @EliCourtwright 谢谢!我希望这会对你有所帮助。
【解决方案2】:

如果您的代码需要跨多个视图控制器运行,AngularJS 会为此类实例提供根范围。您可以找到文档here

但是,我建议不要使用实际上是多个视图的选项卡系统。打开多个项目意味着将它们全部放在您的工作空间中。

您可能需要考虑为您的电子贺卡使用带有 Angular 指令的单一视图。这样,它们每个都可以有自己的范围,并且可以在一个实例中使用,而无需重新渲染页面。

他们还可以共享控制器的$scope 中定义的功能,而无需应用范围的根范围。请注意,必须在指令上启用范围。 scope: true

查看 AngularJS 网站以获取相关教程和文档。

【讨论】:

  • 一个有趣的想法。我们选择了多个视图,因为它可以让浏览器的后退/前进/重新加载按钮按预期工作,但您说得对,如果我们接受您的建议,这个问题提出的问题就不会成为问题。
  • 当然要权衡。我想我在上面想出了一个更好的解决方案。
【解决方案3】:

您所描述的问题的最佳解决方案似乎是使用像ui-router 这样的状态机。

使用这样的库,您可以拥有一个具有多种状态的单页应用程序(您也可以将其作为 url 的一部分),因此每当状态发生变化时,您都可以保存您的电子贺卡,然后您将永远不会有任何可见的重新加载,因为您正在处理单页应用程序。

【讨论】:

  • 我想这种方法在这种情况下无济于事。控制器会继续重新渲染页面,输入框会失去焦点。
  • 它不会重新加载,因为从技术上讲,它是一个单页应用程序,因此您并没有真正改变位置,而是在改变已经完整加载的应用程序的状态。
  • 此功能 ($location.path('/ecard/' + response.id).replace();) 也不会重新加载页面。但是控制器再次初始化。因此,活动输入字段失去焦点。
  • 您说的有道理,但这取决于您构建状态机的方式。 Ui-router 允许您拥有嵌套的控制器,然后您可以控制构成较大视图的小模板,因此在状态更改时控制器可能会重新加载,但不一定是全部。
  • 是的,我对 ui-router 很熟悉,但是如果您仔细阅读了这个问题,请确保这种方法对作者描述的情况没有帮助。
【解决方案4】:

所以我知道路径想要反映最新版本的 id,所以在这种情况下,您需要刷新每次保存。

但是,如果路径类似于 ecard/latest 作为最新版本的别名会怎样。这样您就不必刷新视图,因为您不必更改路径,只需在后端实现一些东西即可将参数 latest 指向最新版本的 id。

【讨论】:

  • 一旦创建,id 将是静态的,不会再改变。第一次必须更改的原因是,在创建之前,没有 id。为了澄清,我所指的路径是哈希标记之后的路径(因此服务器永远不会看到它),例如example.com/#/ecard/new.
  • 是的,我明白你的意思是内部路由。嗯,这是一个艰难的。我唯一能想到的就是在他们创建电子卡后立即保存它,然后有一些逻辑来计算天气如果他们从不写任何东西就删除它,或者创建一个将光标放回的路线参数到位。但我不认为重新加载是问题,当路径改变时,应用程序应该重新渲染视图。
【解决方案5】:

事实证明,有一种方法可以完全按照我的意愿行事,尽管它没有得到 Angular 的正式祝福。有人为这个确切的用例打开了 Angular 票证:https://github.com/angular/angular.js/issues/1699

提议的更改已作为拉取请求提交并被拒绝:https://github.com/angular/angular.js/pull/2398

基于原始票证中的 cmets,我实现了如下所示的解决方法:

app.factory('patchLocationWithSkipReload', function ($location, $route, $rootScope) {
    $location.skipReload = function () {
        var prevRoute = $route.current;
        var unregister = $rootScope.$on('$locationChangeSuccess', function () {
            $route.current = prevRoute;
            unregister();
        });
        return $location;
    };
});

然后我基本上可以说(为简洁起见,省略了错误处理)

ECardService.autosave($scope.eCard).then(function (response) {
    $location.skipReload().path('/ecard/' + response.id).replace();
    $scope.resetAutosaveTimeout();
});

基本测试表明这很有效!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-28
    • 2018-03-24
    • 2020-08-05
    • 1970-01-01
    • 2019-11-10
    • 2019-12-11
    • 1970-01-01
    • 2018-10-05
    相关资源
    最近更新 更多