codecamp

Angular 英雄之旅-创建特性组件

主从组件

此刻,​HeroesComponent ​同时显示了英雄列表和所选英雄的详情。

把所有特性都放在同一个组件中,将会使应用“长大”后变得不可维护。你要把大型组件拆分成小一点的子组件,每个子组件都要集中精力处理某个特定的任务或工作流。

本页面中,你将迈出第一步 —— 把英雄详情移入一个独立的、可复用的 ​HeroDetailComponent​。

HeroesComponent ​将仅仅用来表示英雄列表。​HeroDetailComponent ​将用来表示所选英雄的详情。

要查看本页所讲的范例程序,参阅现场演练 / 下载范例

制作 HeroDetailComponent

使用 Angular CLI 生成一个名叫 ​hero-detail​ 的新组件。

ng generate component hero-detail

这个命令会做这些事:

  • 创建目录 ​src/app/hero-detail​。

在这个目录中会生成四个文件:

  • 作为组件样式的 CSS 文件。
  • 作为组件模板的 HTML 文件。
  • 存放组件类 ​HeroDetailComponent ​的 TypeScript 文件。
  • HeroDetailComponent ​类的测试文件。

该命令还会把 ​HeroDetailComponent ​添加到 ​src/app/app.module.ts​ 文件中 ​@NgModule​ 的 ​declarations ​列表中。

编写模板

从 ​HeroesComponent ​模板的底部把表示英雄详情的 HTML 代码剪切粘贴到所生成的 ​HeroDetailComponent ​模板中。

所粘贴的 HTML 引用了 ​selectedHero​。新的 ​HeroDetailComponent ​可以展示任意英雄,而不仅仅所选的。因此还要把模板中的所有 ​selectedHero ​替换为 ​hero​。

完工之后,​HeroDetailComponent ​的模板应该是这样的:

<div *ngIf="hero">

  <h2>{{hero.name | uppercase}} Details</h2>
  <div><span>id: </span>{{hero.id}}</div>
  <div>
    <label for="hero-name">Hero name: </label>
    <input id="hero-name" [(ngModel)]="hero.name" placeholder="name">
  </div>

</div>

添加 @Input() hero 属性

HeroDetailComponent ​模板中绑定了组件中的 ​hero ​属性,它的类型是 ​Hero​。

打开 ​HeroDetailComponent ​类文件,并导入 ​Hero ​符号。

import { Hero } from '../hero';

hero ​属性必须是一个带有 ​@Input()​ 装饰器的输入属性,因为外部的 ​HeroesComponent ​组件将会绑定到它。就像这样:

<app-hero-detail [hero]="selectedHero"></app-hero-detail>

修改 ​@angular/core​ 的导入语句,导入 ​Input ​符号。

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

添加一个带有 ​@Input()​ 装饰器的 ​hero ​属性。

@Input() hero?: Hero;

这就是你要对 ​HeroDetailComponent ​类做的唯一一项修改。没有其它属性,也没有展示逻辑。这个组件所做的只是通过 ​hero ​属性接收一个英雄对象,并显示它。

显示 HeroDetailComponent

HeroesComponent ​会自行显示英雄的详情,但后面我们要移除这部分。本节会指导你把这部分逻辑委派给 ​HeroDetailComponent​。

这两个组件将会具有父子关系。当用户从列表中选择了某个英雄时,父组件 ​HeroesComponent ​将通过把要显示的新英雄发送给子组件 ​HeroDetailComponent​,来控制子组件。

你不用修改 ​HeroesComponent​ ,但是要修改它的模板

修改 HeroesComponent 的模板

HeroDetailComponent ​的选择器是 ​'app-hero-detail'​。把 ​<app-hero-detail>​ 添加到 ​HeroesComponent ​模板的底部,以便把英雄详情的视图显示到那里。

把 ​HeroesComponent.selectedHero​ 绑定到该元素的 ​hero ​属性,就像这样。

<app-hero-detail [hero]="selectedHero"></app-hero-detail>

[hero]="selectedHero"​ 是 Angular 的属性绑定语法。

这是一种单向数据绑定。从 ​HeroesComponent ​的 ​selectedHero ​属性绑定到目标元素的 ​hero ​属性,并映射到了 ​HeroDetailComponent ​的 ​hero ​属性。

现在,当用户在列表中点击某个英雄时,​selectedHero ​就改变了。当 ​selectedHero ​改变时,属性绑定会修改 ​HeroDetailComponent ​的 ​hero ​属性,​HeroDetailComponent ​就会显示这个新的英雄。

修改后的 ​HeroesComponent ​的模板是这样的:

<h2>My Heroes</h2>

<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <button [class.selected]="hero === selectedHero" type="button" (click)="onSelect(hero)">
      <span class="badge">{{hero.id}}</span>
      <span class="name">{{hero.name}}</span>
    </button>
  </li>
</ul>

<app-hero-detail [hero]="selectedHero"></app-hero-detail>

浏览器刷新,应用又像以前一样开始工作了。

有哪些变化?

像以前一样,一旦用户点击了一个英雄的名字,该英雄的详情就显示在了英雄列表下方。现在,​HeroDetailComponent ​负责显示那些详情,而不再是 ​HeroesComponent​。

把原来的 ​HeroesComponent ​重构成两个组件带来了一些优点,无论是现在还是未来:

  1. 你通过缩减 ​HeroesComponent ​的职责缩小了该组件。
  2. 你可以把 ​HeroDetailComponent ​改进成一个功能丰富的英雄编辑器,而不用改动父组件 ​HeroesComponent​。
  3. 你可以改进 ​HeroesComponent​,而不用改动英雄详情视图。
  4. 将来你可以在其它组件的模板中重复使用 ​HeroDetailComponent​。

查看最终代码

下面是本页所提到的源代码。

  • src/app/hero-detail/hero-detail.component.ts
  • import { Component, OnInit, Input } from '@angular/core';
    import { Hero } from '../hero';
    
    @Component({
      selector: 'app-hero-detail',
      templateUrl: './hero-detail.component.html',
      styleUrls: ['./hero-detail.component.css']
    })
    export class HeroDetailComponent implements OnInit {
      @Input() hero?: Hero;
    
      constructor() { }
    
      ngOnInit(): void {
      }
    
    }
  • src/app/hero-detail/hero-detail.component.html
  • <div *ngIf="hero">
    
      <h2>{{hero.name | uppercase}} Details</h2>
      <div><span>id: </span>{{hero.id}}</div>
      <div>
        <label for="hero-name">Hero name: </label>
        <input id="hero-name" [(ngModel)]="hero.name" placeholder="name">
      </div>
    
    </div>
  • src/app/heroes/heroes.component.html
  • <h2>My Heroes</h2>
    
    <ul class="heroes">
      <li *ngFor="let hero of heroes">
        <button [class.selected]="hero === selectedHero" type="button" (click)="onSelect(hero)">
          <span class="badge">{{hero.id}}</span>
          <span class="name">{{hero.name}}</span>
        </button>
      </li>
    </ul>
    
    <app-hero-detail [hero]="selectedHero"></app-hero-detail>
  • src/app/app.module.ts
  • import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    import { AppComponent } from './app.component';
    import { HeroesComponent } from './heroes/heroes.component';
    import { HeroDetailComponent } from './hero-detail/hero-detail.component';
    
    @NgModule({
      declarations: [
        AppComponent,
        HeroesComponent,
        HeroDetailComponent
      ],
      imports: [
        BrowserModule,
        FormsModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }

小结

  • 你创建了一个独立的、可复用的 ​HeroDetailComponent ​组件。
  • 你用属性绑定语法来让父组件 ​HeroesComponent ​可以控制子组件 ​HeroDetailComponent​。
  • 你用 ​@Input​ 装饰器来让 ​hero ​属性可以在外部的 ​HeroesComponent ​中绑定。


Angular 英雄之旅-显示列表
Angular 英雄之旅-添加服务
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

Angular 开发指南

Angular 特性预览

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }