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";
}
}
4.2 Property Binding
4.2.1 Nornmal Property
属性绑定分为两种情况,绑定 DOM 对象属性和绑定HTML标记属性。
- 使用
[属性名称]
为元素绑定 DOM 对象属性。
- 使用
[attr.属性名称]
为元素绑定 HTML 标记属性
在大多数情况下,DOM 对象属性和 HTML 标记属性是对应的关系,所以使用第一种情况。 但是某些属性只有HTML 标记存在,DOM 对象中不存在,此时需要使用第二种情况,比如 colspan 属性,在DOM 对象中就没有,或者自定义 HTML属性也需要使用第二种情况。
4.2.2 Class Property
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。 -->
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");
}
}
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);
}
}
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 装饰器获取一个元素
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 代码
}
使用 ViewChildren 获取一组元素
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;
}
_
: 自由属性, 不允许被直接访问, 需要使用它里面的方法
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>
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>
如果只有一个ng-content,不需要select属性.
ng-content
在浏览器中会被 <div class="a"></div>
替代,如果不想要这个额外的div,可以使用ng-container替代这个div。
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
:
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>
4.8 Global Styles
npm install bootstrap
/* method1 in the css file */
@import '~bootstrap/dist/css/bootstrap.css';
/* ~ relative to node_modules folder */
<!-- 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>
method3 in theangular.json
file: