如何在没有控制器刷新的情况下更换角度位置?

Eli*_*ght 7 javascript tabs angularjs angularjs-routing

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

我们喜欢自动保存功能,例如用户期望的内容,例如现代网络邮件或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);
Run Code Online (Sandbox Code Playgroud)

上面的代码工作得很好,除了一件事:当位置改变时,我们的控制器重新加载并且视图重新渲染.因此,如果用户在自动保存完成时处于打字过程中,则会出现短暂的闪烁,并且会丢失它们的位置.

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

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

2)在这种情况下,不要打扰编辑URL并处理后退按钮给出奇怪的行为.这很诱人,但如果用户打开现有电子贺卡列表并尝试打开已保存的特定电子贺卡,我们希望标签系统能够识别它应该只显示当前存在的标签而不是打开新标签.
我们理论上可以通过更新我们的标签系统来更聪明地解决这个问题; 它不仅可以检查路径,还可以检查路径和持久性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);
    }
};
Run Code Online (Sandbox Code Playgroud)

我最终选择了#3; 它比选项#2简单得多,但实现和测试要比我想要的复杂得多,特别是一旦我考虑了边缘情况,例如"当超时触发时标签不再是活动标签怎么办?" 我希望选项#4成为可能.

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

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

Eli*_*ght 0

事实证明,有一种方法可以完全满足我的要求,尽管它没有得到 Angular 的正式支持。有人为此确切的用例开了一张 Angular 票证:https://github.com/angular/angular.js/issues/1699

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

根据原始票证中的评论,我实现了一个如下所示的解决方法:

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;
    };
});
Run Code Online (Sandbox Code Playgroud)

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

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

基本测试表明这非常有效!