Table of Contents

Understanding Angular

https://angular.io/guide/understanding-angular-overview#understanding-angular

Components

Overview

Components are the main building blocks for Angular applications. Each component consists of:

Creating a component

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

Declaring a component's styles

Component Lifecycle

https://angular.io/guide/lifecycle-hooks#component-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

Initializing a component or directive

Use the ngOnInit() method to perform the following initialization tasks:

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.

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):

These functions can be used for:

General examples

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:

…

Component interaction

https://angular.io/guide/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.

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

…

…

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

…