我们把主从结构的页面重构成多个组件
Posted 不是玩的
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我们把主从结构的页面重构成多个组件相关的知识,希望对你有一定的参考价值。
多个组件
我们的应用正在成长中。现在又有新的用例:重复使用组件,传递数据给组件并创建更多可复用。 我们来把英雄详情从英雄列表中分离出来,让这个英雄详情组件可以被复用。
首先老规矩,我们得让我们的代码运行起来:
让应用代码保持转译和运行
我们要启动 TypeScript 编译器,它会监视文件变更,并启动开发服务器。只要敲:
npm start
这个命令会在我们构建《英雄指南》的时候让应用得以持续运行。
制作英雄详情组件
目前,英雄列表和英雄详情位于同一个文件的同一个组件中。 它们现在还很小,但很快它们都会长大。 我们将来肯定会收到新需求:针对这一个,却不能影响另一个。 然而,每一次更改都会给这两个组件带来风险和双倍的测试负担,却没有任何好处。 如果我们需要在应用的其它地方复用英雄详情组件,英雄列表组件也会跟着混进去。
我们当前的组件违反了单一职责原则。 虽然这只是一个教程,但我们还是得坚持做正确的事 — 况且,做正确的事这么容易,在此过程中,我们又能学习如何构建 Angular 应用。
我们来把英雄详情拆分成一个独立的组件。
拆分英雄详情组件
在app
目录下添加一个名叫hero-detail.component.ts
的文件,并且创建HeroDetailComponent
。代码如下:
import {Component,Input } from \'@angular/core\';
@Component({
selector:"my-hero-detail",
})
export class HeroDetailComponent{
}
命名约定
我们希望一眼就能看出哪些类是组件,哪些文件包含组件。
你会注意到,在名叫app.component.ts
的文件中有一个AppComponent
组件,在名叫hero-detail.component.ts
的文件中有一个HeroDetailComponent
组件。
我们的所有组件名都以Component
结尾。所有组件的文件名都以.component
结尾。
这里我们使用小写中线命名法 (也叫烤串命名法)拼写文件名, 所以不用担心它在服务器或者版本控制系统中出现大小写问题。
我们先从 Angular 中导入Component
和Input
装饰器,因为马上就会用到它们。
我们使用@Component
装饰器创建元数据。在元数据中,我们指定选择器的名字,用以标识此组件的元素。 然后,我们导出这个类,以便其它组件可以使用它
做完这些,我们把它导入AppComponent
组件,并创建相应的<my-hero-detail>
元素。
英雄详情模板
此时,AppComponent
的英雄列表和英雄详情视图被组合进同一个模板中。 让我们从AppComponent
中剪切出英雄详情的内容,并且粘贴到HeroDetailComponent
组件的template
属性中。
之前我们绑定了AppComponent
的selectedHero.name
属性。 HeroDetailComponent
组件将会有一个hero
属性,而不是selectedHero
属性。 所以,我们要把模板中的所有selectedHero
替换为hero
。只改这些就够了。 最终结果如下所示:
template: ` <div *ngIf="hero"> <h2>{{hero.name}} details!</h2> <div><label>id: </label>{{hero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="hero.name" placeholder="name"/> </div> </div> `
现在,我们的英雄详情布局只存在于HeroDetailComponent
组件中。
添加 HERO 属性
把刚刚所说的hero
属性添加到组件类。
hero: Hero;
我们声明hero
属性是Hero
类型,但是我们的Hero
类还在app.component.ts
文件中。 我们有了两个组件,它们位于各自的文件,并且都需要引用Hero
类。
要解决这个问题,我们从app.component.ts
文件中把Hero
类移到属于它自己的hero.ts
文件中。
app/hero.ts
export class Hero {
id: number;
name: string;
}
我们从hero.ts
中导出Hero
类,因为我们要从两个组件文件中引用它。 在app.component.ts
和hero-detail.component.ts
的顶部添加下列 import 语句:
import {Hero} from \'./hero\';
HERO属性是一个输入属性
还得告诉HeroDetailComponent
显示哪个英雄。谁告诉它呢?自然是父组件AppComponent
了!
AppComponent
确实知道该显示哪个英雄:用户从列表中选中的那个。 用户选择的英雄在它的selectedHero
属性中。
我们马上升级AppComponent
的模板,把该组件的selectedHero
属性绑定到HeroDetailComponent
组件的hero
属性上。 绑定看起来可能是这样的:
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
注意,hero
是属性绑定的目标 — 它位于等号 (=) 左边方括号中。
Angular 希望我们把目标属性声明为组件的输入属性,否则,Angular 会拒绝绑定,并抛出错误。
我们在这里详细解释了输入属性,以及为什么目标属性需要这样的特殊待遇,而源属性却不需要。
我们有几种方式把hero
声明成输入属性。 这里我们采用首选的方式:使用我们前面导入的@Input
装饰器向hero
属性添加注解。
@Input() hero: Hero;
更新 AppModule
回到应用的根模块AppModule
,让它使用HeroDetailComponent
组件。
我们先导入HeroDetailComponent
组件,后面好引用它。
import { HeroDetailComponent } from \'./hero-detail.component\';
接下来,添加HeroDetailComponent
到NgModule
装饰器中的declarations
数组。 这个数组包含了所有由我们创建的并属于应用模块的组件、管道和指令。
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
HeroDetailComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
更新 AppComponent
现在,应用知道了我们的HeroDetailComponent
, 找到我们刚刚从模板中移除英雄详情的地方, 放上用来表示HeroDetailComponent
组件的元素标签。
<my-hero-detail></my-hero-detail>
这两个组件目前还不能协同工作,直到我们把AppComponent
组件的selectedHero
属性和HeroDetailComponent
组件的hero
属性绑定在一起,就像这样:
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
AppComponent的模板是这样的:
template: ` <h1>{{title}}</h1> <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <my-hero-detail [hero]="selectedHero"></my-hero-detail> `,
感谢数据绑定机制,HeroDetailComponent
应该能接收来自AppComponent
的英雄数据,并在列表下方显示英雄的详情。 每当用户选中一个新的英雄时,详情信息应该随之更新。
搞定!
当在浏览器中查看应用时,可以看到英雄列表。 当选中一个英雄时,可以看到所选英雄的详情。
值得关注的进步是:我们可以在应用中的任何地方使用这个HeroDetailComponent
组件来显示英雄详情。
我们创建了第一个可复用组件!
完整的代码文件如下
app/hero-detail.component.ts
import { Component, Input } from \'@angular/core\'; import { Hero } from \'./hero\'; @Component({ selector: \'my-hero-detail\', template: ` <div *ngIf="hero"> <h2>{{hero.name}} details!</h2> <div><label>id: </label>{{hero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="hero.name" placeholder="name"/> </div> </div> ` }) export class HeroDetailComponent { @Input() hero: Hero; }
app/app.component.ts
import { Component } from \'@angular/core\'; import { Hero } from \'./hero\'; const HEROES: Hero[] = [ { id: 11, name: \'Mr. Nice\' }, { id: 12, name: \'Narco\' }, { id: 13, name: \'Bombasto\' }, { id: 14, name: \'Celeritas\' }, { id: 15, name: \'Magneta\' }, { id: 16, name: \'RubberMan\' }, { id: 17, name: \'Dynama\' }, { id: 18, name: \'Dr IQ\' }, { id: 19, name: \'Magma\' }, { id: 20, name: \'Tornado\' } ]; @Component({ selector: \'my-app\', template: ` <h1>{{title}}</h1> <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> <my-hero-detail [hero]="selectedHero"></my-hero-detail> `, styles: [` .selected { background-color: #CFD8DC !important; color: white; } .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: .3em 0; height: 1.6em; border-radius: 4px; } .heroes li.selected:hover { background-color: #BBD8DC !important; color: white; } .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } .heroes .text { position: relative; top: -3px; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; } `] }) export class AppComponent { title = \'Tour of Heroes\'; heroes = HEROES; selectedHero: Hero; onSelect(hero: Hero): void { this.selectedHero = hero; } }
app/hero.ts
export class Hero {
id: number;
name: string;
}
app/app.module.ts
import { NgModule } from \'@angular/core\';
import { BrowserModule } from \'@angular/platform-browser\';
import { FormsModule } from \'@angular/forms\';
import { AppComponent } from \'./app.component\';
import { HeroDetailComponent } from \'./hero-detail.component\';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
HeroDetailComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
走过的路
来盘点一下我们已经构建了什么。
-
我们创建了一个可复用组件
-
我们学会了如何让一个组件接收输入
-
我们学会了在 Angular 模块中声明该应用所需的指令。 只要把这些指令列在
NgModule
装饰器的declarations
数组中就可以了。 -
我们学会了把父组件绑定到子组件。
前方的路
通过抽取共享组件,我们的《英雄指南》变得更有复用性了。
在AppComponent
中,我们仍然使用着模拟数据。 显然,这种方式不能“可持续发展”。 我们要把数据访问逻辑抽取到一个独立的服务中,并在需要数据的组件之间共享。
在下一步,我们将学习如何创建服务。
以上是关于我们把主从结构的页面重构成多个组件的主要内容,如果未能解决你的问题,请参考以下文章