Table of Contents
Understanding Angular
Components
Overview
Components are the main building blocks for Angular applications. Each component consists of:
- An HTML template that declares what renders on the page
- A TypeScript class that defines behavior
- A CSS selector that defines how the component is used in a template
- Optionally, CSS styles applied to the template
Creating a component
- Creating a component using Angular CLI
ng generate component <component-name>- By default this command generates:
- directory named after the component
- component file,
<component-name>.component.ts - template file,
<component-name>.component.html - CSS file,
<component-name>.component.css - testing specification file,
<component-name>.component.spec.ts
- Creating a component manually
- …
Specifying a component's CSS selector
Every component requires(!) a CSS selector. A selector instructs Angular to instantiate this component wherever it finds the corresponding tag in template HTML.
@Component({
selector: 'app-component-overview',
})
Defining a component's template
- inline
template: '<h1>Hello World!</h1>',- If you want your template to span multiple lines, use backticks (`)
- with file path URL
templateUrl: './component-overview.component.html',
Declaring a component's styles
- inline
styles: ['h1 { font-weight: normal; }']
- with file path URL
styleUrls: ['./component-overview.component.css']
Component Lifecycle
https://angular.io/guide/lifecycle-hooks#component-lifecycle
- Lifecycle:
- start
- instantiate component class
- render component view
- change detection
- detection of data-bound properties ⇒ update component and view
- end
- destroy component instance
- remove rendered template from the DOM
- Lifecycle hook methods
- can be used to tap into key events in the lifecycle
Responding to lifecycle events
Responding to lifecycle event is possible by implementing interface of a lifecycle hook. Every such interface contains only one method. The interface is named usually like OnXyz and the corresponding method ngOnXyz(). For example:
@Directive({selector: '[appPeekABoo]'})
export class PeekABooDirective implements OnInit {
constructor(private logger: LoggerService) { }
// implement OnInit's `ngOnInit` method
ngOnInit() {
this.logIt('OnInit');
}
logIt(msg: string) {
this.logger.log(`#${nextId++} ${msg}`);
}
}
Lifecycle event sequence
ngOnChanges()- Respond when Angular sets or resets data-bound input properties.
- called before (!)
ngOnInit() - This happens frequently, so any operation you perform here impacts performance significantly.
- If your component has no inputs or you use it without providing any inputs, the framework will not call
ngOnChanges().
ngOnInit()- Initialize the directive or component after Angular first displays the data-bound properties and sets the directive or component's input properties.
- Called once, after the first ngOnChanges(). ngOnInit() is still called even when ngOnChanges() is not (which is the case when there are no template-bound inputs).
ngDoCheck()- Detect and act upon changes that Angular can't or won't detect on its own.
- Called immediately after
ngOnChanges()on every change detection run, and immediately afterngOnInit()on the first run.
ngAfterContentInit()- Respond after Angular projects external content into the component's view, or into the view that a directive is in.
- Called once after the first
ngDoCheck().
ngAfterContentChecked()- Respond after Angular checks the content projected into the directive or component.
- Called after
ngAfterContentInit()and every subsequentngDoCheck().
ngAfterViewInit()- Respond after Angular initializes the component's views and child views, or the view that contains the directive.
- Called once after the first
ngAfterContentChecked().
ngAfterViewChecked()- Called once after the first
ngAfterContentChecked(). - Called after the
ngAfterViewInit()and every subsequentngAfterContentChecked().
ngOnDestroy()- Cleanup just before Angular destroys the directive or component. Unsubscribe Observables and detach event handlers to avoid memory leaks.
- Called immediately before Angular destroys the directive or component.
Initializing a component or directive
Use the ngOnInit() method to perform the following initialization tasks:
- Perform complex initializations outside of the constructor
- Components should be cheap and safe to construct. You should not, for example, fetch data in a component constructor. You shouldn't worry that a new component will try to contact a remote server when created under test or before you decide to display it. An
ngOnInit()is a good place for a component to fetch its initial data.
- Set up the component after Angular sets the input properties
- Components should be cheap and safe to construct. You should not, for example, fetch data in a component constructor. You shouldn't worry that a new component will try to contact a remote server when created under test or before you decide to display it.
An ngOnInit() is a good place for a component to fetch its initial data.
Cleaning up on instance destruction
Angular provides several ways to clean up when an instance is destroyed.
ngOnDestroy()- This is the place to free resources that won't be garbage-collected automatically.
- Unsubscribe from Observables and DOM events
- Stop interval timers
- Unregister all callbacks that the directive registered with global or application services
- notify another part of the application that the component is going away.
- DestroyRef
- In addition to
ngOnDestroy(), you can inject Angular's DestroyRef and register callback functions to be called when the enclosing context is destroyed. This can be useful for building reusable utilities that require cleanup.
- takeUntilDestroyed
- (actually available only for developer preview)
- When using RxJS Observables in components or directives, you may want to complete any observables when the component or directive is destroyed. Angular's @angular/core/rxjs-interop package provides an operator, takeUntilDestroyed, to simplify this common task
Reading and writing the DOM
Sometimes it is necessary to use browser-only APIs to read or write the DOM. It is not possible with lifecycle events because they run during server-side rendering and pre-rendering. For this purpose Angular provides two functions (both are currently available only for developer preview):
- afterRender
- Synchronize state with the DOM.
- Runs after every change detection cycle that follows.
- afterNextRender
- When using RxJS Observables in components or directives, you may want to complete any observables when the component or directive is destroyed. Angular's @angular/core/rxjs-interop package provides an operator, takeUntilDestroyed, to simplify this common task
- Runs once after the next change detection cycle.
These functions can be used for:
- One-time initialization
- synchronization every time Angular finishes mutating data
General examples
- Sequence and frequency of all lifecycle events
- PeekABooComponent example
- The hook calling order:
- 1 OnChanges
- 2 OnInit
- 3 DoCheck
- 4 AfterContentInit
- 5 AfterContentChecked
- 6 AfterViewInit
- 7 AfterViewChecked
- 8 DoCheck
- 9 AfterContentChecked
- 10 AfterViewChecked
- 11 OnDestroy
- Use directives to watch the DOM
Spyexample- The SpyDirective implements two hooks, ngOnInit() and ngOnDestroy(), to discover when a watched element is in the current view.
- This template applies the SpyDirective to a <div> in the ngFor hero repeater managed by the parent SpyComponent.
- Use component and directive hooks together
- …
Using change detection hooks
Angular calls the ngOnChanges() method of a component or directive whenever it detects changes to the input properties.
…
Defining custom change detection
…
While the ngDoCheck() hook can detect when the hero's name has changed, it is an expensive hook. This hook is called with enormous frequency —after every change detection cycle no matter where the change occurred.
If you use this hook, your implementation must be extremely lightweight or the user experience suffers.
View encapsulation
In Angular, a component's styles can be encapsulated within the component's host element so that they don't affect the rest of the application.
Following modes are available:
- ViewEncapsulation.ShadowDom
- based on Shadow DOM API
- ViewEncapsulation.Emulated
- emulating Shadow DOM
- ViewEncapsulation.None
- no encapsulation
…
Component interaction
Pass data from parent to child with input binding
Example:
HeroChildComponent has two input properties, typically adorned with @Input() decorator:
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'app-hero-child',
template: `
<h3>{{hero.name}} says:</h3>
<p>I, {{hero.name}}, am at your service, {{masterName}}.</p>
`
})
export class HeroChildComponent {
@Input() hero!: Hero;
@Input('master') masterName = '';
}
Using HerChildComponent in HeroParentComponent:
import { Component } from '@angular/core';
import { HEROES } from './hero';
@Component({
selector: 'app-hero-parent',
template: `
<h2>{{master}} controls {{heroes.length}} heroes</h2>
<app-hero-child
*ngFor="let hero of heroes"
[hero]="hero"
[master]="master">
</app-hero-child>
`
})
export class HeroParentComponent {
heroes = HEROES;
master = 'Master';
}
Intercept input property changes with a setter
Use an input property setter to intercept and act upon a value from the parent.
The setter of the name input property in the child NameChildComponent trims the whitespace from a name and replaces an empty value with default text.
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-name-child',
template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
@Input()
get name(): string { return this._name; }
set name(name: string) {
this._name = (name && name.trim()) || '<no name set>';
}
private _name = '';
}
Using NameChildComponent:
import { Component } from '@angular/core';
@Component({
selector: 'app-name-parent',
template: `
<h2>Master controls {{names.length}} names</h2>
<app-name-child *ngFor="let name of names" [name]="name"></app-name-child>
`
})
export class NameParentComponent {
// Displays 'Dr. IQ', '<no name set>', 'Bombasto'
names = ['Dr. IQ', ' ', ' Bombasto '];
}
Intercept input property changes with ngOnChanges()
Detect and act upon changes to input property values with the ngOnChanges() method of the OnChanges lifecycle hook interface.
You might prefer this approach to the property setter when watching multiple, interacting input properties.
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-version-child',
template: `
<h3>Version {{major}}.{{minor}}</h3>
<h4>Change log:</h4>
<ul>
<li *ngFor="let change of changeLog">{{change}}</li>
</ul>
`
})
export class VersionChildComponent implements OnChanges {
@Input() major = 0;
@Input() minor = 0;
changeLog: string[] = [];
ngOnChanges(changes: SimpleChanges) {
const log: string[] = [];
for (const propName in changes) {
const changedProp = changes[propName];
const to = JSON.stringify(changedProp.currentValue);
if (changedProp.isFirstChange()) {
log.push(`Initial value of ${propName} set to ${to}`);
} else {
const from = JSON.stringify(changedProp.previousValue);
log.push(`${propName} changed from ${from} to ${to}`);
}
}
this.changeLog.push(log.join(', '));
}
}
Test it for Intercept input property changes with ngOnChanges()
Test that both input properties are set initially and that button clicks trigger the expected ngOnChanges calls and values. …
Parent listens for child event
The child component exposes an EventEmitter property with which it emits events when something happens. The parent binds to that event property and reacts to those events.
The child's EventEmitter property is an output property, typically adorned with an @Output() decorator as seen in this VoterComponent:
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-voter',
template: `
<h4>{{name}}</h4>
<button type="button" (click)="vote(true)" [disabled]="didVote">Agree</button>
<button type="button" (click)="vote(false)" [disabled]="didVote">Disagree</button>
`
})
export class VoterComponent {
@Input() name = '';
@Output() voted = new EventEmitter<boolean>();
didVote = false;
vote(agreed: boolean) {
this.voted.emit(agreed);
this.didVote = true;
}
}
The parent VoteTakerComponent binds an event handler called onVoted() that responds to the child event payload $event and updates a counter.
import { Component } from '@angular/core';
@Component({
selector: 'app-vote-taker',
template: `
<h2>Should mankind colonize the Universe?</h2>
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
<app-voter
*ngFor="let voter of voters"
[name]="voter"
(voted)="onVoted($event)">
</app-voter>
`
})
export class VoteTakerComponent {
agreed = 0;
disagreed = 0;
voters = ['Dr. IQ', 'Celeritas', 'Bombasto'];
onVoted(agreed: boolean) {
if (agreed) {
this.agreed++;
} else {
this.disagreed++;
}
}
}
Parent interacts with child using local variable
A parent component cannot use data binding to read child properties or invoke child methods. Do both by creating a template reference variable for the child element and then reference that variable within the parent template as seen in the following example.
The local variable #timer is a reference to the child component.
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';
@Component({
selector: 'app-countdown-parent-lv',
template: `
<h3>Countdown to Liftoff (via local variable)</h3>
<button type="button" (click)="timer.start()">Start</button>
<button type="button" (click)="timer.stop()">Stop</button>
<div class="seconds">{{timer.seconds}}</div>
<app-countdown-timer #timer></app-countdown-timer>
`,
styleUrls: ['../assets/demo.css']
})
export class CountdownLocalVarParentComponent { }
Parent calls an @ViewChild()
The local variable approach is straightforward. But it is limited because the parent-child wiring must be done entirely within the parent template. The parent component itself (the parent class!) has no access to the child.
When the parent component class requires that kind of access, inject the child component into the parent as a ViewChild.
import { AfterViewInit, ViewChild } from '@angular/core';
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';
@Component({
selector: 'app-countdown-parent-vc',
template: `
<h3>Countdown to Liftoff (via ViewChild)</h3>
<button type="button" (click)="start()">Start</button>
<button type="button" (click)="stop()">Stop</button>
<div class="seconds">{{ seconds() }}</div>
<app-countdown-timer></app-countdown-timer>
`,
styleUrls: ['../assets/demo.css']
})
export class CountdownViewChildParentComponent implements AfterViewInit {
@ViewChild(CountdownTimerComponent)
private timerComponent!: CountdownTimerComponent;
seconds() { return 0; }
ngAfterViewInit() {
// Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ...
// but wait a tick first to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
}
start() { this.timerComponent.start(); }
stop() { this.timerComponent.stop(); }
}
Parent and children communicate using a service
A parent component and its children share a service whose interface enables bidirectional communication within the family.
The scope of the service instance is the parent component and its children. Components outside this component subtree have no access to the service or their communications.
...
…
Component styles
Using component styles
For every Angular component you write, you can define not only an HTML template, but also the CSS styles that go with that template, specifying any selectors, rules, and media queries that you need.
One way to do this is to set the styles property in the component metadata. The styles property takes an array of strings that contain CSS code.
@Component({
selector: 'app-root',
template: `
<h1>Tour of Heroes</h1>
<app-hero-main [hero]="hero"></app-hero-main>
`,
styles: ['h1 { font-weight: normal; }']
})
export class HeroAppComponent {
/* . . . */
}
Component styling best practices
You should consider the styles of a component to be private implementation details for that component.
Authoring a component to support customization
As component author, you can explicitly design a component to accept customization in one of four different ways.
- 1. Use CSS Custom Properties (recommended)
- You can define a supported customization API for your component by defining its styles with CSS Custom Properties, alternatively known as CSS Variables. Anyone using your component can consume this API by defining values for these properties, customizing the final appearance of the component on the rendered page.
- While this requires defining a custom property for each customization point, it creates a clear API contract that works in all style encapsulation modes.
- 2. Declare global CSS with @mixin
- While Angular's emulated style encapsulation prevents styles from escaping a component, it does not prevent global CSS from affecting the entire page. While component consumers should avoid directly overwriting the CSS internals of a component, you can offer a supported customization API via a CSS preprocessor like Sass. For example, a component may offer one or more supported mixins to customize various aspects of the component's appearance. While this approach uses global styles in its implementation, it allows the component author to keep the mixins up to date with changes to the component's private DOM structure and CSS classes.
- 3. Customize with CSS ::part
- If your component uses Shadow DOM, you can apply the part attribute to specify elements in your component's template. This allows consumers of the component to author arbitrary styles targeting those specific elements with the ::part pseudo-element.
- 4. Provide a TypeScript API (not recommended)
- You can define a TypeScript API for customizing styles, using template bindings to update CSS classes and styles.
- This is not recommended because the additional JavaScript cost of this style API incurs far more performance cost than CSS.
Special selectors
:host
…
:host-context
…
(deprecated) /deep/, >>>, and ::ng-deep
…
…
Loading component styles
…
Styles in component metadata
…
Style files in component metadata
…
Template inline styles
…
Template link tags
…
CSS @imports
…
External and global style files
…
Non-CSS style files
…
Sharing data between child and parent directives and components
…
Content projection
…
Dynamic component loader
…
Angular elements overview
…
Templates
…
Directives
…
Dependency injection
…
