Angular - Angular change detection and runtime optimization
Angular change detection and runtime optimization
link
Change detection
is the process through which Angular checks to see whether your application state has changed, and if any DOM needs to be updated. At a high level, Angular walks your components from top to bottom, looking for changes. Angular runs its change detection mechanism periodically so that changes to the data model are reflected in an application’s view. Change detection can be triggered either manually or through an asynchronous event (for example, a user interaction or an XMLHttpRequest completion).
Change detection is a highly optimized performant, but it can still cause slowdowns if the application runs it too frequently.
In this guide, you’ll learn how to control and optimize the change detection mechanism by skipping parts of your application and running change detection only when necessary.
Watch this video if you prefer to learn more about performance optimizations in a media format:
Last reviewed on Wed May 04 2022
Angular - Cheat Sheet
Cheat Sheet
link
Bootstrapping
Details
import{platformBrowserDynamic} from '@angular/platform-browser-dynamic';
Import
platformBrowserDynamic
from
@angular/platform-browser-dynamic
.
<video #movieplayer …></video><button (click)="movieplayer.play()">
Play
</button>
Creates a local variable
movieplayer
that provides access to the
video
element instance in data-binding and event-binding expressions in the current template.
<p *myUnless="myExpression">
…
</p>
The asterisk (
*
) character turns the current element into an embedded template. Equivalent to:
Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return
{class-name: true/false}
map.
Allows you to assign styles to an HTML element using CSS. You can use CSS directly, as in the first example, or you can call a method from the component.
Forms
Details
import{FormsModule} from '@angular/forms';
Import
FormsModule
from
@angular/forms
.
<input [(ngModel)]="userName">
Provides two-way data-binding, parsing, and validation for form controls.
Class decorators
Details
import{Directive,…} from '@angular/core';
Import
Directive, …
from
@angular/core';
.
@Component({…})classMyComponent(){}
Declares that a class is a component and provides metadata about the component.
@Directive({…})classMyDirective(){}
Declares that a class is a directive and provides metadata about the directive.
@Pipe({…})classMyPipe(){}
Declares that a class is a pipe and provides metadata about the pipe.
@Injectable()classMyService(){}
Declares that a class can be provided and injected by other classes. Without this decorator, the compiler won't generate enough metadata to allow the class to be created properly when it's injected somewhere.
Directive configuration
Details
@Directive({
property1: value1,…})
Add
property1
property with
value1
value to Directive.
selector:'.cool-button:not(a)'
Specifies a CSS selector that identifies this directive within a template. Supported selectors include
element
,
[attribute]
,
.class
, and
:not()
.
Does not support parent-child relationship selectors.
providers:[MyService,{ provide:…}]
List of dependency injection providers for this directive and its children.
Component configuration
@Component
extends
@Directive
, so the
@Directive
configuration applies to components as well
Details
moduleId: module.id
If set, the
templateUrl
and
styleUrl
are resolved relative to the component.
viewProviders:[MyService,{ provide:…}]
List of dependency injection providers scoped to this component's view.
Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the
/
prefix; for a child route, use the
./
prefix; for a sibling or parent, use the
../
prefix.
The provided classes and
aria-current
attribute are added to the element when the
routerLink
becomes the current active route.
function canActivateGuard:CanActivateFn=(
route:ActivatedRouteSnapshot,
state:RouterStateSnapshot)=>{…}{ path:…, canActivate:[canActivateGuard]}
An interface for defining a function that the router should call first to determine if it should activate this component. Should return a
boolean|UrlTree
or an Observable/Promise that resolves to a
boolean|UrlTree
.
function canDeactivateGuard:CanDeactivateFn<T>=(
component: T,
route:ActivatedRouteSnapshot,
state:RouterStateSnapshot)=>{…}{ path:…, canDeactivate:[canDeactivateGuard]}
An interface for defining a function that the router should call first to determine if it should deactivate this component after a navigation. Should return a
boolean|UrlTree
or an Observable/Promise that resolves to a
boolean|UrlTree
.
function canActivateChildGuard:CanActivateChildFn=(
route:ActivatedRouteSnapshot,
state:RouterStateSnapshot)=>{…}{ path:…, canActivateChild:[canActivateGuard], children:…}
An interface for defining a function that the router should call first to determine if it should activate the child route. Should return a
boolean|UrlTree
or an Observable/Promise that resolves to a
boolean|UrlTree
.
function resolveGuard implementsResolveFn<T>=(
route:ActivatedRouteSnapshot,
state:RouterStateSnapshot)=>{…}{ path:…, resolve:[resolveGuard]}
An interface for defining a function that the router should call first to resolve route data before rendering the route. Should return a value or an Observable/Promise that resolves to a value.
function canLoadGuard:CanLoadFn=(
route:Route)=>{…}{ path:…, canLoad:[canLoadGuard], loadChildren:…}
An interface for defining a function that the router should call first to check if the lazy loaded module should be loaded. Should return a
boolean|UrlTree
or an Observable/Promise that resolves to a
boolean|UrlTree
.
Use class and style bindings to add and remove CSS class names from an element's
class
attribute and to set styles dynamically.
Prerequisites
link
Property binding
Binding to a single CSS
class
link
To create a single class binding, type the following:
[class.sale]="onSale"
Angular adds the class when the bound expression,
onSale
is truthy, and it removes the class when the expression is falsy—with the exception of
undefined
. See styling delegation for more information.
Binding to multiple CSS classes
link
To bind to multiple classes, type the following:
[class]="classExpression"
The expression can be one of:
A space-delimited string of class names.
An object with class names as the keys and truthy or falsy expressions as the values.
An array of class names.
With the object format, Angular adds a class only if its associated value is truthy.
With any object-like expression—such as
object
,
Array
,
Map
, or
Set
—the identity of the object must change for Angular to update the class list.
Updating the property without changing object identity has no effect.
If there are multiple bindings to the same class name, Angular uses styling precedence to determine which binding to use.
The following table summarizes class binding syntax.
Binding Type
Syntax
Input Type
Example Input Values
Single class binding
[class.sale]="onSale"
boolean | undefined | null
true
,
false
Multi-class binding
[class]="classExpression"
string
"my-class-1 my-class-2 my-class-3"
Multi-class binding
[class]="classExpression"
Record<string, boolean | undefined | null>
{foo: true, bar: false}
Multi-class binding
[class]="classExpression"
Array<string>
['foo', 'bar']
Binding to a single style
link
To create a single style binding, use the prefix
style
followed by a dot and the name of the CSS style.
For example, to set the
width
style, type the following:
[style.width]="width"
Angular sets the property to the value of the bound expression, which is usually a string. Optionally, you can add a unit extension like
em
or
%
, which requires a number type.
To write a style in dash-case, type the following:
<nav [style.background-color]="expression"></nav>
To write a style in camelCase, type the following:
<nav [style.backgroundColor]="expression"></nav>
Binding to multiple styles
link
To toggle multiple styles, bind to the
[style]
attribute—for example,
[style]="styleExpression"
. The
styleExpression
can be one of:
A string list of styles such as
"width: 100px; height: 100px; background-color: cornflowerblue;"
.
An object with style names as the keys and style values as the values, such as
{width: '100px', height: '100px', backgroundColor: 'cornflowerblue'}
.
Note that binding an array to
[style]
is not supported.
When binding
[style]
to an object expression, the identity of the object must change for Angular to update the class list.
Updating the property without changing object identity has no effect.
A number of Angular CLI commands run a complex process on your code, such as linting, building, or testing.
The commands use an internal tool called Architect to run
CLI builders
, which apply another tool to accomplish the wanted task.
With Angular version 8, the CLI Builder API is stable and available to developers who want to customize the Angular CLI by adding or modifying commands.
For example, you could supply a builder to perform an entirely new task, or to change which third-party tool is used by an existing command.
This document explains how CLI builders integrate with the workspace configuration file, and shows how you can create your own builder.
Find the code from the examples used here in this GitHub repository.
CLI builders
link
The internal Architect tool delegates work to handler functions called
builders
.
A builder handler function receives two arguments; a set of input
options
(a JSON object), and a
context
(a
BuilderContext
object).
The separation of concerns here is the same as with schematics, which are used for other CLI commands that touch your code (such as
ng generate
).
The
options
object is provided by the CLI user, while the
context
object is provided by the CLI Builder API
In addition to the contextual information, the
context
object, which is an instance of the
BuilderContext
, also provides access to a scheduling method,
context.scheduleTarget()
.
The scheduler executes the builder handler function with a given target configuration.
The builder handler function can be synchronous (return a value) or asynchronous (return a Promise), or it can watch and return multiple values (return an Observable).
The return value or values must always be of type
BuilderOutput
.
This object contains a Boolean
success
field and an optional
error
field that can contain an error message.
Angular provides some builders that are used by the CLI for commands such as
ng build
and
ng test
.
Default target configurations for these and other built-in CLI builders can be found (and customized) in the "architect" section of the workspace configuration file,
angular.json
.
Also, extend and customize Angular by creating your own builders, which you can run using the
ng run
CLI command.
Builder project structure
link
A builder resides in a "project" folder that is similar in structure to an Angular workspace, with global configuration files at the top level, and more specific configuration in a source folder with the code files that define the behavior.
For example, your
myBuilder
folder could contain the following files.
Files
Purpose
src/my-builder.ts
Main source file for the builder definition.
src/my-builder.spec.ts
Source file for tests.
src/schema.json
Definition of builder input options.
builders.json
Builders definition.
package.json
Dependencies. See https://docs.npmjs.com/files/package.json.
tsconfig.json
TypeScript configuration.
Publish the builder to
npm
(see Publishing your Library).
If you publish it as
@example/my-builder
, install it using the following command.
npm install @example/my-builder
Creating a builder
link
As an example, create a builder that copies a file.
To create a builder, use the
createBuilder()
CLI Builder function, and return a
Promise<BuilderOutput>
object.
Now let's add some logic to it.
The following code retrieves the source and destination file paths from user options and copies the file from the source to the destination (using the Promise version of the built-in NodeJS
copyFile()
function).
If the copy operation fails, it returns an error with a message about the underlying problem.
By default,
copyFile()
does not print anything to the process standard output or error.
If an error occurs, it might be difficult to understand exactly what the builder was trying to do when the problem occurred.
Add some additional context by logging additional information using the
Logger
API.
This also lets the builder itself be executed in a separate process, even if the standard output and error are deactivated (as in an Electron app).
You can retrieve a
Logger
instance from the context.
The CLI Builder API includes progress and status reporting tools, which can provide hints for certain functions and interfaces.
To report progress, use the
context.reportProgress()
method, which takes a current value, (optional) total, and status string as arguments.
The total can be any number; for example, if you know how many files you have to process, the total could be the number of files, and current should be the number processed so far.
The status string is unmodified unless you pass in a new string value.
You can see an example of how the
tslint
builder reports progress.
In our example, the copy operation either finishes or is still executing, so there's no need for a progress report, but you can report status so that a parent builder that called our builder would know what's going on.
Use the
context.reportStatus()
method to generate a status string of any length.
NOTE
:
There's no guarantee that a long string will be shown entirely; it could be cut to fit the UI that displays it.
Pass an empty string to remove the status.
src/my-builder.ts (progress reporting)
import{BuilderContext,BuilderOutput, createBuilder }from'@angular-devkit/architect';import{JsonObject}from'@angular-devkit/core';import{ promises as fs }from'fs';interfaceOptionsextendsJsonObject{
source:string;
destination:string;}exportdefault createBuilder(copyFileBuilder);asyncfunction copyFileBuilder(
options:Options,
context:BuilderContext,):Promise<BuilderOutput>{
context.reportStatus(`Copying ${options.source} to ${options.destination}.`);try{await fs.copyFile(options.source, options.destination);}catch(err){
context.logger.error('Failed to copy file.');return{
success:false,
error: err.message,};}
context.reportStatus('Done.');return{ success:true};}
Builder input
link
You can invoke a builder indirectly through a CLI command, or directly with the Angular CLI
ng run
command.
In either case, you must provide required inputs, but can let other inputs default to values that are pre-configured for a specific
target
, provide a pre-defined, named override configuration, and provide further override option values on the command line.
Input validation
link
You define builder inputs in a JSON schema associated with that builder.
The Architect tool collects the resolved input values into an
options
object, and validates their types against the schema before passing them to the builder function.
(The Schematics library does the same kind of validation of user input.)
For our example builder, you expect the
options
value to be a
JsonObject
with two keys:
A
source
and a
destination
, each of which are a string.
You can provide the following schema for type validation of these values.
This is a very simple example, but the use of a schema for validation can be very powerful.
For more information, see the JSON schemas website.
To link our builder implementation with its schema and name, you need to create a
builder definition
file, which you can point to in
package.json
.
Create a file named
builders.json
that looks like this:
builders.json
{"builders":{"copy":{"implementation":"./dist/my-builder.js","schema":"./src/schema.json","description":"Copies a file."}}}
In the
package.json
file, add a
builders
key that tells the Architect tool where to find our builder definition file.
package.json
{"name":"@example/copy-file","version":"1.0.0","description":"Builder for copying files","builders":"builders.json","dependencies":{"@angular-devkit/architect":"~0.1200.0","@angular-devkit/core":"^12.0.0"}}
The official name of our builder is now
@example/copy-file:copy
.
The first part of this is the package name (resolved using node resolution), and the second part is the builder name (resolved using the
builders.json
file).
Using one of our
options
is very straightforward.
You did this in the previous section when you accessed
options.source
and
options.destination
.
src/my-builder.ts (report status)
context.reportStatus(`Copying ${options.source} to ${options.destination}.`);try{await fs.copyFile(options.source, options.destination);}catch(err){
context.logger.error('Failed to copy file.');return{
success:false,
error: err.message,};}
context.reportStatus('Done.');return{ success:true};
Target configuration
link
A builder must have a defined target that associates it with a specific input configuration and project.
Targets are defined in the
angular.json
CLI configuration file.
A target specifies the builder to use, its default options configuration, and named alternative configurations.
The Architect tool uses the target definition to resolve input options for a given run.
The
angular.json
file has a section for each project, and the "architect" section of each project configures targets for builders used by CLI commands such as 'build', 'test', and 'lint'.
By default, for example, the
build
command runs the builder
@angular-devkit/build-angular:browser
to perform the build task, and passes in default option values as specified for the
build
target in
angular.json
.
The command passes the builder the set of default options specified in the "options" section.
If you pass the
--configuration=production
flag, it uses the override values specified in the
production
alternative configuration.
Specify further option overrides individually on the command line.
You might also add more alternative configurations to the
build
target, to define other environments such as
stage
or
qa
.
Target strings
link
The generic
ng run
CLI command takes as its first argument a target string of the following form.
project:target[:configuration]
Details
project
The name of the Angular CLI project that the target is associated with.
target
A named builder configuration from the
architect
section of the
angular.json
file.
configuration
(optional) The name of a specific configuration override for the given target, as defined in the
angular.json
file.
If your builder calls another builder, it might need to read a passed target string.
Parse this string into an object by using the
targetFromTargetString()
utility function from
@angular-devkit/architect
.
Schedule and run
link
Architect runs builders asynchronously.
To invoke a builder, you schedule a task to be run when all configuration resolution is complete.
The builder function is not executed until the scheduler returns a
BuilderRun
control object.
The CLI typically schedules tasks by calling the
context.scheduleTarget()
function, and then resolves input options using the target definition in the
angular.json
file.
Architect resolves input options for a given target by taking the default options object, then overwriting values from the configuration used (if any), then further overwriting values from the overrides object passed to
context.scheduleTarget()
.
For the Angular CLI, the overrides object is built from command line arguments.
Architect validates the resulting options values against the schema of the builder.
If inputs are valid, Architect creates the context and executes the builder.
For more information see Workspace Configuration.
You can also invoke a builder directly from another builder or test by calling
context.scheduleBuilder()
.
You pass an
options
object directly to the method, and those option values are validated against the schema of the builder without further adjustment.
Only the
context.scheduleTarget()
method resolves the configuration and overrides through the
angular.json
file.
Default architect configuration
link
Let's create a simple
angular.json
file that puts target configurations into context.
You can publish the builder to npm (see Publishing your Library), and install it using the following command:
npm install @example/copy-file
If you create a new project with
ng new builder-test
, the generated
angular.json
file looks something like this, with only default builder configurations.
angular.json
{//…"projects":{//…"builder-test":{//…"architect":{//…"build":{"builder":"@angular-devkit/build-angular:browser","options":{//… more options…"outputPath":"dist/builder-test","index":"src/index.html","main":"src/main.ts","polyfills":"src/polyfills.ts","tsConfig":"src/tsconfig.app.json"},"configurations":{"production":{//… more options…"optimization":true,"aot":true,"buildOptimizer":true}}}}}}//…}
Adding a target
link
Add a new target that will run our builder to copy a file.
This target tells the builder to copy the
package.json
file.
You need to update the
angular.json
file to add a target for this builder to the "architect" section of our new project.
We'll add a new target section to the "architect" object for our project
The target named "copy-package" uses our builder, which you published to
@example/copy-file
.
(See Publishing your Library.)
The options object provides default values for the two inputs that you defined;
source
, which is the existing file you are copying, and
destination
, the path you want to copy to
The
configurations
key is optional, we'll leave it out for now
To run our builder with the new target's default configuration, use the following CLI command.
ng run builder-test:copy-package
This copies the
package.json
file to
package-copy.json
.
Use command-line arguments to override the configured defaults.
For example, to run with a different
destination
value, use the following CLI command.
ng run builder-test:copy-package--destination=package-other.json
This copies the file to
package-other.json
instead of
package-copy.json
.
Because you did not override the
source
option, it will copy from the
package.json
file (the default value provided for the target).
Testing a builder
link
Use integration testing for your builder, so that you can use the Architect scheduler to create a context, as in this example.
In the builder source directory, you have created a new test file
my-builder.spec.ts
.
The code creates new instances of
JsonSchemaRegistry
(for schema validation),
TestingArchitectHost
(an in-memory implementation of
ArchitectHost
), and
Architect
.
We've added a
builders.json
file next to the builder's
package.json
file, and modified the package file to point to it.
Here's an example of a test that runs the copy file builder.
The test uses the builder to copy the
package.json
file and validates that the copied file's contents are the same as the source.
src/my-builder.spec.ts
import{Architect}from'@angular-devkit/architect';import{TestingArchitectHost}from'@angular-devkit/architect/testing';import{ schema }from'@angular-devkit/core';import{ promises as fs }from'fs';
describe('Copy File Builder',()=>{let architect:Architect;let architectHost:TestingArchitectHost;
beforeEach(async()=>{const registry =new schema.CoreSchemaRegistry();
registry.addPostTransform(schema.transforms.addUndefinedDefaults);// TestingArchitectHost() takes workspace and current directories.// Since we don't use those, both are the same in this case.
architectHost =newTestingArchitectHost(__dirname, __dirname);
architect =newArchitect(architectHost, registry);// This will either take a Node package name, or a path to the directory// for the package.json file.await architectHost.addBuilderFromPackage('..');});
it('can copy files',async()=>{// A "run" can have multiple outputs, and contains progress information.const run =await architect.scheduleBuilder('@example/copy-file:copy',{
source:'package.json',
destination:'package-copy.json',});// The "result" member (of type BuilderOutput) is the next output.const output =await run.result;// Stop the builder from running. This stops Architect from keeping// the builder-associated states in memory, since builders keep waiting// to be scheduled.await run.stop();// Expect that the copied file is the same as its source.const sourceContent =await fs.readFile('package.json','utf8');const destinationContent =await fs.readFile('package-copy.json','utf8');
expect(destinationContent).toBe(sourceContent);});});
When running this test in your repo, you need the
ts-node
package.
You can avoid this by renaming
my-builder.spec.ts
to
my-builder.spec.js
.
Watch mode
link
Architect expects builders to run once (by default) and return.
This behavior is not entirely compatible with a builder that watches for changes (like Webpack, for example).
Architect can support watch mode, but there are some things to look out for.
To be used with watch mode, a builder handler function should return an Observable.
Architect subscribes to the Observable until it completes and might reuse it if the builder is scheduled again with the same arguments.
The builder should always emit a
BuilderOutput
object after each execution.
Once it's been executed, it can enter a watch mode, to be triggered by an external event.
If an event triggers it to restart, the builder should execute the
context.reportRunning()
function to tell Architect that it is running again.
This prevents Architect from stopping the builder if another run is scheduled.
When your builder calls
BuilderRun.stop()
to exit watch mode, Architect unsubscribes from the builder's Observable and calls the builder's teardown logic to clean up.
(This behavior also allows for long-running builds to be stopped and cleaned up.)
In general, if your builder is watching an external event, you should separate your run into three phases.
Phases
Details
Running
For example, webpack compiles. This ends when webpack finishes and your builder emits a
BuilderOutput
object.
Watching
Between two runs, watch an external event stream. For example, webpack watches the file system for any changes. This ends when webpack restarts building, and
context.reportRunning()
is called. This goes back to step 1.
Completion
Either the task is fully completed (for example, webpack was supposed to run a number of times), or the builder run was stopped (using
BuilderRun.stop()
). Your teardown logic is executed, and Architect unsubscribes from your builder's Observable.
Summary
link
The CLI Builder API provides a new way of changing the behavior of the Angular CLI by using builders to execute custom logic.
Builders can be synchronous or asynchronous, execute once or watch for external events, and can schedule other builders or targets
Builders have option defaults specified in the
angular.json
configuration file, which can be overwritten by an alternate configuration for the target, and further overwritten by command line flags
We recommend that you use integration tests to test Architect builders.
Use unit tests to validate the logic that the builder executes.
If your builder returns an Observable, it should clean up in the teardown logic of that Observable
Angular - Observables compared to other techniques
Observables compared to other techniques
link
Observables compared to promises
Creation and subscription
Chaining
Cancellation
Error handling
Cheat sheet
Observables compared to events API
Observables compared to arrays
You can often use observables instead of promises to deliver values asynchronously.
Similarly, observables can take the place of event handlers.
Finally, because observables deliver multiple values, you can use them where you might otherwise build and operate on arrays.
Observables behave somewhat differently from the alternative techniques in each of these situations, but offer some significant advantages.
Here are detailed comparisons of the differences.
Observables compared to promises
link
Observables are often compared to promises.
Here are some key differences:
Observables are declarative; computation does not start until subscription.
Promises execute immediately on creation.
This makes observables useful for defining recipes that can be run whenever you need the result.
Observables provide many values.
Promises provide one.
This makes observables useful for getting multiple values over time.
Observables differentiate between chaining and subscription.
Promises only have
.then()
clauses.
This makes observables useful for creating complex transformation recipes to be used by other part of the system, without causing the work to be executed.
Observables
subscribe()
is responsible for handling errors.
Promises push errors to the child promises.
This makes observables useful for centralized and predictable error handling.
Creation and subscription
link
Observables are not executed until a consumer subscribes.
The
subscribe()
executes the defined behavior once, and it can be called again.
Each subscription has its own computation.
Resubscription causes recomputation of values.
Promises execute immediately, and just once.
The computation of the result is initiated when the promise is created.
There is no way to restart work.
All
then
clauses (subscriptions) share the same computation.
Observables differentiate between transformation function such as a map and subscription.
Only subscription activates the subscriber function to start computing the values.
src/observables.ts (chain)
observable.pipe(map(v =>2* v));
Promises do not differentiate between the last
.then
clauses (equivalent to subscription) and intermediate
.then
clauses (equivalent to map).
src/promises.ts (chain)
promise.then(v =>2* v);
Cancellation
link
Observable subscriptions are cancellable.
Unsubscribing removes the listener from receiving further values, and notifies the subscriber function to cancel work.
sub = obs.subscribe((value)=>{
console.log(value)});
promise.then((value)=>{
console.log(value);});
Unsubscribe
sub.unsubscribe();
Implied by promise resolution.
Observables compared to events API
link
Observables are very similar to event handlers that use the events API.
Both techniques define notification handlers, and use them to process multiple values delivered over time.
Subscribing to an observable is equivalent to adding an event listener.
One significant difference is that you can configure an observable to transform an event before passing the event to the handler.
Using observables to handle events and asynchronous operations can have the advantage of greater consistency in contexts such as HTTP requests.
Here are some code samples that illustrate how the same kind of operation is defined using observables and the events API.
element.addEventListener(eventName,(event)=>{// Cannot change the passed Event into another // value before it gets to the handler });
Observables compared to arrays
link
An observable produces values over time.
An array is created as a static set of values.
In a sense, observables are asynchronous where arrays are synchronous.
In the following examples,
→
implies asynchronous value delivery.
Animate multiple elements using query() and stagger() functions
Parallel animation using group() function
Sequential vs. parallel animations
Filter animation example
Animating the items of a reordering list
Animations and Component View Encapsulation
Animation sequence summary
More on Angular animations
Prerequisites
link
A basic understanding of the following concepts:
Introduction to Angular animations
Transition and triggers
So far, we've learned simple animations of single HTML elements.
Angular also lets you animate coordinated sequences, such as an entire grid or list of elements as they enter and leave a page.
You can choose to run multiple animations in parallel, or run discrete animations sequentially, one following another.
The functions that control complex animation sequences are:
Functions
Details
query()
Finds one or more inner HTML elements.
stagger()
Applies a cascading delay to animations for multiple elements.
group()
Runs multiple animation steps in parallel.
sequence()
Runs animation steps one after another.
The query() function
link
Most complex animations rely on the
query()
function to find child elements and apply animations to them, basic examples of such are:
Examples
Details
query()
followed by
animate()
Used to query simple HTML elements and directly apply animations to them.
query()
followed by
animateChild()
Used to query child elements, which themselves have animations metadata applied to them and trigger such animation (which would be otherwise be blocked by the current/parent element's animation).
The first argument of
query()
is a css selector string which can also contain the following Angular-specific tokens:
Tokens
Details
:enter
:leave
For entering/leaving elements.
:animating
For elements currently animating.
@*
@triggerName
For elements with any—or a specific—trigger.
:self
The animating element itself.
Entering and Leaving Elements
Not all child elements are actually considered as entering/leaving; this can, at times, be counterintuitive and confusing. Please see the query api docs for more information.
You can also see an illustration of this in the animations live example (introduced in the animations introduction section) under the Querying tab.
Animate multiple elements using query() and stagger() functions
link
After having queried child elements via
query()
, the
stagger()
function lets you define a timing gap between each queried item that is animated and thus animates elements with a delay between them.
The following example demonstrates how to use the
query()
and
stagger()
functions to animate a list (of heroes) adding each in sequence, with a slight delay, from top to bottom.
Use
query()
to look for an element entering the page that meets certain criteria
For each of these elements, use
style()
to set the same initial style for the element.
Make it transparent and use
transform
to move it out of position so that it can slide into place.
Use
stagger()
to delay each animation by 30 milliseconds
Animate each element on screen for 0.5 seconds using a custom-defined easing curve, simultaneously fading it in and un-transforming it
You've seen how to add a delay between each successive animation.
But you might also want to configure animations that happen in parallel.
For example, you might want to animate two CSS properties of the same element but use a different
easing
function for each one.
For this, you can use the animation
group()
function.
NOTE
:
The
group()
function is used to group animation
steps
, rather than animated elements.
The following example uses
group()
s on both
:enter
and
:leave
for two different timing configurations, thus applying two independent animations to the same element in parallel.
Complex animations can have many things happening at once.
But what if you want to create an animation involving several animations happening one after the other? Earlier you used
group()
to run multiple animations all at the same time, in parallel.
A second function called
sequence()
lets you run those same animations one after the other.
Within
sequence()
, the animation steps consist of either
style()
or
animate()
function calls.
Use
style()
to apply the provided styling data immediately.
Use
animate()
to apply styling data over a given time interval.
Filter animation example
link
Take a look at another animation on the live example page.
Under the Filter/Stagger tab, enter some text into the
Search Heroes
text box, such as
Magnet
or
tornado
.
The filter works in real time as you type.
Elements leave the page as you type each new letter and the filter gets progressively stricter.
The heroes list gradually re-enters the page as you delete each letter in the filter box.
The HTML template contains a trigger called
filterAnimation
.
The code in this example performs the following tasks:
Skips animations when the user first opens or navigates to this page (the filter animation narrows what is already there, so it only works on elements that already exist in the DOM)
Filters heroes based on the search input's value
For each change:
Hides an element leaving the DOM by setting its opacity and width to 0
Animates an element entering the DOM over 300 milliseconds.
During the animation, the element assumes its default width and opacity.
If there are multiple elements entering or leaving the DOM, staggers each animation starting at the top of the page, with a 50-millisecond delay between each element
Animating the items of a reordering list
link
Although Angular animates correctly
*ngFor
list items out of the box, it will not be able to do so if their ordering changes.
This is because it will lose track of which element is which, resulting in broken animations.
The only way to help Angular keep track of such elements is by assigning a
TrackByFunction
to the
NgForOf
directive.
This makes sure that Angular always knows which element is which, thus allowing it to apply the correct animations to the correct elements all the time.
IMPORTANT
:
If you need to animate the items of an
*ngFor
list and there is a possibility that the order of such items will change during runtime, always use a
TrackByFunction
.
Animations and Component View Encapsulation
link
Angular animations are based on the components DOM structure and do not directly take View Encapsulation into account, this means that components using
ViewEncapsulation.Emulated
behave exactly as if they were using
ViewEncapsulation.None
(
ViewEncapsulation.ShadowDom
behaves differently as we'll discuss shortly).
For example if the
query()
function (which you'll see more of in the rest of the Animations guide) were to be applied at the top of a tree of components using the emulated view encapsulation, such query would be able to identify (and thus animate) DOM elements on any depth of the tree.
On the other hand the
ViewEncapsulation.ShadowDom
changes the component's DOM structure by "hiding" DOM elements inside
ShadowRoot
elements. Such DOM manipulations do prevent some of the animations implementation to work properly since it relies on simple DOM structures and doesn't take
ShadowRoot
elements into account. Therefore it is advised to avoid applying animations to views incorporating components using the ShadowDom view encapsulation.
Animation sequence summary
link
Angular functions for animating multiple elements start with
query()
to find inner elements; for example, gathering all images within a
<div>
.
The remaining functions,
stagger()
,
group()
, and
sequence()
, apply cascades or let you control how multiple animation steps are applied.