Perhaps the most comprehensive introduction to Angular novice in the whole network

Angular overview

Angular is an open source web front-end framework developed by Google, based on TypeScript.

Compared with react and vue, Angular is more suitable for medium and large enterprise projects.

Angular program architecture

Angular advantage

  • Scalability: Based on RxJS and immutable JS and other push models can meet the needs of massive data
  • Cross platform: progressive application (high performance, offline use, installation free), native (Ionic), desktop
  • Productivity: templates (quickly create UI views through simple and powerful template syntax), CLI (quickly enter the build phase, add components and tests, and then deploy immediately)
  • Testing: unit testing (supporting Karma, Jasmine and other tools for unit testing), end-to-end testing (supporting Protractor and other tools for end-to-end testing)

@angular/cli scaffold

  1. ng new new project
  • ——Configuring routing
  • ——style=css|scss|less configure css Style
  1. ng serve startup project
  • ——port 4200 port number, default 4200
  • ——open automatically opens the browser
  1. ng build package project
  • ——aot precompiling
  • ——prod compression packaging
  • -base-href=/static/
  1. ng generate create module / component / service
  • Module - routing creation module
  • Component create component
  • Service / create service

File loading order

main.ts => app.module.ts => app.component.ts => index.html => app.component.html

Project directory structure

|-- project
	|-- .editorconfig // Used to unify the code style in different editors
	|-- .gitignore // Ignore file list in git
	|-- README.md // Description file in markdown format
	|-- angular.json // Profile of angular
	|-- browserslist // File used to configure browser compatibility
	|-- karma.conf.js // Configuration file of automated test framework Karma
	|-- package-lock.json // Package version dependent locking file
	|-- package.json // Package definition file of npm
	|-- tsconfig.app.json // ts configuration file for app project
	|-- tsconfig.json // ts profile for the entire workspace
	|-- tsconfig.spec.json // ts configuration file for testing
	|-- tslint.json // Code static scanning configuration of ts
	|-- e2e // Automated integration test catalog
	|-- src // Source code directory
 
|-- src // Source code directory
	|-- favicon.ico // Favorite Icon
	|-- index.html // Apply single page to host HTML
	|-- main.ts // Entry ts file
	|-- polyfills.ts // Compatible script loading for different browsers
	|-- styles.css // Global css for the entire project
	|-- test.ts // Test entrance
	|-- app // Project source code directory
	|-- assets // Resource directory
	|-- environments // Environment configuration
		|-- environments.prod.ts // production environment 
		|-- environments.ts // development environment 

Angular module

On app module. AppModule is defined in TS, and this root module will tell Angular how to assemble applications.

@NgModule decorator

@NgModule accepts a metadata object and tells Angular how to compile and start the application

Design intent

  • Static metadata (declarations)
  • Runtime metadata (providers)
  • Combination and grouping (imports and exports)

metadata

  • declarations array: the components, instructions or pipelines owned by the module. Note that each component / instruction / pipeline can only be declared in one module
  • providers array: the services to be used in the module
  • imports array: import the dependent modules required by this module. Please note that the modules
  • exports array: components, instructions or pipelines exposed to other modules
  • bootstrap array: Specifies that the main view of the application (called root component) starts the application by booting the root AppModule, that is, which component to read when the project is just loaded
  • entryComponents array: generally used for dynamic components

Built in module

Commonly used are: core module, general module, form module, network module, etc

Custom module

When the project is relatively small, the user-defined module can not be used

However, when the project is very large, it is not appropriate to mount all components into the root module

Therefore, the user-defined module can be used to organize projects, and the lazy loading of routes can be realized through the user-defined module

Module tips

When importing other modules, you need to know the purpose of using this module

  • If it is a component, it needs to be imported in each required module
  • If it is a service, you can generally import it once in the root module

It needs to be imported in each required module

  • CommonModule: provides basic instructions such as binding, * ngIf and * ngFor. Basically, each module needs to import it
  • FormsModule / ReactiveFormsModule: the form module needs to be imported in each required module
  • A module that provides a component, instruction, or pipeline

Imported only once in the root module

  • HttpClientModule / BrowerAnimationsModule NoopAnimationsModule
  • Service only modules

Angular component

  • Component is the core of Angular and the most basic UI building block in Angular application. It controls a small area on the screen called view
  • Components must be subordinate to a NgModule to be used by other components or applications
  • The component is referenced in the declarations field of @ NgModule metadata

@Component metadata

  • Selector: selector, which selects the instruction template in the matching HTML
  • templateUrl: replace the matching instruction sibling in the selector with the template of the value
  • Template: embedded template, in which you can write HTML template directly
  • styleUrls: an array of styles corresponding to the template. Multiple css style control components can be introduced
  • Encapsulation: component style encapsulation strategy
@Component({
  selector: 'app-xxx',
  templateUrl: 'XXX',
  styleUrls: ['XXX'],
  encapsulation:ViewEncapsulation.Emulated  // If it is not written, the default value indicates that the component style only works on the component itself, does not affect the global style, and generates a separate style label in the head
})

Data binding

  • Data binding {{data}}

  • Attribute binding [id]="id", where [class. Style class name] = "judgment expression" is a common skill when applying a single class style

  • Event binding (keyup)="keyUpFn($event)"

  • Style binding can use a pseudo class selector such as: host. The bound style acts on the component itself

  • Bidirectional data binding [(ngModel)]

    // Note: FormsModule
    import { FormsModule } from '@angular/forms';
    
    <input type="text" [(ngModel)]="inputValue"/> {{inputValue}}
    
    // It's actually a grammar sugar
    [ngModel]="username" (ngModelChange)="username = $event"
    

Dirty value detection

Dirty value detection: update the view (DOM) when the data changes

How to detect: detect two status values (current status and new status)

When to trigger dirty value detection: Browser events (click, mouseover, keyup, etc.), setTimeout() or setInterval(), HTTP request

There are two policies for change detection: push and Default

Metadata can be set in @ detectedstrategy. Changestrategy Onpush to switch

Default:

Advantages: every time an asynchronous event occurs, Angular will trigger change detection, traverse its sub components from the root component, detect the change of each component and update the dom.

Disadvantages: there are many component states that have not changed, so there is no need for change detection. If there are more components in the application, the performance problems will become more and more obvious.

OnPush:

Advantages: the component change detection completely depends on the component input (@ Input). As long as the input value remains unchanged, the change detection will not be triggered, nor will the change detection be carried out on its sub components. There will be significant performance improvement in many components.

Disadvantages: the input (@ Input) must be immutable (Immutable.js can be used to solve it), and each input change must be a new reference.

Parent child component communication

The parent component passes the value @ input to the child component

The parent component can not only transfer simple data to the child component, but also transfer its own methods and the whole parent component to the child component.

// Data is passed in when the parent component calls the child component
<app-header [msg]="msg"></app-header>

// Sub components are introduced into the Input module
import { Component, OnInit ,Input } from '@angular/core';

// The @ Input decorator in the child component receives the data from the parent component
export class HeaderComponent implements OnInit {
  @Input() msg:string
	constructor() { }
	ngOnInit() { }
}

// The data of the parent component is used in the child component
<h2>This is the head assembly--{{msg}}</h2>

**The child component triggers the parent component's method @ Output**

// The subcomponent introduces Output and EventEmitter
import { Component,OnInit,Input,Output,EventEmitter} from '@angular/core';

// Instantiate EventEmitter in subcomponent
// Use EventEmitter and @ Output decorator to specify type variables with < string >
@Output() private outer=new EventEmitter<string>();

// The subcomponent broadcasts data through the outer instance of the EventEmitter object
sendParent(){
  this.outer.emit('msg from child')
}

// When a parent component calls a child component, it defines to receive events. outer is the EventEmitter object of the child component
<app-header (outer)="runParent($event)"></app-header>

// When the parent component receives the data, it will call its own runParent. At this time, it can get the data of the child component
// Receive data from sub components
  runParent(msg:string){
   alert(msg);
}

Actively call the parent and child DOM components through the ViewChild method

// Define a name for the subcomponent
<app-footer #footerChild></app-footer>

// Introducing ViewChild
import { Component, OnInit ,ViewChild} from '@angular/core';

// ViewChild is associated with child components
@ViewChild('footerChild') footer;

// Call subcomponent
run(){
   this.footer.footerRun();
}

Projection component

Because over nesting of components will lead to data redundancy and event transmission, the concept of projection component is introduced

The projection component ng content is used as a container component

It is mainly used for rendering dynamic content of components, which has no complex business logic and does not need to be reused, but only a small part of HTML fragments

Use the ng content instruction to project any fragment in the parent component template onto its child components

The ng content part in the component can be replaced by elements wrapped outside the component

// Manifestation: < ng content select = "style class / HTML tag / instruction" > < / ng content >

<ng-content select="[appGridItem]"></ng-content>

select indicates that only elements containing the instruction of appGridItem can be projected and penetrated


Angular instruction

An instruction can be understood as a component without a template, which requires a host element

It is recommended to specify the Selector with square brackets [] to make it a property

@Directive({
selector: '[appGridItem]'
})

Built in attribute type instruction

NgClass

ngClass is the most flexible and extensible style binding method

<div [ngClass]="{'red': true, 'blue': false}">
  This is a div
</div>

NgStyle

Because ngStyle is an embedded style, it may overwrite other styles. Be careful

<div [ngStyle]="{'background-color':'green'}">Hello ngStyle</div>

NgModel

// Note: FormsModule
import { FormsModule } from '@angular/forms';

<input type="text" [(ngModel)]="inputValue"/> {{inputValue}}

Built in structured instruction

ngIf

ngIf determines whether to display DOM tags according to whether the expression is valid

<p *ngIf="list.length > 3">This is ngIF Judge whether to display</p>

ngIf else

<div *ngIf="show else ElseContent">This is ngIF content</div>
<ng-template #ElseContent>
  <h2>This is else content</h2>
</ng-template>

// Structural instructions depend on ng template, * ngIf is actually the [ngIf] attribute of the ng template instruction.

ngFor

<ul>
  <li *ngFor="let item of list;let i = index;">
     {{item}} --{{i}}
  </li>
</ul>

ngSwitch

<ul [ngSwitch]="score">
   <li *ngSwitchCase="1">Paid</li>
   <li *ngSwitchCase="2">Confirmed</li>
   <li *ngSwitchCase="3">Shipped</li>
   <li *ngSwitchDefault>Invalid</li>
</ul>

Instruction event style binding

@HostBinding binds the properties or styles of the host

@HostBinding('style.display') display = "grid";

// Use style binding instead of rd2's this setStyle('display','grid');

@HostListener binds the event of the host

@HostListener('click',['$event.target'])

// The first parameter is the event name, and the second is the parameter carried by the event

Angular life cycle

Generally speaking, life cycle function is a series of methods triggered when creating, updating and destroying components

When Angular uses the constructor to create a new component or instruction, it will call the life cycle hook at a specific time in the following order

  • Constructor: constructor is always called first. It is generally used for variable initialization and class instantiation

  • ngOnChanges: called when the bound input attribute changes. The first call must be before ngOnInit. The change of input attribute is triggered, but the change of input attribute inside the component will not be triggered. Note: if the component has no input or does not provide any input when using it, the framework will not call ngOnChanges

  • ngOnInit: called during component initialization. It is called only once after the first round of ngOnChanges. Using ngOnInit, you can execute complex initialization logic immediately after the constructor. At the same time, after setting the input properties of Angular, you can build the component safely

  • ngDoCheck: called during dirty value detection, after ngOnChanges and ngOnInit in the change detection cycle

    • ngAfterContentInit: called when the content projection ng content is completed, only after the first ngDoCheck

    • ngAfterContentChecked: called each time after the change detection of the projected component content is completed (multiple times)

    • ngAfterViewInit: called when the initialization of component view and sub view is completed. It is only called once after the first ngAfterContentChecked

    • ngAfterViewChecked: called after detecting the change of component view and child view (multiple times)

  • ngOnDestroy is called when the component is destroyed. You can unsubscribe observable objects and separate event handlers to prevent memory leakage


Angular routing

Routing (navigation) is essentially a mechanism to switch views. The navigation URL of the route does not really exist

Angular's routing draws on the mechanism of page switching caused by browser URL changes

Angular is a single page program. The path displayed by the route is just a mechanism to save the route state. This path does not exist on the web server

Basic routing configuration

/**
 * After defining a sub route in the function module, importing the module is equivalent to defining it directly in the root route
 * That is, when importing HomeModule in AppModule,
 * Because HomeRouting Module is imported into HomeModule
 * The routes defined in HomeRoutingModule will be merged into the root route table
 * It is equivalent to defining the following array directly in the root module.
 * const routes = [{
 *   path: 'home',
 *   component: HomeContainerComponent
 * }]
 */

const routes: Routes = [
  {path: 'home', component: HomeComponent},
  {path: 'news', component: NewsComponent},
  {path: 'newscontent/:id', component: NewscontentComponent},  // Configure dynamic routing
  {
    path: '',
    redirectTo: '/home',  // redirect
    pathMatch: 'full'
	},
  //The component loaded or the route skipped when the route cannot be matched
  {
     path: '**', /*Arbitrary routing*/
     // component:HomeComponent
     redirectTo:'home'
  }
]

@NgModule({
  /**
   * Root route use ` routermodule Forroot (routes) ` form.
   * The routing module in the function module uses ` outermodule Forchild (routes) ` form.
   * To enable the debug tracking mode of routing, you need to set ` enableTracing: true in the root module`
   */
  imports: [RouterModule.forRoot(routes, { enableTracing: true })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Activate route

Find app component. HTML root component template, configure router outlet

Access routes through template properties, i.e. route link routerLink

<h1>
  <a [routerLink]="['/home']">home page</a>
  <a [routerLink]="['/home',tab.link]">home page</a><!-- Path parameters -->
  <a [routerLink]="['/home',tab.link,{name:'val1'}]">home page</a> <!-- Path object parameters -->
  <a [routerLink]="['/home']" [queryParams]="{name:'val1'}">home page</a> <!-- Query parameters -->
</h1>
<router-outlet></router-outlet>  <!-- Routing socket, occupancy label -->
<!--
  The content displayed by the route is inserted into router-outlet Lower node of peer
  Not in router-outlet Contains
-->
<!--
  When an event is processed or a condition is reached, you can use manual jump
	this.router.navigate(['home']); 
	this.router.navigate(['home',tab.link]); 
	this.router.navigate(['home',tab.link,{name:'val1'}]); 
	this.router.navigate(['home'],{queryParams:{name:'val1'}}); 
-->

The style that controls the route activation status routerLinkActive

<h1>
    <a routerLink="/home" routerLinkActive="active">home page</a>
    <a routerLink="/news" routerLinkActive="active">Journalism</a>
</h1>

<h1>
   <a [routerLink]="[ '/home' ]" routerLinkActive="active">home page</a>
   <a [routerLink]="[ '/news' ]" routerLinkActive="active">Journalism</a>
</h1>

.active{
   color:red;
}

Routing parameters

Path parameter reading

this.route.paramsMap.subscribe(params => {...})

Query parameter reading

this.route.queryParamsMap.subscribe(params => {...})

Route passes a parameter and its receiving method:

Pass parameter: path: 'info/:id'

Receiving parameters:

constructor(private routerInfo: ActivatedRoute){}
ngOnInit(){
	this.routerInfo.snapshot.params['id']
}

Route passing multiple parameters and its receiving method:

Pass: [queryParams] = '{id:1,name:' crm '}'

Receiving parameters:

constructor(private routerInfo: ActivatedRoute){}
ngOnInit(){
	this.routerInfo.snapshot.params['id']
	this.routerInfo.snapshot.params['name']
}

Route lazy loading

Lazy loading sub module. The sub module needs to configure routing settings and start the sub module loadChildren

const routes: Routes = [
    {path:'user',loadChildren:'./module/user/user.module#UserModule' },
    {path:'product',loadChildren:'./module/product/product.module#ProductModule'},
    {path:'article',loadChildren:'./module/article/article.module#ArticleModule'},
    {path:'**',redirectTo:'user'}
];

// It seems that the Error find module will be reported 
// Configure lazy loading
const routes: Routes = [
    {path:'user',loadChildren:()=>import('./module/user/user.module').then(mod=>mod.UserModule)},
    {path:'article',loadChildren:()=>import('./module/article/article.module').then(mod=>mod.ArticleModule)},
    {path:'product',loadChildren:()=>import('./module/product/product.module').then(mod=>mod.ProductModule)},
    {path:'**',redirectTo:'user'}
];

Angular service

Instead of directly acquiring or saving data, components should focus on displaying data and delegate the responsibility of data access to a service

Data acquisition and view presentation should be separated, and the method of data acquisition should be put in the service

Similar to VueX, global shared data (general data) and value transfer and shared data of non parent and child components are placed in the service

Components call the methods defined in each component

The methods used by multiple components (such as data caching) are placed in the service

import { Injectable } from '@angular/core';
@Injectable({
  providedIn: 'root',
})
export class HeroService {
  aa = 'abc';
  constructor(){ }
  ngOnInit(){ }
}

import { HeroService } from '../../../services/hero/hero.service';
export class AComponent implements OnInit{
  constructor(private heroService : HeroService) {} //instantiation 
  ngOnInit(){
    console.log(this.heroService.aa)
  }
}

@Injectable() decorator

In Angular, to define a class as a service, use the @ Injectable() decorator to provide metadata so that Angular can inject it into the component as a dependency.

Similarly, use the @ Injectable () decorator to indicate that a component or other class (such as another service, pipe, or NgModule) has a dependency.

@The Injectable () decorator marks this service class as one of the participants in the dependency injection system, which is the basic element in each Angular service definition.

When Angular's dependency injector is not configured, Angular cannot actually inject it anywhere.

@The Injectable () decorator has a metadata option called providedIn. If providedIn is set to 'root', that is, in the root component, the service can be used in the whole application.

providedIn provides these values: 'root', 'platform', 'any', null

For any service to be used, at least one provider must be registered.

Services can register themselves as providers in their own metadata, make themselves available everywhere, or register providers for specific modules or components.

To register a provider, you need to provide its metadata in the @ Injectable () decorator of the service, or in the metadata of @ NgModule () or @ Component ().

When providing services in components, you can also use viewProdivers. viewProviders are not visible to the sub component tree

Different levels of providers can be used to configure the injector, which also indicates the scope of the service

  • The default method used by Angular to create a service is in the @ Injectable () decorator of the service itself

  • This service is only used in a service: in the @ NgModule() decorator of NgModule

  • The service is used in a Component: in the @ Component () decorator of the Component

Dependency injection

In the project, some people provide services and others consume services, while the dependency injection mechanism provides an intermediate interface and creates and initializes processing for consumers

Consumers only need to know that what they get is a complete and available service. They don't need to pay attention to the internal implementation of this service or even what other services it depends on.

Angular shares state through services, and these services that manage state and data are processed through dependency injection

The essence of an Angular service is dependency injection, which injects the service into the component as an Injector

In the final analysis, we often create services to maintain common state and data, and specify which components can be shared through dependency injection

It is precisely because of the dependency injection mechanism provided by Angular that instantiation can be declared directly in the constructor

  constructor(private heroService : HeroService) {} // Dependency injection

Let's first look at the injection of TS single file in Angular

// First, write @ injectable what we need to inject, such as product
@Injectable()
class Product {
  constructor(
    private name: string,
    private color: string,
    private price: number,
  ) { }
}

class PurchaseOrder {
  constructor(private product: Product){ }
}
 
export class HomeGrandComponent implements OnInit {
  constructor() { }
  ngOnInit() {
    // Construct an injector and use the create method to write what we need to construct in the providers array
    const injector = Injector.create({
      providers: [
        {
          provide: Product,
          // If the product is constructed, the product defined above will be injected here in useFactory
          useFactory: () => {
            return new Product('Rice mobile phone', 'black', 2999);
          },
          deps: []
        },
        {
          provide: PurchaseOrder,
          deps: [Product]
        },
        {
          provide: token,
          useValue: { baseUrl: 'http://local.dev' }
        }
      ]
    }); 
    console.log('injector obtain product', injector.get(PurchaseOrder).getProduct);
    console.log(injector.get(token));
  }

Let's take another look at the injection of module module in Angular

// .service. @ Injectable() dependency injection in TS
@Injectable()
export class HomeService {
  imageSliders: ImageSlider[] = [
    {
      imgUrl:'',
      link: '',
      caption: ''
    }
  ]
  getBanners() {
    return this.imageSliders;
  }
}

// Use the corresponding of the module module. In TS
@NgModule({
  declarations: [
    HomeDetailComponent,
  ],
  providers:[HomeService], // Write the corresponding service directly in the providers and inject the service into the module directly
  imports: [SharedModule, HomeRoutingModule]
})

Whether in the component or in the module, when we use providers, we register and initialize dependency injection

In fact, like components, the module class (NgModule) is an injector in dependency injection, which provides the interface of dependency injection as a container

NgModule eliminates the need to inject another component into one component. It can be obtained and shared through the module class (NgModule)


Angular pipe

The Angular pipeline is a way to write a display value transformation that can be declared in an HTML component

The pipeline takes data as input and converts it to the desired output

A pipeline is actually a filter used to convert data and display it to users

The pipeline takes integer, string, array and date as input, separated by |, and then converts the format as needed and displays it in the browser

In interpolation expressions, you can define pipes and use them as appropriate

Many types of pipes can be used in Angular applications

Built in pipe

  • String -> String
    • Convert UpperCasePipe to uppercase characters
    • Convert LowerCasePipe to lowercase characters
    • TitleCasePipe is converted to title form, with the first letter uppercase and the rest lowercase
  • Number -> String
    • DecimalPipe formats values based on numeric options and locale rules
    • PercentPipe converts a number to a percentage string
    • CurrencyPipe changes the name currency format
  • Object -> String
    • JsonPipe object serialization
    • DatePipe date format conversion
  • Tools
    • SlicePipe string interception
    • AsyncPipe resolves a value from the asynchronous receipt
    • I18nPluralPipe Pluralization
    • I18nSelectPipe displays a string that matches the current value

usage method

<div>{{ 'Angular' | uppercase }}</div>  <!-- Output: ANGULAR -->

<div>{{ data | date:'yyyy-MM-dd' }}</div>  <!-- Output: 2022-05-17 -->

<div>{{ { name: 'ccc' } | json }}</div>  <!-- Output: { "name": "ccc" } -->

<!-- 
	The pipe can receive any number of parameters by adding after the pipe name: And parameter values
	If multiple parameters need to be passed, the parameters are separated by colons 
-->

<!-- Multiple pipelines can be connected together to form a pipeline chain for data processing -->
<div>{{ 'ccc' | slice:0:1 | uppercase }}</div>

Custom pipe

Pipeline is essentially a class in which the transform method of PipeTransfrom interface is implemented

  • Use the @ Pipe decorator to define the metadata information of the Pipe, such as the name of the Pipe - that is, the name attribute
  • Implement the transform method defined in the PipeTransform interface
// PipeTransform was introduced to inherit the transform method
import { Pipe, PipeTransform } form '@angular/core'; 
// The name attribute value is usually written in the small hump method, and the value of name is the name after | in html
@Pipe({ name: 'sexReform' }) 
export class SexReformPipe implements PipeTransform {
    transform(value: string, args?: any): string {
    // The value of value is the value passed in before in html, and args is the parameter passed in after the name
        switch(value){
            case 'male': return 'male';
            case 'female': return 'female';
            default: return 'Hermaphrodite';
        } 
    }
}

// demo.component.ts
export Class DemoComponent {
    sexValue = 'female';
}

// demo.component.html
<span>{{ sexValue | sexReform }}</span>

// Browser output
 female

// Pipes can be used in chains and can also pass parameters
<span> {{date | date: 'fullDate' | uppercase}} </span>
// Each custom pipeline needs to implement the PipeTransform interface. This interface is very simple. You only need to implement the transform method.
// transform() method parameter format - transform(value: string, args1: any, args2?: any): 
// Value is the incoming value (that is, the value that needs to be processed with this pipeline, | the previous value); 
// args is the passed in parameter (?: optional);
// Use pipeline format in html - {{data | pipeline Name: parameter 1: parameter 2}}
// Like component, pipe needs to be declared in the declarations array before use

Angular operation DOM

Native JS operation

ngAfterViewInit(){
   var boxDom:any=document.getElementById('box');
   boxDom.style.color='red';
}

ElementRef

ElementRef is a wrapper class for a native element in a view

Because the DOM element is not a class in Angular, you need a wrapper class to use and identify its type in Angular

Behind ElementRef is a concrete element that can be rendered. In browsers, it is usually a DOM element

class ElementRef<T> {
  constructor(nativeElement: T)
  nativeElement: T  //The native element behind it is null if it does not support direct access to the native element (for example, when running this application in the Web Worker environment).
}

When you need to access DOM directly, please use this API as the last choice. Give priority to the template and data binding mechanism provided by Angular

If you rely on direct access to the DOM, there may be a tight coupling between the application and the rendering layer. This will make it impossible to separate the two, so the application cannot be published to the Web Worker

ViewChild

Use template and data binding mechanism, and use @ viewChild

// Give DOM a reference name in the template so that it can be referenced in the component class or template < div #myattr > < / div >

// Introducing ViewChild
import { ViewChild,ElementRef } from '@angular/core';

// Binding DOM with ViewChild	
@ViewChild('myattr') myattr: ElementRef;

// In the ngAfterViewInit lifecycle function, you can safely obtain the DOM referenced by ViewChild
ngAfterViewInit(){
   let attrEl = this.myattr.nativeElement;
}

In the parent component, you can call the methods of the child component through ViewChild

// Define a name for the subcomponent
<app-footer #footerChild></app-footer>

// Introducing ViewChild
import { Component, OnInit ,ViewChild} from '@angular/core';

// ViewChild is associated with child components 
// If you want to reference the Angular component in the template, you can use the reference name or the component type in ViewChild
@ViewChild('footerChild') footer;

// @Viewchild ('imageslider ', {static: true}) / / static specifies whether it is dynamic or static. It is dynamic in * ngFor or * ngIf. Otherwise, it is static and dynamic is true

// Call subcomponent
run(){
   this.footer.footerRun();
}

To reference multiple template elements, @ ViewChildren can be used, and the reference name can be used in ViewChildren

Or use the type of Angular component / instruction and declare the type as querylist <? >

<img
  #img
  *ngFor="let slider of sliders"
  [src]="slider.imgUrl"
  [alt]="slider.capiton"
>

// Get using the ViewChildren reference
@ViewChildren('img');

// Get using type reference
imgs: QueryList<ElementRef>;

Renderer2

Renderer2 is an abstract class of operation element provided by Angular. Using the methods provided by this class, you can operate the elements on the page without directly contacting the DOM.

Common methods of Renderer2:

  • addClass /removeClass adds or deletes a class in the host element of the directive
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

@Directive({
    selector: '[testRenderer2]'
})

export class TestRenderer2Directive implements OnInit {
    constructor(private renderer: Renderer2, private el: ElementRef) {} // instantiation 

    ngOnInit() {
    this.renderer.addClass(this.el.nativeElement, 'test-renderer2');
    // this.renderer.removeClass(this.el.nativeElement, 'old-class');
    }
}
  • createElement /appendChild/createText creates a DIV element, inserts text content, and mounts it on the host element
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    const div = this.renderer.createElement('div');
    const text = this.renderer.createText('Hello world!');
    
    this.renderer.appendChild(div, text);
    this.renderer.appendChild(this.el.nativeElement, div);
}
  • setAttribute /removeAttribute adds or removes attributes on the host element
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.setAttribute(this.el.nativeElement, 'aria-hidden', 'true');
}
  • setStyle /removeStyle add inline style on host element
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core';

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.setStyle(
        this.el.nativeElement,
        'border-left',
        '2px dashed olive'
    );
}

Remove inline style:

constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.removeStyle(this.el.nativeElement, 'border-left');
}
  • setProperty sets the value of the property of the host element
constructor(private renderer: Renderer2, private el: ElementRef) {}

ngOnInit() {
    this.renderer.setProperty(this.el.nativeElement, 'alt', 'Cute alligator');
}

Direct operation of DOM is not recommended by angular. Try to use the combination of @ viewChild and renderer2. Angular recommends using constructor(private rd2: Renderer2) {} dependency injection,

import {
  Component,
  OnInit,
  Renderer2,
  ViewChild,
} from '@angular/core';
import { AboxItemComponent } from './abox-item/abox-item.component';
 
@Component({
  selector: 'app-abox',
  templateUrl: './abox.component.html',
  styleUrls: ['./abox.component.less'],
})
export class AboxComponent implements OnInit {
  private container;
  activeIndex: number;
  @ViewChild('containers') containers: any;
  constructor(private rd2: Renderer2) {}
 
  ngOnInit(): void {}
 
  ngAfterViewInit(): void {
    this.container = this.containers.nativeElement;
    this.initCarouselWidth();
  }
    
  initCarouselWidth() {
    this.rd2.setStyle(this.container, 'width', '100px');
  }
}

Angular network request

HttpClient

HttpClientModule needs to be imported, which is only imported in the root module, and the whole application only needs to be imported once, not in other modules

Inject HttpClient into the constructor. The get/post method corresponds to the HTTP method. These methods are generic and can directly convert the returned JSON into the corresponding type. For non-standard requests, use the request method

The returned value is Observable. The request will be sent only after subscription, otherwise it will not be sent

get request data

// On app module. Introduce HttpClientModule into ts and inject
import {HttpClientModule} from '@angular/common/http';
imports: [
  BrowserModule,
  HttpClientModule
]

// Introduce HttpClient where it is used and declare it in the constructor
import {HttpClient} from "@angular/common/http";
constructor(private http: HttpClient,private cd: ChangeDetectorRef) { } // Dependency injection

// get request data
var api = "http://baidu.com/api/productlist";
this.http.get(api).subscribe(response => {
  console.log(response);
  this.cd.markForCheck();
  // If the change principle of dirty value detection is changed, changedetection: changedetectionstrategy OnPush
  // You need to use this cd. Markforcheck() manually reminds Angular that dirty value detection is required here
});

post submit data

// On app module. Introduce HttpClientModule into ts and inject
import {HttpClientModule} from '@angular/common/http';
imports: [
   BrowserModule,
   HttpClientModule
]

// Introduce HttpClient and HttpHeaders where they are used, and declare HttpClient in the constructor
import {HttpClient,HttpHeaders} from "@angular/common/http";
constructor(private http:HttpClient) { } // instantiation 

// post submit data
const httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
var api = "http://127.0.0.1:4200/doLogin";
this.http.post(api,{username:'Rui Mengmeng',age:'22'},httpOptions).subscribe(response => {
		console.log(response);
});

Jsonp request data

// On app module. Introduce HttpClientModule and HttpClientJsonpModule into ts and inject
import {HttpClientModule,HttpClientJsonpModule} from'@angular/common/http';
imports: [
   BrowserModule,
   HttpClientModule,
   HttpClientJsonpModule
]

// Introduce HttpClient where it is used and declare it in the constructor
import {HttpClient} from "@angular/common/http";
constructor(private http:HttpClient) { } // instantiation 

// jsonp request data
var api = "http://baidu.com/api/productlist";
this.http.jsonp(api,'callback').subscribe(response => {
   console.log(response);
});

Interceptor

The Angular interceptor is a way to capture and modify HTTP requests and responses globally in Angular applications, such as carrying tokens and capturing errors

The premise is that only requests sent by HttpClientModule can be intercepted. If axios is used, it cannot be intercepted

Create interceptor

// Use the command ng g interceptor name to create the interceptor ng g interceptor LanJieQi here
// There is no way to generate cli shorthand

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class LanJieQiInterceptor implements HttpInterceptor {
  constructor() {}
  // The default intercept() method simply forwards the request to the next interceptor (if any), and finally returns the Observable of the HTTP response body
  // Request: httprequest < unknown > indicates the request object, which contains all information related to the request. Unknown specifies the type of the request body
  // Next: after the HttpHandler request object is modified, the modified request object is returned to the method that actually sends the request through the handle method in next
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 	{
    // The next object represents the next interceptor in the interceptor chain list (multiple interceptors can be set in the application)
    return next.handle(request);
  }
}

Injection interceptor

// Inject interceptors into @ NgModule module
// Interceptor is also a service managed by Angular dependency injection (DI) system. You must provide this interceptor class before you can use it
// Since interceptors are a dependency of HttpClient service, they must be provided in the same injector (or its parent injector at all levels) that provides HttpClient
@NgModule({
  imports: [
    HttpClientModule
    // others...
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: LanJieQiInterceptor,
      // multi: true indicates HTTP_INTERCEPTORS is a multi provider token, indicating that this token can be injected into multiple interceptors
      multi: true
    },
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }

Request header interception

@Injectable()
export class LanJieQiInterceptor implements HttpInterceptor {
  constructor() {}
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 	{
    // In order to uniformly set the request header, you need to modify the request
    // However, the properties of HttpRequest and HttpResponse instances are read-only
    // Therefore, a copy of clone is required before modification. After modifying the clone, it is passed to next handle()
    let req = request.clone({
    	setHeaders:{
      	token:"123456" // Add token: 123456 in the request header
    	}
			// setHeaders and headers: request headers. Set ('token ',' 123456 ') is consistent
  	})
  	return next.handle(req)// Return the modified request to the application
  }
}

Response capture

@Injectable()
export class LanJieQiInterceptor implements HttpInterceptor {
  constructor() {}
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 	{
    let req = request.clone({
      setHeaders:{
        token:"123456" // Add token: 123456 in the request header
      }
    })
    return next.handle(req).pipe(
      retry(2), // The retry operator retry of RxJS automatically requests another 2 times after finding an exception
      catchError((error:HttpErrorResponse) => throwError(error))
    )
  }
}

If there are multiple interceptors, the request order is executed according to the configuration order, and the response interception is in the reverse order

If the order of providing interceptors is A, B and C, the execution order of the request phase is A - > b - > C, and the execution order of the response phase is C - > b - > A


Angular form

Template driven form

Template driven forms are very useful when adding simple forms to applications, but they are not as easy to expand as responsive forms

If you have very basic form requirements and logic that is simple enough to be managed with templates, use template driven forms

Reactive forms and template driven forms share some underlying building blocks:

The FormControl instance is used to track the value and validation status of a single form control

FormGroup is used to track the value and status of a form control group

FormArray is used to track the value and status of the form control array. It has a length attribute and is usually used to represent a collection of fields that can grow

ControlValueAccessor is used to create a bridge between the FormControl instance of Angular and the native DOM element

FormControl and FormGroup are the two most basic form objects in angular

FormControl represents a single input field. It is the smallest single member in the Angular form. It encapsulates the values and status of these fields, such as whether they are valid, dirty (modified) or error

FormGroup can provide wrapper interface for a group of formcontrols to manage multiple formcontrols

When we try to get value from FormGroup, we will receive an object with "key value pair" structure

It allows us to get all the values from the form at one time without traversing FormControl one by one, which is quite easy to use

FormGroup and FormControl both inherit from the same ancestor AbstractControltractControl (this is the base class of FormControl, FormGroup and FormArray)

Load FormsModule first

// First, import the FormsModule form library in NgModule
// FormsModule provides us with some template driven instructions, such as ngModel and NgForm
import { 
  FormsModule
} from '@angular/forms'; 

@NgModule({ 
  declarations: [ 
    FormsDemoApp, 
    DemoFormSku, 
    // ... our declarations here 
  ], 
  imports: [ 
    BrowserModule, 
    FormsModule,
  ], 
  bootstrap: [ FormsDemoApp ] 
}) 
class FormsDemoAppModule {}

Next, create a template form

 <div>
      <h2>Basic form:Trade name</h2>
      <form #f="ngForm" (ngSubmit)="onSubmit(f.value)">
        <div class="sku">
          <label for="skuInput">Trade name:</label>
          <input
            type="text"
            id="skuInput"
            placeholder="Trade name"
            name="sku" //When using the form, it must be defined, which can be understood as the name of the current control
            ngModel
          />
        </div>
        <button>Submit</button>
      </form>
    </div>

We have imported FormsModule, so we can use NgForm in the view

When these instructions are available in the view, they are attached to any node that matches its selector

ngForm does a convenient but obscure job: its selector contains the form tag (instead of explicitly adding the ngForm attribute)

This means that when importing FormsModule, NgForm will be automatically attached to all tags in the view

NgForm provides two important functions:

  • A FormGroup object of ngForm
  • An output event (ngSubmit)
 <form #f="ngForm" (ngSubmit)="onSubmit(f.value)" >
 <!-- 
	Used here #f="ngForm",#v=thing means that we want to create a local variable in the current view
	Here is the in the view ngForm An alias is created and bound to the variable #f
	this ngForm By NgForm Instruction derived
	ngForm The object of type is FormGroup Type
	This means that you can put variables in the view f As FormGroup Use, which is exactly what we are outputting events (ngSubmit) Usage in
	Bind in form ngSubmit event (ngSubmit)="onSubmit (f.value)"
	(ngSubmit) come from NgForm instructions
	onSubmit() It will be defined in the component class
	f namely FormGroup ,and .value Will be returned as a key value pair FormGroup Values for all controls in
	
	Summary: when submitting a form, the value of the form will be used as a parameter to call the on the component instance `onSubmit` method
-->

NgModel will create a new FormControl object and automatically add it to the parent FormGroup (here is the form object)

And bind the FormControl object to a DOM

That is, it establishes an association between the input tag in the view and the FormControl object

This association is established through the name attribute, which in this case is "name"

Responsive form

Using ngForm to build FormControl and FormGroup is convenient, but it can't provide customization options, so it introduces responsive forms

Responsive forms provide a model driven way to process form input, where values change over time

When using responsive forms, you create an underlying data model by writing TypeScript code instead of HTML code

After the model is defined, some specific instructions are used to connect the HTML elements on the template with the underlying data model

FormBuilder is a form building assistant worthy of its name (you can think of it as a "factory" object)

In the previous example, add a FormBuilder, and then use FormGroup in the component definition class

// First, import the ReactiveFormsModule form library in NgModule
import { 
  ReactiveFormsModule 
} from '@angular/forms'; 
@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule
  ]
}) 

// To build this component using formGroup and formControl instructions, you need to import the corresponding classes
import { 
  FormBuilder, 
  FormGroup,
  ReactiveFormsModule
} from '@angular/forms'; 

// Inject an object instance created from the FormBuilder class into the component class and assign it to the fb variable (from the constructor)
export class DemoFormSkuBuilder { 
  myForm: FormGroup;  // myForm is a FormGroup type
  constructor(fb: FormBuilder) { 
    // The group method in FormBuilder is used to create a new FormGroup
    // The parameters of the group method are key value pairs representing each FormControl in the group
    this.myForm = fb.group({  // Call FB Group() to create a FormGroup
      // Set a control named sku. The default value of the control is "123456"
      'sku': ['123456'] 
    }); 
  }
  onSubmit(value: string): void { 
    console.log('submit value:', value); 
  } 
}

Use custom formgroups in view forms

<h2 class="ui header">Demo Form: Sku with Builder</h2>
<!--  
	When importing FormsModule When, ngForm Will automatically create its own FormGroup
	But we don't want to use external FormGroup,But use FormBuilder Create this myForm Instance variable
	Angular Provided formGroup Instructions that allow us to use existing FormGroup
	NgForm Does not apply to tape formGroup On the node of the attribute
	Here we tell Angular,Want to use myForm As part of this form FormGroup
-->
<form [formGroup]="myForm" 
  <label for="skuInput"> SKU </label> 
  <input type="text" 
     id="skuInput" 
     placeholder="SKU" 
     [formControl]="myForm.controls['sku']">
<!--  
	take FormControl Bind to input On the label : 
	ngModel A new one will be created FormControl Object and attach to parent FormGroup in
	But in the example, we have used FormBuilder Created your own FormControl
	To convert an existing FormControl Bind to input Yes, you can formControl instructions
	take input On the label formControl Instruction pointing myForm.controls Existing on FormControl control sku  
-->

Remember the following two points:

  1. If you want to implicitly create a new FormGroup and FormControl, use: ngForm, ngModel
  2. If you want to bind an existing formGroup and formControl, use: formGroup, formControl

Form Validation

The data format entered by the user is not always correct. If someone enters the wrong data format, we hope to give him feedback and prevent him from submitting the form

Therefore, we need to use the verifier, which is provided by the validators module

Validators.required is the simplest verification, indicating that the specified field is required. Otherwise, FormControl is considered invalid

If one FormControl in a FormGroup is invalid, the whole FormGroup is invalid

To assign a verifier to a FormControl object, you can pass it directly to the FormControl constructor as the second parameter

const control = new FormControl('name', Validators.required);

// Using FormBuilder in a component definition class
  constructor(fb: FormBuilder) { 
    this.myForm = fb.group({ 
      'name': ['',Validators.required] 
    }); 
    this.name = this.myForm.controls['name']; 
  } 

Check the status of the validator in the view and act accordingly

template:`<div>
      <h2>Product form:Trade name</h2>
      <form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)">
        <div>
          <label for="nameInput">Trade name:</label>
          <input
            type="text"
            id="nameInput"
            placeholder="Please enter a name"
            [formControl]="myForm.controls['name']"
          />
          <div style="color:red" *ngIf="!name.valid">
            name invalid
          </div>
          <div style="color:red" *ngIf="name.hasError('textinvalid')">
            The name does not start with '123'
          </div>
          <div *ngIf="name.dirty">
            Data changed
          </div>
        </div>
        <div>
          <label for="codeInput">Item No.:</label>
          <input
            type="text"
            id="codeInput"
            placeholder="Please enter item No"
            [formControl]="myForm.controls['code']"
          />
          <div
            style="color:red"
            *ngIf="myForm.controls.code.hasError('required')"
          >
            This item is required
          </div>
          <div
            style="color:red"
            *ngIf="myForm.controls.code.hasError('pattern')"
          >
            Only numbers and English can be entered
          </div>
        </div>
        <div style="color:green" *ngIf="myForm.isvalid">
          Invalid form
        </div>
        <div style="color:green" *ngIf="myForm.valid">
          Form valid
        </div>
        <button type="submit">Submit</button>
      </form>
    </div>`
export class NonInWarehouseComponent implements OnInit {
  myForm: FormGroup;
  name: AbstractControl;
  constructor(fb: FormBuilder) {
    this.myForm = fb.group({
      name: ['milk', Validators.compose([Validators.required, textValidator])],
      code: ['', [Validators.required, Validators.pattern('^[A-Za-z0-9]*$')]],
    });
    this.name = this.myForm.controls.name;
  }
  ngOnInit() {
    const nameControl = new FormControl('nate');
    console.log('nameControl', nameControl);
  }
  onSubmit(a: any) {
    console.log('a', a);
  }
}

Built in calibrator

Angular provides several built-in calibrators, and the following are commonly used calibrators:

  • Validators.required - form control value is not empty
  • Validators.email - the format of the form control value is email
  • Validators.minLength() - the minimum length of the form control value
  • Validators.maxLength() - the maximum length of the form control value
  • Validators.pattern() - the value of the form control must match the pattern corresponding to the pattern (regular expression)

Custom validator

Suppose our name has special verification requirements. For example, name must start with 123

When the input value (control.value of the control) does not start with 123, the validator will return the error code invalidSku

// Validators. Is implemented in angular source code required 
export class Validators {
  // Receive an AbstractControl object as input
	static required(control: AbstractControl): ValidationErrors | null;
}
// When the verifier fails, it will return a string map < string, any > object. Its key is "error code" and its value is true
export declare type ValidationErrors = {
    [key: string]: any;
};

// Custom validator
function textValidator(
  controls: FormControl // Because FormControl inherits from AbstractControl, it can also be written as a FormControl object
): {
  [s: string]: boolean;
} {
  if (!controls.value.match(/^123/)) {
    return { textinvalid: true };
  }
}

Assign verifiers to FormControl, but name already has one. How to add multiple verifiers to the same field

Use validators Compose

Validators.compose wraps the two validators together, and we can assign them to FormControl

FormControl is valid only if both validators are valid

Validators.compose([Validators.required, textValidator])
// Don't use compose [validators. Required, textvalidator]
// Keep compose for compatibility with previous historical versions. It can be implemented without compose

Dynamic form

To realize the Angular dynamic form, the formArray method is mainly used. The instance generated by formArray is an array. In this array, formGroup and formControl can be dynamically put into it, which forms a dynamic form.

export class ReativeFormsComponent implements OnInit {
  ngOnInit() {
    this.addContact()
  }
  //Dynamic form
  personMess: FormGroup = new FormGroup({
    //Generate dynamic form array
    contacts: new FormArray([]) 
  })
  //Get array object
  get contacts(){
    return this.personMess.get('contacts') as FormArray
  }
  //Add a form group
  addContact(){
    let myContact = new FormGroup({
      name: new FormControl(),
      phone: new FormControl()
    })
    this.contacts.push(myContact)
  }
   //Delete a form group
  deleteContact(i:number){
    this.contacts.removeAt(i)
  }
  //Submit Form 
  OnSubmit() {
    console.log(this.personMess.value)
  }
}
<form [formGroup]="personMess" (submit)="OnSubmit()">
  <div formArrayName="contacts">
    <!-- Note: when traversing here contacts.controls -->
    <div *ngFor="let contact of contacts.controls;let i =index" [formGroupName]="i">
      <input type="text" formControlName="name">
      <input type="text" formControlName="phone">
      <button (click)="deleteContact(i)">Delete information</button>
    </div>
  </div>
  <button (click)="addContact()">Add information</button><br>
  <input type="submit">
</form>

Angular CDK

CDK is the abbreviation of Component Dev kit. When developing the Library, the Angular Material team found that the components have many similarities. Finally, it extracted and refined the common logic. This part is CDK

Officials use a very vivid metaphor: if the component library is a rocket spacecraft, then CDK is the engine parts box

Tags: Javascript Front-end angular TypeScript

Posted by echox on Mon, 23 May 2022 00:06:26 +0300