$apply()
is used to execute an expression in AngularTS from outside of the AngularTS
framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
Because we are calling into the AngularTS framework we need to perform proper scope life
cycle of ng.$exceptionHandler exception handling,
ng.$rootScope.Scope#$digest executing watches.
Life cycle: Pseudo-Code of $apply()
function $apply(expr) {
try {
return $eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
$root.$digest();
}
}
Scope's $apply()
method transitions through the following stages:
Optional
expr: string | ((arg0: Scope) => any)An AngularTS expression to be executed.
string
: execute using the rules as defined in guide/expression expression.function(scope)
: execute the function with current scope
parameter.The result of evaluating the expression.
Schedule the invocation of $apply to occur at a later time. The actual time difference varies across browsers, but is typically around ~10 milliseconds.
This can be used to queue up multiple expressions which need to be evaluated in the same digest.
An AngularTS expression to be executed.
string
: execute using the rules as defined in guide/expression expression.function(scope)
: execute the function with current scope
parameter.Dispatches an event name
downwards to all child scopes (and their children) notifying the
registered ng.$rootScope.Scope#$on listeners.
The event life cycle starts at the scope on which $broadcast
was called. All
ng.$rootScope.Scope#$on listeners listening for name
event on this scope get
notified. Afterwards, the event propagates to all direct and indirect scopes of the current
scope and calls all registered listeners along the way. The event cannot be canceled.
Any exception emitted from the ng.$rootScope.Scope#$on listeners will be passed onto the ng.$exceptionHandler $exceptionHandler service.
Event name to broadcast.
Rest
...args: any[]Optional one or more arguments which will be passed onto the event listeners.
Event object, see ng.$rootScope.Scope#$on
Removes the current scope (and all of its children) from the parent scope. Removal implies that calls to ng.$rootScope.Scope#$digest $digest() will no longer propagate to the current scope and its children. Removal also implies that the current scope is eligible for garbage collection.
The $destroy()
is usually used by directives such as
ng.directive:ngRepeat ngRepeat for managing the
unrolling of the loop.
Just before a scope is destroyed, a $destroy
event is broadcasted on this scope.
Application code can register a $destroy
event handler that will give it a chance to
perform any necessary cleanup.
Note that, in AngularTS, there is also a $destroy
event, which can be used to
clean up DOM bindings before an element is removed from the DOM.
Processes all of the ng.$rootScope.Scope#$watch watchers of the current scope and
its children. Because a ng.$rootScope.Scope#$watch watcher's listener can change
the model, the $digest()
keeps calling the ng.$rootScope.Scope#$watch watchers
until no more listeners are firing. This means that it is possible to get into an infinite
loop. This function will throw 'Maximum iteration limit exceeded.'
if the number of
iterations exceeds 10.
Usually, you don't call $digest()
directly in
ng.directive:ngController controllers or in
ng.$compileProvider#directive directives.
Instead, you should call ng.$rootScope.Scope#$apply $apply() (typically from within
a ng.$compileProvider#directive directive), which will force a $digest()
.
If you want to be notified whenever $digest()
is called,
you can register a watchExpression
function with
ng.$rootScope.Scope#$watch $watch() with no listener
.
In unit tests, you may need to call $digest()
to simulate the scope life cycle.
let scope = ...;
scope.name = 'misko';
scope.counter = 0;
expect(scope.counter).toEqual(0);
scope.$watch('name', function(newValue, oldValue) {
scope.counter = scope.counter + 1;
});
expect(scope.counter).toEqual(0);
scope.$digest();
// the listener is always called during the first $digest loop after it was registered
expect(scope.counter).toEqual(1);
scope.$digest();
// but now it will not be called unless the value changes
expect(scope.counter).toEqual(1);
scope.name = 'adam';
scope.$digest();
expect(scope.counter).toEqual(2);
Dispatches an event name
upwards through the scope hierarchy notifying the
registered ng.$rootScope.Scope#$on listeners.
The event life cycle starts at the scope on which $emit
was called. All
ng.$rootScope.Scope#$on listeners listening for name
event on this scope get
notified. Afterwards, the event traverses upwards toward the root scope and calls all
registered listeners along the way. The event will stop propagating if one of the listeners
cancels it.
Any exception emitted from the ng.$rootScope.Scope#$on listeners will be passed onto the ng.$exceptionHandler $exceptionHandler service.
Event name to emit.
Rest
...args: any[]Optional one or more arguments which will be passed onto the event listeners.
Event object (see ng.$rootScope.Scope#$on).
Executes the expression
on the current scope and returns the result. Any exceptions in
the expression are propagated (uncaught). This is useful when evaluating AngularTS
expressions.
Optional
expr: string | ((arg0: Scope) => any)An AngularTS expression to be executed.
string
: execute using the rules as defined in guide/expression expression.function(scope)
: execute the function with the current scope
parameter.Local variables object, useful for overriding values in scope.
The result of evaluating the expression.
Executes the expression on the current scope at a later point in time.
The $evalAsync
makes no guarantees as to when the expression
will be executed, only
that:
expression
execution.Any exceptions from the execution of the expression are forwarded to the ng.$exceptionHandler $exceptionHandler service.
Note: if this function is called outside of a $digest
cycle, a new $digest
cycle
will be scheduled. However, it is encouraged to always call code that changes the model
from within an $apply
call. That includes code evaluated via $evalAsync
.
An AngularTS expression to be executed.
string
: execute using the rules as defined in guide/expression expression.function(scope)
: execute the function with the current scope
parameter.Local variables object, useful for overriding values in scope.
Call this method to determine if this scope has been explicitly suspended. It will not tell you whether an ancestor has been suspended. To determine if this scope will be excluded from a digest triggered at the $rootScope, for example, you must check all its ancestors:
function isExcludedFromDigest(scope) {
while(scope) {
if (scope.$isSuspended()) return true;
scope = scope.$parent;
}
return false;
Be aware that a scope may not be included in digests if it has a suspended ancestor,
even if $isSuspended()
returns false.
true if the current scope has been suspended.
Creates a new child Scope.
The parent scope will propagate the ng.$rootScope.Scope#$digest $digest() event. The scope can be removed from the scope hierarchy using ng.$rootScope.Scope#$destroy $destroy().
ng.$rootScope.Scope#$destroy $destroy() must be called on a scope when it is desired for the scope and its child scopes to be permanently detached from the parent and thus stop participating in model change detection and listener notification by invoking.
If true, then the scope does not prototypically inherit from the parent scope. The scope is isolated, as it can not see parent scope properties. When creating widgets, it is useful for the widget to not accidentally read parent state.
Optional
parent: ScopeThe ng.$rootScope.Scope Scope
that will be the $parent
of the newly created scope. Defaults to this
scope if not provided.
This is used when creating a transclude scope to correctly place it
in the scope hierarchy while maintaining the correct prototypical
inheritance.
The newly created child scope.
Event name to listen on.
Function to call when the event is emitted witn angular.IAngularEvent
Returns a deregistration function for this listener.
Listens on events of a given type. See ng.$rootScope.Scope#$emit $emit for discussion of event life cycle.
The event listener function format is: function(event, args...)
. The event
object
passed into the listener has the following attributes:
targetScope
- {Scope}
: the scope on which the event was $emit
-ed or
$broadcast
-ed.currentScope
- {Scope}
: the scope that is currently handling the event. Once the
event propagates through the scope hierarchy, this property is set to null.name
- {string}
: name of the event.stopPropagation
- {function=}
: calling stopPropagation
function will cancel
further event propagation (available only for events that were $emit
-ed).preventDefault
- {function}
: calling preventDefault
sets defaultPrevented
flag
to true.defaultPrevented
- {boolean}
: true if preventDefault
was called.Suspend watchers of this scope subtree so that they will not be invoked during digest.
This can be used to optimize your application when you know that running those watchers is redundant.
Warning
Suspending scopes from the digest cycle can have unwanted and difficult to debug results. Only use this approach if you are confident that you know what you are doing and have ample tests to ensure that bindings get updated as you expect.
Some of the things to consider are:
$apply()
or $rootScope.$digest()
.$suspend()
on an already suspended scope is a no-op.$resume()
on a non-suspended scope is a no-op.$digest()
directly on a descendant of a suspended scope will still run the watchers
for that scope and its descendants. When digesting we only check whether the current scope is
locally suspended, rather than checking whether it has a suspended ancestor.$resume()
on a scope that has a suspended ancestor will not cause the scope to be
included in future digests until all its ancestors have been resumed.$q
deferreds and $http
calls, trigger $apply()
against the $rootScope
and so will still trigger a global digest even if the promise was
initiated by a component that lives on a suspended scope.Registers a listener
callback to be executed whenever the watchExpression
changes.
watchExpression
is called on every call to ng.$rootScope.Scope#$digest $digest() and should return the value that will be watched. (watchExpression
should not change
its value when executed multiple times with the same input because it may be executed multiple
times by ng.$rootScope.Scope#$digest $digest(). That is, watchExpression
should be
idempotent.)listener
is called only when the value from the current watchExpression
and the
previous call to watchExpression
are not equal (with the exception of the initial run,
see below). Inequality is determined according to reference inequality,
strict comparison
via the !==
Javascript operator, unless objectEquality == true
(see next point)objectEquality == true
, inequality of the watchExpression
is determined
according to the angular.equals function. To save the value of the object for
later comparison, the structuredClone function is used. This therefore means that
watching complex objects will have adverse memory and performance implications.structuredClone
.listener
may change the model, which may trigger other listener
s to fire.
This is achieved by rerunning the watchers until no changes are detected. The rerun
iteration limit is 10 to prevent an infinite loop deadlock.If you want to be notified whenever ng.$rootScope.Scope#$digest $digest is called,
you can register a watchExpression
function with no listener
. (Be prepared for
multiple calls to your watchExpression
because it will execute multiple times in a
single ng.$rootScope.Scope#$digest $digest cycle if a change is detected.)
After a watcher is registered with the scope, the listener
fn is called asynchronously
(via ng.$rootScope.Scope#$evalAsync $evalAsync) to initialize the
watcher. In rare cases, this is undesirable because the listener is called when the result
of watchExpression
didn't change. To detect this scenario within the listener
fn, you
can compare the newVal
and oldVal
. If these two values are identical (===
) then the
listener was called due to initialization.
Expression that is evaluated on each
ng.$rootScope.Scope#$digest $digest cycle. A change in the return value triggers
a call to the listener
.
string
: Evaluated as guide/expression expressionfunction(scope)
: called with current scope
as a parameter.Optional
listener: WatchListenerOptional
objectEquality: booleanCompare for object equality using angular.equals instead of comparing for reference equality.
Returns a deregistration function for this listener.
// let's assume that scope was dependency injected as the $rootScope
let scope = $rootScope;
scope.name = 'misko';
scope.counter = 0;
expect(scope.counter).toEqual(0);
scope.$watch('name', function(newValue, oldValue) {
scope.counter = scope.counter + 1;
});
expect(scope.counter).toEqual(0);
scope.$digest();
// the listener is always called during the first $digest loop after it was registered
expect(scope.counter).toEqual(1);
scope.$digest();
// but now it will not be called unless the value changes
expect(scope.counter).toEqual(1);
scope.name = 'adam';
scope.$digest();
expect(scope.counter).toEqual(2);
// Using a function as a watchExpression
let food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
scope.$watch(
// This function returns the value being watched. It is called for each turn of the $digest loop
function() { return food; },
// This is the change listener, called when the value returned from the above function changes
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
// Only increment the counter if the value changed
scope.foodCounter = scope.foodCounter + 1;
}
}
);
// No digest has been run so the counter will be zero
expect(scope.foodCounter).toEqual(0);
// Run the digest but since food has not changed count will still be zero
scope.$digest();
expect(scope.foodCounter).toEqual(0);
// Update food and run digest. Now the counter will increment
food = 'cheeseburger';
scope.$digest();
expect(scope.foodCounter).toEqual(1);
Shallow watches the properties of an object and fires whenever any of the properties change
(for arrays, this implies watching the array items; for object maps, this implies watching
the properties). If a change is detected, the listener
callback is fired.
obj
collection is observed via standard $watch operation and is examined on every
call to $digest() to see if any items have been added, removed, or moved.listener
is called whenever anything within the obj
has changed. Examples include
adding, removing, and moving items belonging to an object or array.Evaluated as guide/expression expression. The
expression value should evaluate to an object or an array which is observed on each
ng.$rootScope.Scope#$digest $digest cycle. Any shallow change within the
collection will trigger a call to the listener
.
a callback function called when a change is detected.
newCollection
object is the newly modified data obtained from the obj
expressionoldCollection
object is a copy of the former collection data.
Due to performance considerations, theoldCollection
value is computed only if the
listener
function declares two or more arguments.scope
argument refers to the current scope.Returns a de-registration function for this listener. When the de-registration function is executed, the internal watch operation is terminated.
A variant of ng.$rootScope.Scope#$watch $watch() where it watches an array of watchExpressions
.
If any one expression in the collection changes the listener
is executed.
watchExpressions
array are observed via the standard $watch
operation. Their return
values are examined for changes on every call to $digest
.listener
is called whenever any expression in the watchExpressions
array changes.Array of expressions that will be individually watched using ng.$rootScope.Scope#$watch $watch()
Callback called whenever the return value of any
expression in watchExpressions
changes
The newValues
array contains the current values of the watchExpressions
, with the indexes matching
those of watchExpression
and the oldValues
array contains the previous values of the watchExpressions
, with the indexes matching
those of watchExpression
The scope
refers to the current scope.
Returns a de-registration function for all listeners.
DESIGN NOTES
The design decisions behind the scope are heavily favored for speed and memory consumption.
The typical use of scope is to watch the expressions, which most of the time return the same value as last time so we optimize the operation.
Closures construction is expensive in terms of speed as well as memory:
Loop operations are optimized by using while(count--) { ... }
Child scopes are created and removed often
There are fewer watches than observers. This is why you don't want the observer to be implemented in the same way as watch. Watch requires return of the initialization function which is expensive to construct.