list/detail 是重要的 UI 设计模式。
vscode terminal 运行如下命令生成 detail 组件:
PS D:\Angular\my-app> ng generate component pokemon-base/pokemon-detail --module=pokemon-base/pokemon-base.module.ts
CREATE src/app/pokemon-base/pokemon-detail/pokemon-detail.component.html (29 bytes)
CREATE src/app/pokemon-base/pokemon-detail/pokemon-detail.component.spec.ts (649 bytes)
CREATE src/app/pokemon-base/pokemon-detail/pokemon-detail.component.ts (306 bytes)
CREATE src/app/pokemon-base/pokemon-detail/pokemon-detail.component.css (0 bytes)
UPDATE src/app/pokemon-base/pokemon-base.module.ts (428 bytes)
也可以使用通过安装 Angular Files 扩展生成上面的 detail 组件:
安装扩展后可以直接使用右键菜单命令生成组件:
工程文件结构:
然后修改 pokemon-base.module.ts
:
@NgModule({
declarations: [PokemonListComponent, PokemonDetailComponent],
imports: [CommonModule],
// 增加 PokemonDetailComponent
exports: [PokemonListComponent, PokemonDetailComponent],
})
export class PokemonBaseModule {}
工程中现在有两个新生成的组件,pokemon-list
和 pokemon-detail
,其中,pokemon-list
是 smart 组件,pokemon-detail
是 dumm 组件。
smart 组件总是向下传递数据给 dumm 组件,smart 组件之所以称为 smart ,是因为它能从数据库获得数据,相对而言,dumm 组件不会访问数据库,它只能从 smart 组件那里接收数据。
接下来将 app.component.ts
中的数据移至更合适的地方,通常来说,app.component.ts
中直接存放数据不是最佳的设计方式。虽然在某些情况下可能需要在 app
组件中保留关键数据,但一般来说, app
中的其他代码能移则移,移到其他更合适的组件中,以提高代码的可维护性和可扩展性。
app.component.ts
:
export class AppComponent {
constructor() {}
// 删除!!!
// pokemons: Pokemon[] = [
// // Pokemon: 精灵宝可梦
// {
// id: 1,
// name: 'pikachu', // 皮卡丘
// type: 'electric',
// isCool: false,
// isStylish: true,
// },
// {
// id: 2,
// name: 'squirtle', // 杰尼龟
// type: 'water',
// isCool: true,
// isStylish: true,
// },
// {
// id: 3,
// name: 'charmander', // 小火龙
// type: 'fire',
// isCool: true,
// isStylish: false,
// },
// ];
}
将数据粘贴到 pokemon-list.component.ts
,模拟从数据库中获取的数据:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-pokemon-list',
templateUrl: './pokemon-list.component.html',
styleUrls: ['./pokemon-list.component.css'],
})
export class PokemonListComponent implements OnInit {
// 从 app.component.ts 里剪切过来的数据
pokemons: Pokemon[] = [
// pokemon: 精灵宝可梦
{
id: 1,
name: 'pikachu', // 皮卡丘
type: 'electric',
isCool: false,
isStylish: true,
},
{
id: 2,
name: 'squirtle', // 杰尼龟
type: 'water',
isCool: true,
isStylish: true,
},
{
id: 3,
name: 'charmander', // 小火龙
type: 'fire',
isCool: true,
isStylish: false,
},
];
constructor() {}
ngOnInit(): void {}
}
在 app
文件夹下新建文件夹models
以及新文件 pokemon.ts
, 将用于类型检查的 Pokemon
interface 移到此文件中,以后项目中的其他类也可以使用此 interface,这样能够避免代码重复。
pokemon.ts
,
export interface Pokemon {
id: number;
name: string;
type: string;
isCool: boolean;
isStylish: boolean;
}
相应地,pokemon-list.component.ts
中增加 import { Pokemon } from 'src/app/models/pokemon';
以使用此 interface.
pokemon-list.component.html
:
<table>
<thead>
<th>Name</th>
<th>Index</th>
</thead>
<tbody>
<tr *ngFor="let pokemon of pokemons; let i = index">
<td class="pokemon-td" [class.cool-bool]="pokemon.isCool">
{{ i }} {{ pokemon.name }}
</td>
</tr>
<tr *ngFor="let pokemon of pokemons; let i = index">
<td class="pokemon-td" [ngClass]="{ 'cool-bool': pokemon.isCool }">
{{ i }} {{ pokemon.name }}
</td>
</tr>
<tr *ngFor="let pokemon of pokemons; let i = index">
<td
class="pokemon-td"
[style.backgroundColor]="pokemon.isStylish ? '#800080' : ''"
>
{{ i }} {{ pokemon.name }}
</td>
</tr>
<tr *ngFor="let pokemon of pokemons; let i = index">
<td
class="pokemon-td"
[ngStyle]="{ 'backgroundColor': (pokemon.isStylish ? '#800080' : '') }"
>
{{ i }} {{ pokemon.name }}
</td>
</tr>
</tbody>
</table>
运行 ng serve
, 可看到如下界面:
这一步要做的是迭代 pokemons
数组,将每一个 pokemon
传给 detail
:
修改 pokemon-detail.component.ts
:
import { Component, Input, OnInit } from '@angular/core';
import { Pokemon } from 'src/app/models/pokemon';
@Component({
selector: 'app-pokemon-detail',
templateUrl: './pokemon-detail.component.html',
styleUrls: ['./pokemon-detail.component.css'],
})
export class PokemonDetailComponent implements OnInit {
// 增加以下两行代码:
@Input()
detail!: Pokemon; // add a ! - a bang or null operator or null coalescing
constructor() {}
ngOnInit(): void {}
}
重构 pokemon-list.component.html
:
<table>
<thead>
<th>Name</th>
<th>Index</th>
</thead>
<tbody>
<app-pokemon-detail
*ngFor="let pokemon of pokemons"
[detail]="pokemon"
></app-pokemon-detail>
</tbody>
</table>
pokemon-detail.component.html
:
<tr>
<td class="pokemon-td" [class.cool-bool]="detail.isCool">
{{ detail.id }} : {{ detail.name }}
{{ detail.isCool == true ? "is COOL" : "is NOT COOL" }}
</td>
</tr>
运行 ng serve
:
Input
装饰器用来标记一个类字段为输入属性,并提供配置元数据。input 属性与模板中的一个DOM属性绑定。在变更检测期间,Angular会自动用DOM属性的值更新数据属性。
(Decorator that marks a class field as an input property and supplies configuration metadata. The input property is bound to a DOM property in the template. During change detection, Angular automatically updates the data property with the DOM property’s value.)
pokemon-detail
是一个子组件,它被设计用于插入到一个拥有 detail
数据的父组件中。此 detail
数据通过被 @Input
装饰器标记为 input 的 detail
实例变量传递到 pokemon-detail
组件中。
用法:使用 input 装饰器标记实例变量,使父组件可以通过此变量传数据下来。
@Input()
detail: Pokemon;
父组件接下来将会使用子组件,并传数据给它。
<pokemon-detail [detail]="pokemon"></pokemon-detail>
父组件名为 pokemon
的实例变量含有数据,这些数据将传递到 pokemon-detail
组件中。