Data binding for templates

Overview of binding syntax

There are roughly three types of binding syntax (basic)

  • Model => view (one-way: interpolation, property binding, style binding)
  • View => model(unidirectional: event binding)
  • View <=> model (Two-Way: ngModule)
<! -- model => view --> {{expression}} [target]="expression" bind-target="expression" <p> {{MSG}} </p> // Interpolation <img [SRC]="heroImageUrl"> // attribute binding <app-hero-detail [hero]="currentHero"></app-hero-detail> // Component passes parameter <div by attribute binding [ngClass]="{'special': <div [class. Special]="isSpecial"> special </div> // class <div [style. Color]="isSpecial? 'red' : 'green'"> // style binding <button [attr.aria-label]="help">help</button> // Attribute binding <! -- view => model --> (target)="statement" on-target="statement" <button (click)="onSave()">Save</button <app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail> <div (myClick)="clicked=$event" clickable>click me</div> // instruction event <! -- view <=> model --> [(target)]="expression" bindon-target="expression" <input [(ngModel)]="name"> // Two-way data bindingCopy the code

HTML Attribute vs. DOM Property

Understanding the difference between HTML and DOM attributes is key to understanding how Angular bindings work. Attributes are defined by HTML. Property is accessed from a DOM (Document Object Model) node

  • Some HTML attributes can map 1:1 to properties; For example, id.
  • Some HTML attributes do not have corresponding properties. For example, aria-* colSpan rowSpan.
  • Some DOM properties do not have corresponding attributes. For example, textContent.

It’s important to remember that HTML attributes and DOM properties are different, even if they have the same name. In Angular, the only function of HTML attributes is to initialize the state of elements and directives.

Template bindings use properties and events instead of attributes.

When you write a data binding, you’re just dealing with the DOM Property and events of the target object.

Note:

This general rule helps you model HTML attributes and DOM properties: The Attribute is responsible for initializing the DOM Attribute and then completing it. Property values can be changed; Attribute values do not.

There is one exception to this rule. The Attribute can be changed by setAttribute(), which then reinitializes the corresponding DOM Attribute.

Case 1: Input
<input type="text" value="Sarah">
Copy the code

When the browser renders the input, it creates a corresponding DOM node with its value Property initialized to “Sarah”.

When the user enters Sally in the input, the DOM element’s value Property changes to Sally. However, if you look at the HTML Attribute value using input.getAttribute(‘value’), you can see that the Attribute remains the same — it returns Sarah.

The HTML value attribute specifies the initial value; The value of the DOM is this property is the current value.

Case 2: Disable button

The Disabled Attribute is another example. The button’s Disabled Property defaults to false, so the button is enabled.

When you add the Disabled Attribute, its mere presence initializes the button’s Disabled Property to true, so the button is disabled.

<button disabled>Test Button</button>
Copy the code

Adding and removing the Disabled Attribute disables and enables the button. However, the value of the Attribute is irrelevant, which is why you can’t enable this button by writing it while still disabled.

To control the state of the button, set the Disabled Property,

<input [disabled]="condition ? true : false">
<input [attr.disabled]="condition ? 'disabled' : null">
Copy the code

Template/Interpolation {{}} (Basic, master)

In addition to binding variables, templates can also bind methods

Templates can also contain simple logic, such as judgments or calculations

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` < p > variable bindings: {{title}} < / p > < p > method binding: {{getVal}} < / p > < p > method binding: {{getVal2 ()}} < / p > < p > simple arithmetic {+ 1} {1}. < / p > < p > simple calculations. {{price * 0.7}} < / p > < p > simple operation: {{gender = = = 0? 'male' : 'female'}} < / p > < p > with the method combining with the {{price * 0.7 + getVal}}. < / p > < p > and method combined with {{price * 0.7 + getVal2 ()}}. < / p > `, }) export class AppComponent {title = "template binding"; price = 30; gender = 0; Get getVal(): number {//es6 new syntax, functions can be used as variables to return 20; } getVal2(): number { return 33; }}Copy the code

When using template expressions, follow these guidelines:

  • Very simple
  • Execute quickly
  • No visible side effects (i.e. logic in the template cannot change component variables)

Property binding (Basic, Master)

Binding pictures

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <img src=".. /assets/images/madao.jpg" Alt ="madao" /> <img [SRC]="madaoSrc" Alt ="madao" /> // Recommend <img bind-src="madaoSrc" Alt ="madao"  /> `, styles: [] }) export class AppComponent { madaoSrc = '.. /assets/images/madao.jpg'; }Copy the code

Bind common properties

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <img [src]="user.pic" [alt]="user.name" /> <table class="table-bordered"> <tr> <th>name</th> <th>phone</th> < th > age < / th > < / tr > < tr > < td > * * < / td > < td > 13398490594 < / td > < td > 33 < / td > < / tr > < tr > < td/colSpan = "colSpan" > and < / td > / / </td> </td> </tr> </table> <button class=" BTN bTN-primary" [disabled]="isDisabled">click</button> `, styles: [] }) export class AppComponent { madaoSrc = '.. /assets/images/madao.jpg'; user = { name: 'madao', pic: this.madaoSrc }; colSpan = 2; isDisabled = false; }Copy the code

Bind custom properties

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: '<span [attr.data-title]="customTitle"> a line of text </span> <span [attr.title]="customTitle">test title</span> <span [title]="customTitle">test title</span> `, styles: [] }) export class AppComponent { madaoSrc = '.. /assets/images/madao.jpg'; customTitle = 'bbb'; }Copy the code

Using interpolation (not recommended)

Interpolation can also be used for attributes, but the general practice is to use brackets [], which is recommended to maintain a uniform style throughout the project

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <img src="{{ user.pic }}" alt="{{ user.name }}" /> `, styles: [] }) export class AppComponent { madaoSrc = '.. /assets/images/madao.jpg'; user = { name: 'madao', pic: this.madaoSrc }; }Copy the code

Style binding (belongs to property binding, Basic, Master)

Bind a single style

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <button type="button" class="btn" [class.btn-primary]="theme === 'primary'">Primary</button> <button type="button" class="btn" [class.btn-secondary]="true">secondary</button> <button type="button" class="btn" [class.btn-success]="isSuccess">success</button> <button type="button" class="btn" [class.btn-danger]=" ">danger</button> <button type=" btn-danger "class=" 0"> Danger </button> //false <button type="button" class="btn" [class.btn-danger]="undefined">danger</button> //false `, styles: [] }) export class AppComponent { theme = 'primary'; isSuccess = true; }Copy the code

Bind multiple classes

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <button type="button" [class]="btnCls">btnCls</button> <button type="button" [class]="btnCls2">btnCls2</button> <button type="button" [class]="btnCls3">btnCls3</button> <! <button type="button" [ngClass]="btnCls"> </button> <button type="button" [ngClass]="btnCls2">btnCls2</button> <button type="button" [ngClass]="btnCls3">btnCls3</button> `, styles: [] }) export class AppComponent { btnCls = 'btn btn-primary'; btnCls2 = ['btn', 'btn-success']; btnCls3 = { btn: true, 'btn-info': true }; }Copy the code

Bind a single style

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` < p [style. Color] = "' # f60" > a paragraph < / p > < p/style. Height = "' 50 px"/style. Border = "' 1 px solid" > setting height < / p > < p [style.height.px]="50" [style.border]="'1px solid'"> Set height </p>, styles: []}) export Class AppComponent {}Copy the code

Bind multiple styles

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <p [style]="style1">style1</p> <p [style]="style2">style2</p> <p [style]="style3">style3</p> <! The built-in ngStyle directive can also be used, but it is not recommended and may be deprecated later --> <! -- <p [ngStyle]="style1">style1</p>--> <! -- <p [ngStyle]="style2">style2</p>--> <! --> <p [ngStyle]="style3">style3</p>, styles: []}) export Class AppComponent {style1 = 'width: 200px; height: 50px; text-align: center; border: 1px solid; '; style2 = ['width', '200px', 'height', '50px', 'text-align', 'center', 'border', '1px solid']; Style3 = {width: '200px', height: '50px', 'text-align': 'center', border: '1px solid'}; }Copy the code

Binding priority

  • The more specific a class or style binding is, the higher its priority
  • Binding always takes precedence over static properties

Event binding (Basic, Master)

Basic usage

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <button type="button" class="btn btn-primary" (click)="onClick()">Primary</button> `, styles: [] }) export class AppComponent { onClick() { console.log('onClick'); }}Copy the code

The event object

$event is the native event object

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <button type="button" class="btn btn-primary" (click)="onClick($event)">Primary</button> `, styles: [] }) export class AppComponent { onClick(event: MouseEvent) { console.log('onClick', event.target); Console. log((event.target as HTMLInputElement).value)}}Copy the code

Event capture or event bubbling

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <div style="width:200px; height:200px; background-color:red;" (click)="parentClick()"> <! --<div style="width:100px; height:100px; background-color:blue;" (click)="chilrenClick($event)"></div>--> <div style="width:100px; height:100px; background-color:blue;" (click)="$event.stopPropagation()"></div> // You can use some simple syntax in HTML </div> ', styles: [] }) export class AppComponent { parentClick() { console.log('parentClick'); } chilrenClick(event: MouseEvent) { event.stopPropagation(); // Prevent events from bubbling console.log('chilrenClick'); }}Copy the code

Input and output properties (mainly child to parent, through custom events)

The input attribute

Child components

import { Component, Input } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `<p>
               Today's item: {{item}}
             </p>`
})
export class ItemDetailComponent  {
  @Input() item: string;
}
Copy the code

The parent component

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
     <app-item-detail [item]="currentItem"></app-item-detail>
  `,
})
export class AppComponent {
  currentItem = 'Television';
}
Copy the code
Output properties
  • Customize an event with New EventEmitter();
  • Call EventEmitter. Emit (data) and pass in data.
  • The parent directive listens for custom events and receives data through the passed $event object.

Child components

import { Component, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-root', template: `<label>Add an item: <input #newItem></label> <button (click)="addNewItem(newItem.value)">Add to parent's list</button>`, }) export class ItemOutputComponent { @Output() newItemEvent = new EventEmitter<string>(); AddNewItem (value: string) {this.newitemEvent.emit (value); // Custom event trigger}}Copy the code

The parent component

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: '<app-item-output (newItemEvent)="addItem($event)"></app-item-output> }) export class AppComponent { items = ['item1', 'item2', 'item3', 'item4']; addItem(newItem: string) { this.items.push(newItem); }}Copy the code

Declare input and output properties in metadata

While it is possible to declare inputs and outputs in @directive and @Component metadata, aliases are not recommended

@input () and @output () can accept a single argument as an alias for a variable, so the parent component can only be bound with an alias

Child components

import { Component, Input, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'app-root', template: `<p> Today's item: {{item}} </p>` }) export class ItemDetailComponent { @Input('aliasItem') item: string; @Output('newItem') newItemEvent = new EventEmitter<string>(); addNewItem(value: string) { this.newItemEvent.emit(value); }}Copy the code

The parent component

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: '<app-item-detail [aliasItem]="currentItem" // note is the binding alias (newItem)="addItem($event)"></app-item-detail> }) export class AppComponent { currentItem = 'Television'; items = ['item1', 'item2', 'item3', 'item4']; addItem(newItem: string) { this.items.push(newItem); }}Copy the code
Input attributes must be bound with brackets []?

If the binding value is static, you don’t need []; In order to unify the style, use as much as possible []

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
     <app-item-detail item="static item"></app-item-detail>
  `,
})
export class AppComponent {
  // currentItem = 'Television';
}
Copy the code

Bidirectional binding (Basic, Master)

A prerequisite for

  • Component property binding
  • Component event binding
  • Input and output (parent-child component communication)

Basic bidirectional binding

Child components

import {Component, OnInit, ChangeDetectionStrategy, EventEmitter, Input, Output} from '@angular/core';
@Component({
  selector: 'app-sizer'.template: ` 
      
`
.styles: [].changeDetection: ChangeDetectionStrategy.OnPush }) export class SizerComponent implements OnInit { @Input() size: number | string; // To use the bidirectional binding syntax, the output variable name must be the input property name plus Change @Output() sizeChange = new EventEmitter<number> ();constructor() { } ngOnInit(): void{}dec() { this.resize(-1); } inc() { this.resize(+1); } resize(delta: number) { this.size = Math.min(40.Math.max(8, +this.size + delta)); this.sizeChange.emit(this.size); }}Copy the code

The parent component

import { Component } from '@angular/core';
@Component({
  selector: 'app-root'.template: ` 
       
      
Resizable Text
`
,})export class AppComponent { fontSizePx = 16; } Copy the code

How bidirectional binding works

For two-way data binding to work, the name of the @Output() property must follow the inputChange pattern, where input is the name of the corresponding @input () property. For example, if the @input () attribute is size, the @Output() attribute must be sizeChange.

The sizerComponent above has the value attribute size and the event attribute sizeChange. The size attribute is @input (), so data can flow into the sizerComponent. The sizeChange event is an @Output() that allows data to flow out of the sizerComponent into the parent.

In the example above, there are two methods, dec() for decreasing font size and inc() for increasing font size. Both methods use resize() to change the value of the size attribute within the min/Max constraint and emit an event with the new size value.

shorthand

Bidirectional binding syntax is shorthand for a combination of attribute binding and event binding

<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>
Copy the code

Bidirectional binding in forms

Because none of the native HTML elements follow the naming patterns for x values and xChange events, bidirectional binding with form elements requires NgModel

The basic use

Based on the previous basic bidirectional binding knowledge, [(ngModel)] syntax can be broken down into:

  • An input property named ngModel
  • An output property named ngModelChange

The prerequisite for using the [(ngModule)] bidirectional binding is to introduce the FormsModule into the module

import {Component} from '@angular/core'; @Component({ selector: 'example-app', template: ` <input [(ngModel)]="name" #ctrl="ngModel" required> <p>Value: {{ name }}</p> <p>Valid: {{ ctrl.valid }}</p> <button (click)="setValue()">Set value</button> `, }) export class SimpleNgModelComp { name: string = ''; setValue() { this.name = 'Nancy'; }}Copy the code
<input [value]="name" (input)="name = $event.target.value" />Copy the code
Use in forms

To use [(ngModel)] in the form, you need to do one of two things

  • Appends the name property to the control
  • Will ngModelOptions. Standalone set to true
<form>
    <input [(ngModel)]="value" name="name" />
    <input [(ngModel)]="value" [ngModelOptions]="{ standalone: true }" />
</form>
Copy the code

Note: Bidirectional data binding is used in the form, so there are a lot of knowledge points. This is only a brief understanding, and a special chapter will be published to discuss it later

Built-in commands

Loop instruction *ngFor (very basic, master)

Arr :string[] = [' arr ',' arr ']; trackByItems(index: number, item: Item): number { return item.id; } <div *ngFor="let item of arr; Let I =index" (click)='choseThis(item, I)'> {{item}} </div> //trackBy is usually used with long lists to reduce dom substitution times and improve performance. trackBy: trackByItems"> ({{item.id}}) {{item.name}} </div>Copy the code

*ngIf ngStyle ngClass ngSwitch (very basic)

isShow: Boolean = true; personState: number = 2; // Frequent switching is not recommended, <p *ngIf="isShow"> Command mode </p> // Infrequent switching is recommended <p [hidden]="isShow"> Command mode </p> // Frequent switching is recommended currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': ! this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px' }; <div [ngClass]="isSpecial ? 'special' : ">ngClass</div> <div [ngStyle]="currentStyles"> ngStyle </div [style.display]="isShow? <p [class.hidden]="isShow"> Class mode </p> // Match multiple cases of conditional rendering, Similar to vue's V-if/V-else -if/ V-else Display a case <div [ngSwitch] = 'personState'> <div *ngSwitchCase="1"> work </div> <div *ngSwitchCase="2"> eat </div> <div *ngSwitchDefault> sleep </div> </div>Copy the code

Two-way data Binding instruction [(ngModel)]

Import {FormsModule} from '@angular/forms'; import {FormsModule} from '@angular/forms'; Imports: [FormsModule] public name = "import "; <input [(ngModel)] =" name" type="text"> A better approach is to use reactive form binding <input bindon-change="name" type="text"> // Alternative // attribute binding + event binding = ngModel (important) <input [value]="name" (input)="name=$event.target.value" >Copy the code

Template reference variable

The basic use

Declare template reference variables with a pound sign (#) to get DOM elements, directives, components, TemplateRef, or Web Components.

import {Component} from '@angular/core'; @Component({ selector: 'app-tpl-var', template: ` <input #phone placeholder="phone number" /> <button (click)="callPhone(phone.value)">Call</button> `, }) export class TplVarComponent { constructor() { } callPhone(value: string) { console.log('callPhone', value); }}Copy the code

ref

Another way to write it is ref, and the next two ways are the same

<input #fax placeholder="fax number" />

<input ref-fax placeholder="fax number" />
Copy the code

Refer to the component

In the component section, you see how to get attributes and methods for child components. There are two methods: local variables and @viewChild().

import {Component} from '@angular/core';
@Component({
  selector: 'app-tpl-var',
  template: `
    <div class="demo-sec">
      <button class="btn btn-primary" (click)="sizer.inc()">app inc</button>
      <app-sizer [(size)]="size" #sizer></app-sizer>
      size: {{ size }}
    </div>
  `,
})
export class TplVarComponent {
  size = 16;
  constructor() { }
}
Copy the code

Input and output

The input attribute

Child components

import { Component, Input } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `<p>
               Today's item: {{item}}
             </p>`
})
export class ItemDetailComponent  {
  @Input() item: string;
}
Copy the code

The parent component

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
     <app-item-detail [item]="currentItem"></app-item-detail>
  `,
})
export class AppComponent {
  currentItem = 'Television';
}
Copy the code

Output properties

Child components

import { Component, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-root', template: `<label>Add an item: <input #newItem></label> <button (click)="addNewItem(newItem.value)">Add to parent's list</button>`, }) export class ItemOutputComponent { @Output() newItemEvent = new EventEmitter<string>(); addNewItem(value: string) { this.newItemEvent.emit(value); }}Copy the code

The parent component

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-item-output (newItemEvent)="addItem($event)"></app-item-output> `, }) export class AppComponent { items = ['item1', 'item2', 'item3', 'item4']; addItem(newItem: string) { this.items.push(newItem); }}Copy the code

Declare input and output properties in metadata

While it is possible to declare inputs and outputs in @directive and @Component metadata, aliases are not recommended.

@input () and @output () can accept a single argument as an alias for a variable, so the parent component can only bind the child component with an alias

import { Component, Input, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'app-root', template: `<p> Today's item: {{item}} </p>` }) export class ItemDetailComponent { @Input('aliasItem') item: string; // decorate the property with @Input() @Output('newItem') newItemEvent = new EventEmitter<string>(); addNewItem(value: string) { this.newItemEvent.emit(value); }}Copy the code

The parent component

import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-item-detail [aliasItem]="currentItem" (newItem)="addItem($event)"></app-item-detail> `, }) export class AppComponent { currentItem = 'Television'; items = ['item1', 'item2', 'item3', 'item4']; addItem(newItem: string) { this.items.push(newItem); }}Copy the code

Input attributes must be bound with brackets []?

If the value of the binding is static, there is no need for []

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
     <app-item-detail item="static item"></app-item-detail>
  `,
})
export class AppComponent {
  // currentItem = 'Television';
}
Copy the code

Piping (Basic, master)

Common pipe

1, uppercase letter conversion

str = 'Hello';
str1 = 'World';
<p>{{str | uppercase}}-{{str1 | lowercase}} </p>  //str:hello str1:WORLD
Copy the code

2. Date formatting (often used)

today = new Date(); < p > the time now is {{today | date: 'MM - dd yyyy - HH: MM: ss'}} < / p >Copy the code

In the following example, 3 represents the minimum number of decimal places, and 2-4 represents at least 2 decimal places and at most 4 decimal places.

Num = 125.156896; < p > num reserved four decimal value is: {{num | number: '3.2 4'}} < / p > / / 125.1569Copy the code

4. Currency conversion

count = 5; Price = 1.5; < p > quantity: {{count}} < / p > / / data: 5 < p > price: {{price}} < / p > / / price: 1.5 < p > the total price: {{(price * count) | currency: '$'}} < / p > / / price: RMB 7.5Copy the code

5. String interception

Name = 'only to you '; < p > {{name | slice: 2, 4}} < / p > / / what you saidCopy the code

6. Json formatting (sometimes you need to look at the data)

 <p>{{ { name: 'semlinker' } | json }}</p> // { "name": "semlinker" }
Copy the code

Custom pipes

1. Create a pipeline file

ng g pipe /piper/mypiper
Copy the code

2. Write your own logical transform in the pipe file

import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'multiple' }) export class MypiperPipe implements PipeTransform { transform(value: any, args? : any): any {//value: input value args: parameter if(! Args){// args = 1; } return value*args; }}Copy the code

Note: pipes (filters) generated from the command line are automatically declared globally; The parameters passed by the pipe are indicated after the ‘:’ colon