Javascript 如何在不刷新控制器的情况下替换角度位置?

Javascript 如何在不刷新控制器的情况下替换角度位置?,javascript,angularjs,tabs,angularjs-routing,Javascript,Angularjs,Tabs,Angularjs Routing,假设我有一个用于编辑eCards的Angular应用程序。创建新eCard使用类似于#/eCard/create的路径,编辑现有eCard使用类似于#/eCard/:id的路径。选项卡系统允许我们一次打开多个eCard进行编辑 我们想要一个自动保存功能,就像用户希望从现代的webmail或wiki软件(或StackOverflow本身)获得的一样。我们不希望在用户打开创建表单时保存eCard草稿,这会给我们大量空白eCard草稿,因此一旦用户开始键入,我们就开始自动保存 我想在我们的控制器中编写

假设我有一个用于编辑eCards的Angular应用程序。创建新eCard使用类似于
#/eCard/create
的路径,编辑现有eCard使用类似于
#/eCard/:id
的路径。选项卡系统允许我们一次打开多个eCard进行编辑

我们想要一个自动保存功能,就像用户希望从现代的webmail或wiki软件(或StackOverflow本身)获得的一样。我们不希望在用户打开创建表单时保存eCard草稿,这会给我们大量空白eCard草稿,因此一旦用户开始键入,我们就开始自动保存

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

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

我考虑了几种缓解此问题的方法:

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

2) 在这种情况下,不要费心编辑URL和处理返回按钮的怪异行为。这很诱人,但如果用户打开其现有eCard列表并尝试打开已保存的特定eCard,我们希望选项卡系统认识到,它应该只显示当前现有选项卡,而不是打开新选项卡。
理论上,我们可以通过更新我们的标签系统来解决这个问题;它不仅可以检查路径,还可以检查路径和持久id,我们可以将它们存储在某处。这将使制表系统变得更加复杂,而这对于这个特性来说似乎是杀伤力过大

3) 仅在用户未进行积极编辑时更改URL,例如,编写
$scope.userIsIdle()
函数,如果用户进行任何编辑至少10秒,则返回
true
,然后根据该函数更新路径。简化版本如下所示:

$scope.updatePathWhenSafe = function (path) {
    if ($scope.userIsIdle()) {
        $location.path(path).replace();
    } else {
        $timeout(function () {
            $scope.updatePathWhenSafe(path);
        }, 1000);
    }
};
我最终选择了第三种选择;它比选项2简单得多,但实现和测试比我想象的要复杂得多,特别是当我考虑到边缘情况时,比如“如果该选项卡在超时触发时不再是活动选项卡,该怎么办?”我希望选项4成为可能

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


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

这不是角度的方式,但它可能很有用。接收数据后,您可以检查是否存在聚焦元素(用户正在键入)。如果是这样,那么您需要定义一个函数,该函数在元素失去焦点时执行一次。如果没有焦点元素,则立即更改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();
    }
});

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

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

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

你可能想考虑一个带有E-CARD角度指令的单视图。这样,它们可以各自拥有自己的作用域,并且可以在实例中使用,而无需重新呈现页面

他们还可以共享控制器的
$scope
中定义的功能,而不需要应用程序范围的根作用域。请注意,必须在指令上启用作用域<代码>范围:true


请查看AngularJS网站,获取有关此的教程和文档。

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


但是,如果路径类似于
ecard/latest
作为最新版本的别名,该怎么办。这样,您就不必刷新视图,因为您不必更改路径,只需在后端实现某些功能,即可将最新参数定向到最新版本的id。

对于您描述的问题,似乎最好的解决方案是使用类似于的状态机


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

事实证明,有一种方法可以完全满足我的要求,虽然它没有得到官方的赞许。有人打开了这个用例的角度标签:

提议的变更作为拉动请求提交,并被拒绝:

根据原始记录单中的注释,我实现了一个看起来像l的变通方法
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();
    }
});
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();
});