简体   繁体   中英

Update variable without scope.$apply() in AngularJS

In my project, a canvas element shows a joystick. Through mouse/touch events, the canvas is updated to look like the user is moving the joystick. This works fine. The coordinates are held in an object like so:

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

And I've added this HTML to show it to the user:

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

The problem is, when the values of scope.point.x and scope.point.y are changed (in the mouse/touch event handlers), they don't get updated in the HTML. The only solution seems to be to add:

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

to the render loop. This does work, but seems inelegant (imho) and makes performance dip noticeably, as expected.

Is there any other solution?

Thanks in advance.

PS: Though I don't think it's relevant, for reference this is the event handler code, and the render loop:

    //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);
    };

I would just make a filter, that way you still bind to the object, but your filter displays the object in a particular manner.

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

And then bind it in HTML:

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

$apply calls $rootScope.$digest internally, so use $diggest on local scope for better performance. For best performance abandon data binding and manipulate DOM directly. You can do this from own angular directive .

Read about $applyAsync() - it allows you to queue $digest() cycles and throttle them about every 10ms.

It is also is smart enough to know if Angular is already in a $digest() loop avoiding that annoying error that you'll get calling $apply() twice before $digest() has completed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM