component

Component component

  • HTML template
  • Typescript, defines behavior
  • CSS group, which allows you to import multiple CSS files, so you can define multiple CSS files within a component
// Import Component(Component decorator or Component annotation) from the Angular main module
import { Component } from '@angular/core';

// Metadata is declared as JSON in the decorator
@Component({
  // It specifies an element called 
      
       . This element is a placeholder in the index. HTML file
      
  // Why is this component associated with the entry index? Because the main module is bound to the appModule in the entry main.ts
  selector: 'app-root'.// Find the corresponding label in the template, create and insert the component instance
  templateUrl: './app.component.html'./ / HTML templates
  styleUrls: ['./app.component.css'].// CSS styles can be introduced into multiple CSS files
  // This property is either inline template or templateUrl. Template can be followed directly by the HTML string
  // Note that interpolation is used in template syntax (backquotes). You cannot insert values with ${}
  template: `<h1>{{title}}</h1>`   
})
// Component controller, where logic code is written
export class AppComponent {
  title = 'myAngular';
  // Constructors can be used to declare and initialize attributes
  // There is one particularly important point to remember in Angular: dependencies can only be injected through constructors
  constructor(){}}Copy the code

A prerequisite for

  • NPM install -g@angular/CLI
  • Create an Angular project (ng New)

If the two conditions are not met, see Setting up an Environment.

How do I create a component

Create a component using the Angular CLI

Ng generate component <project-name> // generate component <project-name> //Copy the code

Some other options commonly used to create components

Ng g c <project-name> -- skpp-tests // create a component and do not install the test file ng g c <project-name> --inline-style // abbreviate -s, Ng g c <project-name> --inline-template // Inline template ng g c <project-name> --module= <module-name> // Specifies in which module the created component will be referenced and used in projects of multiple modulesCopy the code

In addition to automatically generating components through the Angular CLI, you can also create components manually (not recommended), which is not covered here.

Component lifecycle (advanced content, ngOnInit, ngOnDestroy, ngOnChanges, ngAfterViewInit())

There are only two life cycles that are commonly used at work (ngOninit and ngOnDestroy), and the others are rarely used; But if you can understand the component lifecycle, you can understand Angular even more deeply.

Life cycle meaning

The component instance lifecycle begins when Angular instantiates a component class and renders a component view and its child views. The lifecycle is always accompanied by change detection, where Angular checks when data binding properties change and updates views and component instances as needed. The lifecycle ends when Angular destroys the component instance and removes the template it renders from the DOM. Directives have a similar life cycle when Angular creates, updates, and destroys instances during execution.

Application:

Your application can use lifecycle hook methods to trigger critical events in the component or instruction lifecycle to initialize new instances, initiate change detection when needed, respond to updates during change detection, and clean up instances before deleting them.

How are lifecycle events implemented

Each component or directive can implement one or more lifecycle hooks that operate on the component or directive instance at the appropriate time.

Each interface has a unique hook method whose name is made up of the interface name prefixed with ng. For example, the hook method of the OnInit interface is called ngOnInit(). If you implement this method in a component or directive class, Angular calls it after first checking the input properties of the component or directive

import { Component } from '@angular/core';

@Component({
  selector: 'app-root'.styleUrls: ['./app.component.css'].template: `<h1>{{title}}</h1>`   
})
// Implement the OnInit life cycle. Multiple life cycles can be implemented
export class AppComponent implements OnInit{ 
  title = 'myAngular';
  constructor() {}
  ngOnInit(){
      console.log(Angular calls a component's input property once after first checking it.)}}Copy the code

Lifecycle overview

hook The timing use Pay attention to
ngOnChanges() Called when the value of the bound input property changes, the first call must occur before ngOnInit() Responds when Angular sets or resets the input properties for the data binding. This method accepts SimpleChanges objects for the current and previous property values This happens quite frequently, so anything you do here can significantly affect performance.
ngOnInit() Called after the first round of ngOnChanges(), only once. Initialize the directive/component after Angular first displays the data binding and set directive/component input properties. A good place for the component to get initial data It’s very important that you only call it once
ngDoCheck() Called immediately after ngOnChanges() on each change detection and ngOnInit() on the first change detection. Detect, and react to changes that Angular can’t or won’t detect itself. It happens as frequently as ngOnChanges
ngAfterContentInit() Called after the first ngDoCheck(), only once. Called when Angular projects external content into the view where the component view or directive resides. Only called once
ngAfterContentChecked() NgAfterContentInit () and after each ngDoCheck() Called whenever Angular finishes checking what is projected into a component or directive.
ngAfterViewInit() Called after ngAfterContentChecked() the first time, only once. Called after initializing the component view and its child views. Only called once
ngAfterViewChecked() NgAfterViewInit () and ngAfterContentChecked() each time. Called every time change detection is done for component views and child views.
ngOnDestroy() Called before Angular destroys the directive/component. Called and cleaned every time Angular destroys a directive/component. Here, unsubscribe observables and detach event handlers to prevent memory leaks.

Unsubscribe observables,

Clear timer,

Unregister all callbacks registered by the directive globally or in the application service.
It is very important

Detailed explanation of key life cycle

Initialize the component and the directive ngOnInit

  • Perform complex initialization outside the constructor. Components should be constructed cheaply and securely. For example, you should not get data in component constructors. When creating a component in a test or before deciding to display it, you should not worry that the new component will try to contact the remote server. NgOnInit () is a good place for a component to get its initial data.
  • Set the component after Angular sets the input properties. Constructors should set only the initial local variables to simple values. Remember that the data binding input properties of the directive are set only after construction is complete. If you want to initialize instructions based on these properties, set them when you run ngOnInit()

Clean up ngOnDestroy at instance destruction

This is where resources are released that are not automatically garbage collected. If you don’t, you run the risk of a memory leak.

  • Unsubscribe observables and DOM events.
  • Stop the interval timer.
  • Unregister all callbacks registered by the directive globally or in the application service.
  • The ngOnDestroy() method can also be used to notify other parts of the application that the component is about to disappear

Page buried point

You can count the length of a page in a component using the ngOnInit and ngOnDestroy methods,

A better approach is to implement the ngOnInit and ngOnDestroy life cycles with directives that count page lengths

Route guards can also be used to record the time required for pages to come in and out of the stack

@Directive({selector: '[appSpy]'}) export class SpyDirective implements OnInit, OnDestroy { constructor(private logger: LoggerService) { } ngOnInit() { this.logIt(`onInit`); } ngOnDestroy() { this.logIt(`onDestroy`); } private logIt(msg: string) { this.logger.log(`Spy #${nextId++} ${msg}`); }}Copy the code

Use the change detection hook ngOnchanges

Angular calls its ngOnChanges() method when it detects that the input property of the component or directive has changed.

Because this method is executed frequently, be aware of the amount of computation required to judge, which can affect performance.

NgOnChanges (changes: SimpleChanges) {for (const propName in changes) {const CHNG = changes[propName]; const cur = JSON.stringify(chng.currentValue); const prev = JSON.stringify(chng.previousValue); this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); }}Copy the code

Communication between components (important, must master)

I. Communication between parent and child Components (Basic)

The father the son

1. Parent components pass values to child components via property binding

import { Component } from '@angular/core';
@Component({
  selector: 'app-parent'.template: ` 
       `
})
export class ParentComponent {
  msg = 'Value passed by parent component';
}
Copy the code

2. Child components receive data via @input

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child'.template: ` 

{{msg}}

`
}) export class ChildComponent { @Input() msg: String; } Copy the code
Child the parent

1. Child components send data to parent components through custom events

import { Component, Input, EventEmitter, Output} from '@angular/core'; , @Component({ selector: 'app-child', template: <p>{{MSG}}</p> <button (click)="vote()" > </button> '}) export class ChildComponent {@input () MSG: String; @Output() voted = new EventEmitter<boolean>(); Vote () {this.voted. Emit (" the value passed by the sub-component "); }}Copy the code

2. The parent component listens for custom events and receives values from the child component

import { Component } from '@angular/core'; @Component({ selector: 'app-parent', template: '<app-child [MSG]=" MSG "(commented)=" cute ($event)"></app-child>'}) export class ParentComponent {MSG = '"; "> < div style =" box-sizing: border-box; color: RGB (51, 51, 51); text-align: center;"Copy the code
How does a child component listen for changes in input property values? (2 methods)

1. You can use a setter for the @input () Input property to intercept value changes in the parent component.

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  template: '<h3>"{{name}}"</h3>'
})
export class ChildComponent {
  @Input()
  get name(): string { return this._name; }
  set name(name: string) {
    this._name = (name && name.trim()) || '<no name set>';
  }
  private _name = '';
}
Copy the code

Use ngOnChange() to intercept changes in input property values

When you need to monitor multiple, interactive input properties, this approach is more appropriate than using setters for 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: number; @Input() minor: number; 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(', ')); }}Copy the code
How does a parent component read properties and call methods of child components? (2 methods)

1. Represent child components through local variables

A parent component cannot use data binding to read a child component’s properties or call its methods. However, you can create a local variable in the parent component template to represent the child, and then use this variable to read the child’s properties and call its methods, as shown in the following example.

Consider: Can a parent component read the private properties and methods of its children in this way?

The parent 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 (click)="timer.start()"> start </button (click)="timer.stop()"> stop </button> <div class="seconds">{{timer.seconds}}</div> // Read subcomponent property <app-countdown-timer #timer></app-countdown-timer> `, styleUrls: ['../assets/demo.css'] }) export class CountdownLocalVarParentComponent { }Copy the code
Child components
import { Component, OnDestroy } from '@angular/core'; @Component({ selector: 'app-countdown-timer', template: '<p>{{message}}</p>' }) export class CountdownTimerComponent implements OnDestroy { intervalId = 0; message = ''; seconds = 11; ngOnDestroy() { this.clearTimer(); } start() { this.countDown(); } stop() { this.clearTimer(); this.message = `Holding at T-${this.seconds} seconds`; } private clearTimer() { clearInterval(this.intervalId); } private countDown() { this.clearTimer(); this.intervalId = window.setInterval(() => { this.seconds -= 1; if (this.seconds === 0) { this.message = 'Blast off! '; } else { if (this.seconds < 0) { this.seconds = 10; } // reset this.message = `T-${this.seconds} seconds and counting`; }}, 1000); }}Copy the code

2. The parent component calls @viewChild() (basic, recommended)

This local variable method is a simple and convenient method. But it has limitations (it can only be used in template HTML), because parent-child wiring must all be done in the parent’s template. The parent component’s own TS code has no access to the child component.

When the parent component class needs to access the child component, it can inject the child component into the parent component as a ViewChild.

Properties and methods that access child components in the parent component class:

import { AfterViewInit, ViewChild } from '@angular/core'; import { Component } from '@angular/core'; import { CountdownTimerComponent } from './countdown-timer.component'; // Introduce child @component ({selector: 'app-countdown-parent-vc', template: ` <h3>Countdown to Liftoff (via ViewChild)</h3> <button (click)="start()">Start</button> <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 Property decorator that injects the child CountdownTimerComponent into the private property timerComponent. @ViewChild(CountdownTimerComponent) private timerComponent: CountdownTimerComponent; seconds() { return 0; } // Angular calls a child view of a component when it is created. To load in child component view after ngAfterViewInit () {/ / access subcomponents attribute setTimeout (() = > this. Seconds () = = > this. TimerComponent. Seconds, 0). } start() { this.timerComponent.start(); } // Stop () {this.timerComponent.stop(); }}Copy the code

Note: there are many scenarios to master.

The ngAfterViewInit() lifecycle hook is an important step. The injected timer component cannot be accessed until Angular displays the parent component view, so it displays the number of seconds as 0.

Angular then calls the ngAfterViewInit lifecycle hook, but it’s too late to update the parent view’s countdown. Angular’s one-way data flow rules prevent updating the parent component view in the same cycle. The app is forced to wait another round before displaying the number of seconds.

Use setTimeout() to wait for the next round, and then overwrite the seconds() method so that it then gets the value of the number of seconds from the injected timer component.

2. Components communicate through services (Publish subscriber model, basic, must master)

The parent component and its children share the same service and use this service to achieve two-way communication within the component family.

Not just parent-child components, but as long as components share the same service with components, data communication can be achieved.

<! --parent.component.html--> <p style="width: 1000px; margin: auto"> <p class="card" style="width: 500px; float: Left "> <p class="card-header"> parent </p> <p class="card-body"> <h5 class="card-title"> parent </h5> <p class="form-group"> <label for=" serviceOutput "> </label> <input type="text" class="form-control" id="serviceoutput" placeholder=" serviceInput" [(ngModel)]="serviceInput" > </p> <button class=" BTN btn-primary" (click)="clickService()"> </button> </p> </p> <app-child></app-child> </p>Copy the code
<! --child.component.html--> <p class="card" style="width: 500px;" > <p class="card-header"> subcomponent </p> <p class="card-body"> < H5 class="card-title"> subcomponent </ H5 > <p class="form-group"> <label For =" serviceOutput "> Subcomponent service input: </label> <input type="text" class="form-control" id="serviceoutput" placeholder=" serviceInput" [(ngModel)]="serviceInput" > </p> <button class=" BTN bTN-primary "(click)="clickService()"> </button> </p> </p>Copy the code
Ts import {Injectable} from '@angular/core'; import {Subject} from 'rxjs/Subject'; import {Observable} from 'rxjs/Observable'; @Injectable() export class MeditorService { private subject = new Subject<MeditorMsg>(); Constructor () {} / / public access subscriber getObservable () : observables < MeditorMsg > {return this. Subject. AsObservable (); } public push(MSG: MeditorMsg) {this.subject.next(MSG); }} export interface MeditorMsg {id: string; body: any; }Copy the code
subscription: Subscription = null; // Initialize a subscription object // subcomponent constructor that listens for data push constructor(private meditor: MeditorService) { this.subscription = meditor.getObservable().subscribe( msg => { console.log(msg); If (msg.id === 'parent') {this.serviceInput = msg.body; }}); } // Child components push data to the middle, to the subscriber clickService() {this.meditor.push({id: 'parent', body: this.serviceInput}); } // Parent component constructor to listen for data push constructor(private meditor: MeditorService) { this.subscription = meditor.getObservable().subscribe( msg => { console.log(msg); If (msg.id === 'child') {this.serviceInput = msg.body; }}); } // The parent component pushes the data to the middle, to the subscriber clickService() {this.meditor.push({id: 'parent', body: this.serviceInput}); } // Note: To subscribe to an object is to unsubscribe before the end of its life cycle. ngOnDestroy() { this.subscription.unsubscribe(); }Copy the code

Consider: Is this publishing subscriber model suitable for global state management?

3, local cache can be used to achieve communication (Cookie,LocalStorage, SessionStorage)

<! -- catch_namae_type. Ts --> // Use the prefix session_/ local_ / cookie_ // to dynamically set the cache. Export const CatchNameType = {session_userInfo: 'session_userInfo', // session_toekn: 'session_token', // token local_loginInfo: 'local_loginInfo', // local cache username password}; <! --catch.ts--> import { Injectable } from '@angular/core'; Import {CatchNameType} from './catch_namae_type'; / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the cache tools (method) / / cookies (methods are: Set /get/remove) // SStorage(sessionStorage) (set/get/remove/clear) // LStorage(localStorage) set/get/remove/clear) @Injectable({ providedIn: 'root', }) export class Catch {// cookie public static cookie = {/** * cookie store * @param key attribute * @param value * @param value String expire expiration time, in days */ SET (key: String, value: any, expire: any): void { if (Catch.is_set_catch_name_type(key)) { const d = new Date(); d.setDate(d.getDate() + expire); document.cookie = `${key}=${value}; expires=${d.toDateString()}`; } }, get(key: string): string { const cookieStr = unescape(document.cookie); const arr = cookieStr.split('; '); let cookieValue = ''; // tslint:disable-next-line: prefer-for-of for (let i = 0; i < arr.length; i++) { const temp = arr[i].split('='); if (temp[0] === key) { cookieValue = temp[1]; break; } } return cookieValue; }, remove(key: string): void { document.cookie = `${encodeURIComponent(key)}=; expires=${new Date()}`; }}; // sessionStorage public static SStorage = { set(key: string, value: any): void { if (Catch.is_set_catch_name_type(key)) { sessionStorage.setItem(key, JSON.stringify(value)); } }, get(key: string): any { const jsonString = sessionStorage.getItem(key) === 'undefined' ? undefined : sessionStorage.getItem(key); return jsonString ? JSON.parse(jsonString) : null; }, remove(key: string): void { sessionStorage.removeItem(key); }, clear(): void { sessionStorage.clear(); }}; // localStorage public static LStorage = { set(key: string, value: any): void { if (Catch.is_set_catch_name_type(key)) { localStorage.setItem(key, JSON.stringify(value)); } }, get(key: string): any { const jsonString = localStorage.getItem(key) === 'undefined' ? undefined : localStorage.getItem(key); return jsonString ? JSON.parse(jsonString) : null; }, remove(key: string): void { localStorage.removeItem(key); }, clear(): void { localStorage.clear(); }}; Static is_set_catch_name_type(key: string): Boolean {let allow = false; If (key.indexof ('_noSetType_')! == -1) { allow = true; Console. log(' dynamically set cache ', key); return allow; } const nameRule = key.indexof ('session_')! == -1 || key.indexOf('local_') ! == -1 || key.indexOf('cookie_') ! = = 1; if (! nameRule) { allow = false; Console. log(' naming error ', key); return allow; Values (CatchNameType).foreach ((item) => {if (item === key) {allow = true; }}); if (! Allow) {console.log(' cache operation failed, please check configured cache type '); } return allow; }}Copy the code

Page routing parameters can also realize one-way communication

I will sort this out in the routing section.

Component Communication Summary

Therefore, component communication has the following types:

1, @input () @output 2, @viewChild()

2, through the service

3. Page caching

4. Page-level component parameter transfer (two pages equals two components)

Dynamic components

Component templates are not fixed forever. The application may need to load new components as needed during runtime. The following example illustrates the basic use of dynamic components

1. Create the component, the imported component

@Component({
  template: `<span>hello world</span>`
})
export class DynamicComponent { }
Copy the code

2. Create a container component for loading dynamic components

@Component({ selector: 'app-container', template: ` <div #dynamicContainer></div> <button (click)="createComponent()">Create</button> ` }) export class AppContainerComponent {// Declare @viewChild ("dynamicContainer", {read: ViewContainerRef}) Container: ViewContainerRef; }Copy the code

In the AppContainerComponent, the @ViewChild decorator gets the template element in the view. If no second query argument is specified, the component instance or corresponding DOM element is returned by default. In this example, We need to get the ViewContainerRef instance which is the view container. You can create, insert, delete components, and so on in the view container.

3. Create components dynamically

Before creating a component, you need to inject the ComponentFactoryResolver service object, which is the core of dynamically loading components to render one component instance to another component view.

  • Use ComponentFactoryResolve to resolve declared but uninstantiated components into components that can be loaded dynamically
/ / dependent component types to obtain corresponding factory, can be seen from the name on the component factory is used to initialize the const componentFactory = this.Com ponentFactoryResolver (DynamicComponent);  // Call the createComponent method of the view container and add the component to the container. Internally, the factory create method is called to initialize the component. const modalContainerRef = this.container.createComponent(componentFactory);Copy the code

4. Assign values to component properties

Assign component properties as follows

modalContainerRef.instance.property = ***;
Copy the code

5. Destroy components

After using a component, remember to destroy it.

modalContainerRef.destroy();
Copy the code

6. Component registration

Components need to be declared in the entryComponents of a module in order to be able to create them dynamically. Because every component Angular declares in entryComponents creates a ComponentFactory and stores it in a ComponentFactoryResolver, this is a required step for dynamic loading.

A better blog about dynamic component loading data interaction: blog.csdn.net/qq_30101131…