Skip to content

4 Component Template

4.1 Data Binding

数据绑定就是将组件类中的数据显示在组件模板中,当组件类中的数据发生变化时会自动被同步到组件模板中(数据驱动DOM)。

在 Angular 中使用差值表达式进行数据绑定,即 {{ }}大胡子语法。

<p>data-binding works!</p>
<div>{{ messgae }}</div>
<div>{{ getInfo }}</div>
<div>{{ htmlString }}</div>
<div [innerHTML]="htmlString"></div>
<div>{{ 1===1 ? "equal" : "not equal" }}</div>
<div> {{ "Hello Angular" }} </div>

src/app/components/data-binding/data-binding.component.ts

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

@Component({
  selector: 'app-data-binding',
  templateUrl: './data-binding.component.html',
  styleUrls: ['./data-binding.component.css']
})
export class DataBindingComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

  messgae: string = 'Hello Angular';
  htmlString: string = "<h1>I am from htmlString</h1>";
  getInfo() {
    return "I am from getInfo";
  }

}

Screenshot 2025-02-04 at 12.47.30

Screenshot 2025-02-04 at 12.48.06

4.2 Property Binding

4.2.1 Nornmal Property

属性绑定分为两种情况,绑定 DOM 对象属性和绑定HTML标记属性。

  1. 使用[属性名称] 为元素绑定 DOM 对象属性。
<img [src]=" imgUrl" />
  1. 使用[attr.属性名称]为元素绑定 HTML 标记属性
<td [attr.colspan]="colSpan"></td>

在大多数情况下,DOM 对象属性和 HTML 标记属性是对应的关系,所以使用第一种情况。 但是某些属性只有HTML 标记存在,DOM 对象中不存在,此时需要使用第二种情况,比如 colspan 属性,在DOM 对象中就没有,或者自定义 HTML属性也需要使用第二种情况。

Screenshot 2025-01-16 at 20.48.32

Screenshot 2025-01-16 at 20.48.15

4.2.2 Class Property

<div [class.active]="true"></div>

Screenshot 2025-01-16 at 23.07.31

Screenshot 2025-01-16 at 23.07.26

<div [class.active]="false"></div>

Screenshot 2025-01-16 at 23.08.45

Screenshot 2025-01-16 at 23.08.51

4.2.3 Styles Property

<button [style.backgroundColor]="isActive ? 'bule': 'red'">button </button>
<button [ngStyle]="{'backgroundColor': 'red'}">button</button>
<!-- ngStyle vs style.width 的区别 -->
<!-- ngStyle 可以同时设置多个样式,而style.width只能设置一个样式 -->
<div class="a" [class.active]="false"></div>

<div class="b" [ngClass]="{active: true, error:true}"> </div>
<!-- ngClass在Angular中是一个内置的指令,它可以根据表达式的值动态的增加或者删除一个或多个class。 -->
<!-- ngClass指令的值是一个对象,对象的key是class的名字,value是一个boolean值,如果为true则增button加这个class,如果为false则删除这个class。 -->

<div [style.width]="'200px'"></div>
<div [ngStyle]="{width:'200px', height:'200px', backgroundColor: 'pink'}"></div>
<!-- ngStyle指令的值是一个对象,对象的key是style的名字,value是一个字符串,这个字符串就是style的值。 -->
<!-- ngStyle在Angular中是一个内置的指令,它可以根据表达式的值动态的增加或者删除一个或多个style。 -->

Screenshot 2025-01-16 at 23.26.42

Screenshot 2025-01-16 at 23.26.19

Screenshot 2025-02-04 at 14.09.10

Screenshot 2025-02-04 at 14.09.20

Screenshot 2025-02-04 at 14.12.14

Screenshot 2025-02-04 at 14.12.05

4.3 Event Binding

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


// 组件类 被@Component装饰器装饰
@Component({

  // 制定组件的使用方式, 当前问标记形式
  // app-root => <app-root></app-root>
  selector: 'app-root', // 当前组件调用的时候你要以什么形式去调用
  templateUrl: './app.component.html', // 当前组件对应的模版是什么 组件模版文件路径
  styleUrls: ['./app.component.css'] // 当前组件对应的样式文件
})

// 导出一个类 
export class AppComponent {
  onClick(event: Event){
    console.log(event);
    // alert('Hello Angular');
    console.log(this);
  }

  onKeyup(){
    console.log("onKeyup");
  }
}
<button (click)="onClick($event)">button</button>
<input type="text" (keyup.enter)="onKeyup()"/>

Screenshot 2025-01-17 at 00.01.22

Screenshot 2025-01-17 at 00.01.31

Screenshot 2025-02-04 at 14.19.40

Screenshot 2025-02-04 at 14.21.03

4.4 Access Native DOM Objects

4.4.1 Access from component template

<p>access-native-dom-objects works!</p>
<button #btn (click)="onClick(btn)">Click me!</button>
<input type="text" (keyup.enter)="onKeyup(username.value)" #username />
import { Component, ElementRef, OnInit } from '@angular/core';

@Component({
  selector: 'app-access-native-dom-objects',
  templateUrl: './access-native-dom-objects.component.html',
  styleUrls: ['./access-native-dom-objects.component.css']
})
export class AccessNativeDomObjectsComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }
  // ElementRef 是 Angular 提供的一个服务,用于获取原生 DOM 对象
  // ElementRef 是一个泛型类,可以指定它的类型,这里指定为 HTMLButtonElement
  // 这样就可以获取到 button 元素的原生 DOM 对象


  onClick(button: HTMLButtonElement) {
    console.log('Button clicked:', button);
  }

  // 修正: onKeyup 方法名与 HTML 代码一致,并接受 string 类型的输入值
  onKeyup(inputValue: string) {
    console.log('Input value:', inputValue);
  }


}

Screenshot 2025-02-04 at 15.44.59

Screenshot 2025-02-04 at 15.46.31

Angular 12 类型检查较宽松,#btn 可能被推断为 any,不会报错。

Angular 14 默认启用 strictTemplates#btn 被推断为 HTMLButtonElement不能直接传递给 ElementRef<T>,因此会报错。

解决方案

  • 方法 1:直接传 HTMLButtonElement(适用于事件)
  • 方法 2:使用 @ViewChild() 获取 ElementRef<T>(适用于 ngOnInit 访问 DOM)

4.4.2 Access from component class 在组件类中获取

使用 ViewChild 装饰器获取一个元素

<p #paragraph>use ViewChild works</p>
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-access-native-dom-objects',
  templateUrl: './access-native-dom-objects.component.html',
  styleUrls: ['./access-native-dom-objects.component.css']
})
export class AccessNativeDomObjectsComponent implements AfterViewInit {

  // @ViewChild 装饰器用于获取模板中的元素
  // 通过模板变量名来获取元素
  // 这里获取到的是一个 ElementRef 对象
  // ElementRef 对象包含了原生 DOM 对象
  // 通过 nativeElement 属性可以获取到原生 DOM
  // 这里获取到的是一个 HTMLParagraphElement 对象
  // | 是 TypeScript 中的联合类型,表示 paragraph 可能是 ElementRef<HTMLParagraphElement> 类型,也可能是 undefined 类型
  // 这是因为 Angular 在初始化组件时,可能还没有渲染模板,所以获取不到元素
  @ViewChild("paragraph") paragraph:
    | ElementRef<HTMLParagraphElement>
    | undefined;

  // ngAfterViewInit 是 AfterViewInit 接口的方法
  // AfterViewInit 接口是 Angular 提供的一个生命周期钩子
  // 当组件的视图初始化完成后,Angular 会调用 ngAfterViewInit 方法
  ngAfterViewInit(): void {
    console.log(this.paragraph?.nativeElement as HTMLParagraphElement);
  }
  // nativeElement 属性是 ElementRef 类的一个属性
  // 它是一个只读属性,返回一个原生 DOM 对象
  // 这里获取到的是一个 HTMLParagraphElement 对象
  // as HTMLParagraphElement 是类型断言,将 paragraph.nativeElement 断言为 HTMLParagraphElement 类型
  // 这样就可以调用 HTMLParagraphElement 类的方法和属性
  // ? 是 TypeScript 中的可选链操作符,表示 paragraph 可能是 undefined 类型
  // 这是因为 paragraph 可能获取不到元素

  // 为什么显示 p, 一大串的 HTML 代码?
  // 因为 console.log 方法会将 HTMLParagraphElement 对象转换为字符串
  // 所以显示了一大串的 HTML 代码

}

Screenshot 2025-02-04 at 16.05.32

使用 ViewChildren 获取一组元素

<ul>
<li #items>a</li>
<li #items>b</li>
<li #items>c</li>
</ul>
import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';

@Component({
  selector: 'app-access-native-dom-objects',
  templateUrl: './access-native-dom-objects.component.html',
  styleUrls: ['./access-native-dom-objects.component.css']
})
export class AccessNativeDomObjectsComponent implements AfterViewInit {

  // ElementRef 是 Angular 提供的一个服务,用于获取原生 DOM 对象
  // ElementRef 是一个泛型类,可以指定它的类型,这里指定为 HTMLButtonElement
  // 这样就可以获取到 button 元素的原生 DOM 对象


  // onClick(button: HTMLButtonElement) {
  //   console.log('Button clicked:', button);
  // }

  // // 修正: onKeyup 方法名与 HTML 代码一致,并接受 string 类型的输入值
  // onKeyup(inputValue: string) {
  //   console.log('Input value:', inputValue);
  // }

  ///----------------------------------------------------------------------------------------------------------------

  // @ViewChild 装饰器用于获取模板中的元素
  // 通过模板变量名来获取元素
  // 这里获取到的是一个 ElementRef 对象
  // ElementRef 对象包含了原生 DOM 对象
  // 通过 nativeElement 属性可以获取到原生 DOM
  // 这里获取到的是一个 HTMLParagraphElement 对象
  // | 是 TypeScript 中的联合类型,表示 paragraph 可能是 ElementRef<HTMLParagraphElement> 类型,也可能是 undefined 类型
  // 这是因为 Angular 在初始化组件时,可能还没有渲染模板,所以获取不到元素
  @ViewChild("paragraph") paragraph:
    | ElementRef<HTMLParagraphElement>
    | undefined;

  // ngAfterViewInit 是 AfterViewInit 接口的方法
  // AfterViewInit 接口是 Angular 提供的一个生命周期钩子
  // 当组件的视图初始化完成后,Angular 会调用 ngAfterViewInit 方法
  ngAfterViewInit(): void {
    console.log(this.paragraph?.nativeElement as HTMLParagraphElement);
    console.log(this.items);
  }
  // nativeElement 属性是 ElementRef 类的一个属性
  // 它是一个只读属性,返回一个原生 DOM 对象
  // 这里获取到的是一个 HTMLParagraphElement 对象
  // as HTMLParagraphElement 是类型断言,将 paragraph.nativeElement 断言为 HTMLParagraphElement 类型
  // 这样就可以调用 HTMLParagraphElement 类的方法和属性
  // ? 是 TypeScript 中的可选链操作符,表示 paragraph 可能是 undefined 类型
  // 这是因为 paragraph 可能获取不到元素

  // 为什么显示 p, 一大串的 HTML 代码?
  // 因为 console.log 方法会将 HTMLParagraphElement 对象转换为字符串
  // 所以显示了一大串的 HTML 代码

  @ViewChildren("itmes") items: QueryList<ElementRef<HTMLLIElement>> | undefined;
}

Screenshot 2025-02-04 at 16.20.23

_: 自由属性, 不允许被直接访问, 需要使用它里面的方法

Screenshot 2025-02-04 at 16.27.24

Screenshot 2025-02-04 at 16.27.13

4.5 Two-Way Data Binding

数据在组件类和组件模版中双向同步.

Angular 将双向数据绑定功能放在了@angular/forms模块中, 所以要实现双向数据绑定需要依赖该模块.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SharedModule } from './shared/shared.module';
import { FormsModule } from '@angular/forms';

// 调用NgModule装饰器, 告诉Angular 当前类表示的是Angular模块
@NgModule({
  // 声明当前模块拥有哪些组建
  declarations: [
    AppComponent,
  ], 

  //声明当前模块依赖了哪些其他模块
  imports: [
    BrowserModule,
    AppRoutingModule,
    SharedModule,
    FormsModule
  ],

  // 声明服务的作用域, 数组中接收服务类, 表示该服务只能在当前模块的组件中使用
  providers: [],

  // 可引导组件, Angular 会在引导过程中把它加载到DOM中
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.html:

<input type="text" [(ngModel)]="username" />    
<button (click)="setData()">set Username</button>
<button (click)="getData()">get Username</button>

<div>{{username}}</div>

app.component.ts:

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


// 组件类 被@Component装饰器装饰
@Component({

  // 制定组件的使用方式, 当前问标记形式
  // app-root => <app-root></app-root>
  selector: 'app-root', // 当前组件调用的时候你要以什么形式去调用
  templateUrl: './app.component.html', // 当前组件对应的模版是什么 组件模版文件路径
  styleUrls: ['./app.component.css'] // 当前组件对应的样式文件
})

// 导出一个类 
export class AppComponent {
  username: string = "";

  setData() {
    this.username = "Hello Angular";
  }

  getData() {
    alert(this.username);
  }
}

// selector: '.app-root', // 当前组件调用的时候你要以什么形式去调用
// app-root => <div class="app-root"></div>
// selector: '[app-root]', // 当前组件调用的时候你要以什么形式去调用
// app-root => <div app-root></div>

Screenshot 2025-01-17 at 14.45.46

4.6 Content Projection

内容投影

在vue中叫做组件查找

在react中叫做修正

src/app/app.component.html:

<div>App works!</div>
<!-- <app-layout></app-layout>
<app-data-binding></app-data-binding> -->

<!-- <app-property-binding></app-property-binding> -->
<!-- <app-event-binding></app-event-binding> -->

<!-- <app-access-native-dom-objects></app-access-native-dom-objects> -->

<!-- <app-two-way-data-binding></app-two-way-data-binding> -->


<app-content-projection>
  <div class="a">a</div>
  <div class="b">b</div>
  <div class="c">c</div>
</app-content-projection>

src/app/components/component-template/content-projection/content-projection.component.html:

<p>content-projection works!</p>

<div>
    <ng-content select=".a">
    </ng-content>
</div>
<div>
    <ng-content select=".b">
    </ng-content>
</div>

Screenshot 2025-02-04 at 17.03.58

如果只有一个ng-content,不需要select属性.

Screenshot 2025-02-04 at 17.05.10

Screenshot 2025-02-04 at 17.05.18

ng-content在浏览器中会被 <div class="a"></div> 替代,如果不想要这个额外的div,可以使用ng-container替代这个div。

Screenshot 2025-02-04 at 17.07.09

Screenshot 2025-02-04 at 17.07.15

4.7 Error Handling in Data Binding

src/app/components/component-template/error-handling/error-handling.component.ts:

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

// Task是一个接口, 用于定义任务对象
// person是一个可选属性, 用于定义人员对象
// name是一个字符串类型的属性, 用于定义人员姓名
interface Task {
  person?: {
    name: string;
  }
}

@Component({
  selector: 'app-error-handling',
  templateUrl: './error-handling.component.html',
  styleUrls: ['./error-handling.component.css']
})
export class ErrorHandlingComponent {

  task: Task = {
    person: {
      name: 'Alice',
    }
  }
  // task是个对象, 有一个person属性, person属性是个对象, 有一个name属性, name属性是个字符串
  // task.person.name = 'Alice'

}

src/app/components/component-template/error-handling/error-handling.component.html:

<p>error-handling works!</p>
<div>{{ task.person.name }}</div>

Screenshot 2025-02-04 at 17.19.56

Screenshot 2025-02-04 at 17.20.05

Screenshot 2025-02-04 at 17.21.24

Screenshot 2025-02-04 at 17.21.31

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

// Task是一个接口, 用于定义任务对象
// person是一个可选属性, 用于定义人员对象
// name是一个字符串类型的属性, 用于定义人员姓名
interface Task {
  person?: {
    name: string;
  }
}

@Component({
  selector: 'app-error-handling',
  templateUrl: './error-handling.component.html',
  styleUrls: ['./error-handling.component.css']
})
export class ErrorHandlingComponent {

  task: Task = {
    // person: {
    //   name: 'Alice',
    // }
  }
  // task是个对象, 有一个person属性, person属性是个对象, 有一个name属性, name属性是个字符串
  // task.person.name = 'Alice'

}
<p>error-handling works!</p>
<!--method 1-->
<div *ngIf="task.person">{{ task.person.name }}</div>

<!--method 2-->
<div>{{ task.person?.name }}</div>

Screenshot 2025-02-04 at 17.22.10

Screenshot 2025-02-04 at 17.22.17

Screenshot 2025-02-04 at 17.22.56

Screenshot 2025-02-04 at 17.23.03

4.8 Global Styles

npm install bootstrap

Screenshot 2025-02-04 at 17.28.07

/* method1 in the css file */

@import '~bootstrap/dist/css/bootstrap.css';
/* ~ relative to node_modules folder */
<p>global-styles works!</p>

<button class="btn btn-primary">Primary</button>

Screenshot 2025-02-04 at 17.32.02

Screenshot 2025-02-04 at 17.32.13

<!-- method2 in the html file -->
<p>global-styles works!</p>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>

Screenshot 2025-02-04 at 17.33.35

Screenshot 2025-02-04 at 17.33.41

method3 in theangular.json file:

            "styles": [
              "src/styles.css",
              "./node_modules/bootstrap/dist/css/bootstrap.css"
            ],

Screenshot 2025-02-04 at 17.35.47

Screenshot 2025-02-04 at 17.35.57