Chapter 4: Safe $apply

by billy roberts

HTML

<script src="https://code.angularjs.org/1.3.2/angular.min.js"></script>
<div ng-app="myApp">
    <div ng-controller="Ctrl">
        <button ng-click="increment()">Increment</button>
        {{ val }}
    </div>
</div>

JavaScript

// a cleaner safeApply
// decorate the root scope

angular.module('myApp', [])
.config(function($provide) {
    // define decorator for $rootScope service
    return $provide.decorator('$rootScope', function($delegate) {
        // $delegate acts as the $rootScope instance
        $delegate.safeApply = function(func) {
            var currentPhase = $delegate.$$phase;
            
            // determine if already in $apply/$digest phase
            if (currentPhase === "$apply" || 
                currentPhase === "$digest") {
                // already inside $apply/$digest phase
                
                // if safeApply() was passed a function, invoke it
                if (typeof func === 'function') {
                    func();
                }
            }
            else {
                // not inside $apply/$digest phase, safe to invoke $apply
                $delegate.$apply(func);
            }
        };
        return $delegate;
    });
})
.controller('Ctrl', function ($scope) {
    $scope.val = 0;
    
    // method that may or may not be called from somewhere
    // that will not trigger a $digest
    $scope.increment = function () {
        $scope.val++;
        $scope.safeApply();
    };
    
    // application component that modifies the model without
    // triggering a $digest
    setInterval(function () {
        $scope.increment();
    }, 1000);
});