User Tools

Site Tools


angular_docs_understanding_angular

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
angular_docs_understanding_angular [2023/10/06 11:35] – [Parent listens for child event] kalaschnikowangular_docs_understanding_angular [2023/10/06 12:11] (current) – kalaschnikow
Line 1: Line 1:
 +====== 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:
 +  * 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.
 +
 +<code>
 +@Component({
 +  selector: 'app-component-overview',
 +})
 +</code>
 +
 +=== 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:
 +<code>
 +@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}`);
 +  }
 +}
 +</code>
 +
 +== 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 after ''ngOnInit()'' 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 subsequent ''ngDoCheck()''.
 +  * ''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 subsequent ''ngAfterContentChecked()''.
 +  * ''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 
 +    * see [[https://angular.io/guide/lifecycle-hooks#one-time-initialization|One-time initialization]]
 +  * synchronization every time Angular finishes mutating data 
 +    * see [[https://angular.io/guide/lifecycle-hooks#handling-synchronization|Handling synchronization]]
 +
 +=== 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
 +    * ''Spy'' example
 +    * 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 ====
 +
 +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:
 +<code>
 +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 = '';
 +}
 +</code>
 +
 +Using HerChildComponent in HeroParentComponent:
 +<code>
 +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';
 +}
 +</code>
 +
 +=== 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.
 +<code>
 +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 = '';
 +}
 +</code>
 +
 +Using NameChildComponent:
 +<code>
 +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  '];
 +}
 +</code>
 +
 +=== 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.
 +
 +<code>
 +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(', '));
 +  }
 +}
 +</code>
 +
 +=== 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:
 +<code>
 +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;
 +  }
 +}</code>
 +
 +The parent VoteTakerComponent binds an event handler called onVoted() that responds to the child event payload $event and updates a counter.
 +<code>
 +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++;
 +    }
 +  }
 +}
 +</code>
 +
 +=== 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. 
 +<code>
 +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 { }
 +</code>
 +
 +=== 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.
 +
 +<code>
 +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(); }
 +}
 +</code>
 +
 +=== 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.
 +
 +<code>
 +...
 +</code>
 +...
 +
 +
 +==== 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. 
 +
 +<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 {
 +/* . . . */
 +}
 +</code>
 +
 +=== 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 =====
 +...