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

时间:2014-03-04 16:26:00

标签: javascript angularjs tabs angularjs-routing

假设我有一个用于编辑电子贺卡的Angular应用程序。创建新的电子贺卡使用#/ecard/create之类的路径,编辑现有的电子贺卡会使用#/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)更改路径以使用搜索路径,并在reloadOnSearch配置中将false设置为ngRoute。因此路径会从#/ecard?id=create变为例如#/ecard/id=123因此不会强行重装。问题是我可能打开了多个电子贺卡,我确实想要从#/ecard/id=123#/ecard/id=321触发路由更改并重新加载控制器。所以这不可行。

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

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

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

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

5 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:2)

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

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

您可能需要考虑使用Angular指令的单个视图来存储您的电子贺卡。这样,他们每个人都可以拥有自己的范围,并且可以在实例中使用而无需重新呈现页面。

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

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

答案 2 :(得分:1)

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

使用类似这样的库,您可以拥有一个具有多个状态的单页应用程序(您也可以成为该网址的一部分),因此无论何时状态发生变化,您都可以保存您的电子贺卡。 ; ll永远不会有任何可见的重新加载,因为您正在处理单页应用程序。

答案 3 :(得分:0)

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

但是,如果路径类似于ecard/latest作为最新版本的别名,那该怎么办?这样你就不必刷新你的视图,因为你不必改变你的路径,只需在后端实现一些东西,最后将param指向最新版本的id。

答案 4 :(得分: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;
    };
});

然后我能够基本上(为了简洁而省略错误处理)说

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

基本测试表明这很有效!