【问题标题】:Update variable without scope.$apply() in AngularJS在AngularJS中更新没有范围的变量。 $apply()
【发布时间】:2015-10-29 23:40:46
【问题描述】:

在我的项目中,画布元素显示了一个操纵杆。通过鼠标/触摸事件,画布被更新为用户正在移动操纵杆。这工作正常。坐标保存在一个对象中,如下所示:

scope.point = {
        x: 0,
        y: 0,
    };

我已经添加了这个 HTML 来展示给用户:

<span>X:{{point.x.toFixed(2)}} Y:{{point.y.toFixed(2)}}</span>

问题是,当 scope.point.x 和 scope.point.y 的值发生变化时(在鼠标/触摸事件处理程序中),它们不会在 HTML 中得到更新。唯一的解决方案似乎是添加:

scope.$apply()
//or
scope.$digest()

到渲染循环。这确实有效,但看起来不优雅(恕我直言)并且正如预期的那样使性能明显下降。

还有其他解决办法吗?

提前致谢。

PS:虽然我认为这无关紧要,但作为参考,这是事件处理程序代码和渲染循环:

    //handles mouse or touch movement on joystick
    scope.mouseMove = function(evt) {
        if (leftClick == 1) { //check if left mouse button down or touch
            // get cursor or touch coordinates, saved in point object.
            if (evt.type == 'touchstart' || evt.type == 'touchmove') {
                scope.point.x = evt.targetTouches[0].pageX - joystick.offsetLeft;
                scope.point.y = evt.targetTouches[0].pageY - joystick.offsetTop;
            } else {
                scope.point.x = evt.pageX - joystick.offsetLeft - 3;
                scope.point.y = evt.pageY - joystick.offsetTop - 3;
            };
            //make coordinates relative to canvas center
            scope.point = GeometrySrv.centerCoord(scope.point, joystick);
            //if Directional Lock is ON, enforce
            if (scope.lockMode != "fullAnalog") {
                scope.point = GeometrySrv.forceDirectionLock(scope.point.x, scope.point.y, scope.lockMode);
            };
            // force coordinates into maxRadius
            if (!GeometrySrv.isInsideCircle(scope.point.x, scope.point.y, maxRadius)) {
                scope.point = GeometrySrv.forceIntoCircle(scope.point.x, scope.point.y, maxRadius);
            };
            //send coordinates back to server (websocket)
            updateJoystick(scope.point, scope.lockMode);
        };
    };

    function renderLoop() {
        //erases previous joystick position
        resetJoystick();
        // erases previous vector
        resetVector();
        //change coordinates to canvas reference
        scope.point = GeometrySrv.canvasCoord(scope.point, joystick);
        DrawSrv.drawLineFromCenter(joystickctx, scope.point.x, scope.point.y);
        if (scope.showVector) {
            DrawSrv.drawLineFromCenter(vectorctx, scope.point.x * vector.width / joystick.width, scope.point.y * vector.width / joystick.width);
        };
        //redraw joystick position
        DrawSrv.drawCircle(joystickctx, scope.point.x, scope.point.y, radius, maxRadiusBGColor);
        //change back to relative coordinates
        scope.point = GeometrySrv.centerCoord(scope.point, joystick);
        //scope.$digest();
        //call renderLoop every 15ms (60fps)
        renderReq = requestAnimationFrame(renderLoop);
    };

【问题讨论】:

  • 如果事件发生在改变范围的角核心之外,你必须使用$applystackoverflow.com/a/31756769/1175966
  • 感谢您的回答。这是一个明确的“不可能”吗?真可惜,因为操纵杆的性能确实受到影响,特别是在移动设备上添加 $apply 时。如果是这种情况,我想我会删除文本,因为它弊大于利。

标签: javascript angularjs canvas data-binding angularjs-scope


【解决方案1】:

我只是做一个过滤器,这样你仍然绑定到对象,但你的过滤器以特定的方式显示对象。

[your angular module].filter('toFixed', [function () {
    return function (input) {
        if (typeof input.toFixed == 'function')
            return input.toFixed(2);
        return input;
    };
}]);

然后在 HTML 中绑定:

<span>X:{{point.x | toFixed}} Y:{{point.y | toFixed}}</span>    

【讨论】:

  • 感谢您的快速回复。不幸的是,我刚刚尝试了这个解决方案并没有解决我的问题。也许我没有解释自己:我希望文本在操纵杆移动时动态更新 X 和 Y 的值。过滤器似乎不会触发这些值的更新。实际变量确实发生了变化(坐标通过 websockets 发送到服务器,我可以看到它们动态变化),它只是没有显示在 中,除非调用了 scope.$apply 或 $digest。无论如何,再次感谢您的回答。
  • @charlietfl 为什么不鼓励这样做?显然是完全无知的要求,但我在某处(binpress.com/tutorial/…)读到 $digest 对性能更好,因为它不会“上升”到 rootScope,而是只作用于当前范围和子级。谢谢你的回答。
  • @xpeiro 我当时撤回了我的评论......这是我几年前学到的东西,我认为它当时不受欢迎。那篇文章的作者是一位角度风格指南大师。可能是性能改进改变了我之前学到的东西
【解决方案2】:

$apply 在内部调用$rootScope.$digest,因此在本地范围内使用$diggest 以获得更好的性能。为了获得最佳性能,放弃数据绑定并直接操作 DOM。您可以从自己的角度 directive 执行此操作。

【讨论】:

  • 首先感谢您的回答!我确实读过关于 $digest 与 $apply 的文章(链接到 Joe Pontani 回答的 cmets 中的文章),但我不明白直接操作 DOM 会有什么帮助?我确实尝试在渲染循环中修改&lt;span&gt;innerHTML,如下所示:element[0].children[3].innerHTML= scope.point.x。它有效,但在性能方面我没有发现太大的改进。将其移至指令会改变什么吗?还是我错过了什么?我点击了“有用”的箭头,但似乎我没有足够的声誉。
  • 尝试throttle HTML 更新。
  • 是的,我最终做了类似的事情:我没有创建一个节流函数(不知道,感谢指针),我只是在渲染循环中添加了一个计数器,所以它调用scope.$digest() 每 14 个循环(每个循环 ~1000/60=17ms*14~240ms)而不是每一个循环。这绝对是一种改进,可以接受的妥协。问题是,我需要尽可能准确的实际坐标(它们在后端用于实时控制机器人),但页面上的显示可能不太准确,因为它仅用于用户反馈。再次感谢!
【解决方案3】:

了解$applyAsync() - 它允许您对 $digest() 周期进行排队并大约每 10 毫秒对其进行一次限制。

知道 Angular 是否已经在 $digest() 循环中也很聪明,可以避免在 $digest() 完成之前调用 $apply() 两次的烦人错误。

【讨论】:

    猜你喜欢
    • 2016-07-15
    • 1970-01-01
    • 2013-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-24
    相关资源
    最近更新 更多