Dart和Angular2的英雄之旅(4):多个组件
我们的应用成长了不少。现在又有新的任务:重复使用组件,传递数据给组件,并创建更多可复用的资源。 我们来把英雄详情从英雄列表中拆分出来,让这个英雄详情组件可以被复用。
我们离开的地方
在继续《英雄之旅》之前,先来检查一下是否有以下目录结构。如果不是,你得回到上一章,并看看错过了什么。
保持应用的编译和运行
我们需要启动Dart编译器,它会监视文件变更,并且启动服务器。我们只要敲入:
pub serve
在我们继续构建《英雄之旅》的时候,这个命令会让应用得以持续运行。
制作英雄详细组件
我们的英雄列表和英雄详情目前位于同一个文件的同一个组件中。现在它们还很小,但很快它们都会变大。我们将来肯定会收到新需求:针对这一个,却不能影响另一个。然而,每一个更改都会给这两个组件带来风险,并且带来双倍的测试负担,却没有任何好处。如果我们不得不在本应用之外复用英雄详情组件,那么英雄列表组件也会跟着混进去。
目前,我们这个组件违反了单一职责原则(Single Responsibility Principle)。虽然这只是一个教程,但我们还是得坚持做正确的事——做正确的事这么容易,我们何乐而不为呢?况且,我们学习的就是如何解决构建 Angular 应用过程中的问题。
让我们把英雄详情拆分成一个独立的组件。
拆分英雄详情组件
在 lib 目录下添加一个名叫 hero_detail_component.dart 的文件,并且创建 HeroDetailComponent 。代码如下:
import 'package:angular2/core.dart';
@Component(
selector: 'my-hero-detail',
)
class HeroDetailComponent {
}
我们希望一眼就能看出哪个类是组件,以及哪个文件包含组件。
你会注意到,在名叫 app_component.dart 的文件中有一个 AppComponent 组件。并且,在名叫 hero_detail_component.dart 的文件中有一个 HeroDetailComponent 组件。
我们的所有组件名都以 Component 结尾。所有组件的文件名都以 _component 结尾。
这里我们统一使用小写的 下划线命名法 ( underscore case ,也叫 snake_case ) 拼写文件名。在服务器或源代码控制系统中,这样就不必担心大小写问题。
当我们创建组件的时候,一开始要导入 Angular 的 core.dart 文件,以便能使用 @Component 等通用类型。
我们使用注解 @Component 创建元数据,在其中指定选择器名称,以标记此组件对应的元素。
做完这些,我们把它导入 AppComponent 组件,并创建相应的 <my-hero-detail> 元素。
英雄详情模板
目前, AppComponent 的“英雄列表”和“英雄详情”视图被组合在同一个模板中。让我们从 AppComponent 中剪切英雄详情的内容,并且粘贴到 HeroDetailComponent 组件的 template 属性中。
以前我们绑定了 AppComponent 的 selectedHero.name 属性。 HeroDetailComponent 组件将会有一个 hero 属性,而不是 selectedHero 属性。 所以,我们要把模板中的所有 selectedHero 替换为 hero 。只改这些就够了。 最终结果如下所示:
template: '''
<div *ngIf="hero != null">
<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 类还在 app_component.dart 文件中。我们有了两个组件,它们都有各自的文件,并且都需要引用 Hero 类。
要解决这个问题,我们得从 app_component.dart 文件中把 Hero 类移到属于它自己的 hero.dart 文件中。
class Hero {
final int id;
String name;
Hero(this.id, this.name);
}
然后在 app_component.dart 和 hero_detail_component.dart 的顶部附近添加下列 import 声明语句:
import 'hero.dart';
英雄是一个输入属性
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;
刷新 AppComponent
我们回到 AppComponent,要教它如何使用 HeroDetailComponent 组件。
我们先导入 HeroDetailComponent 组件,以便可以引用它。
import 'hero_detail_component.dart';
在模板中,我们找到刚刚删除“英雄详情”的位置,添加表示 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 接收到英雄数据,并在列表下方显示那个英雄的详情。
现在还什么都没有发生!
我们点击列表内的英雄,并没有详细信息!我们在浏览器开发工具的控制台中找找看错误信息!但并没有出错。
似乎是Angular忽略了新的标签。确实如此!
指令列表
浏览器会忽略它无法识别的标签和特性(Attribute),Angular同样如此。
我们已经导入 HeroDetailComponent ,并在模板中使用它,但是我们并没有告诉Angular。
我们可以通过元数据的 directives 列表告诉 Angular 我们在模板中使用了哪些组件。让我们在配置对象 @Component 中, template 和 styles 的后面,直接添加列表属性。
directives: const [HeroDetailComponent]
搞定!
当在浏览器中查看应用时,我们看到了英雄列表。当选中一个英雄时,还能看到所选英雄的详情。
从根本上的改变是,我们可以在应用中的任何地方使用这个 HeroDetailComponent 组件来显示英雄详情了。
我们创建了第一个可复用组件!
回顾应用结构
让我们来检查一下。在本章中,经过这些漂亮的重构,我们应该得到了下列结构:
我们已经走过的路
我们来盘点一下已经构建的内容:
- 我们创建了一个可复用组件
- 我们学会了如何让一个组件接收输入
- 我们学会了把父组件绑定到子组件
- 我们学会了在 directives 列表中声明我们需要的指令
前方的路
通过抽取共享组件,我们的《英雄之旅》变得更有复用性了。
但在 AppComponent 中,我们仍然使用着 mock 数据。显然,这种方式并不是可持续的。 我们应该将“数据访问”重构为一个独立的服务,并在需要数据的组件中共享它。
在 下一篇教程 中 ,我们将学习如何创建服务。
本文出自“Dart语言中文社区”,允许转载,转载时请务必以超链接形式标明文章原始出处
本文地址:http://www.cndartlang.com/990.html
评论列表(5条)
花了2天时间看完全部文档,写的好,node.js、angular、typescript都学过,感觉语言之间都大同小异,最近研究flutter,碰巧学习一下,好久没有更新了啊,您主要是做什么技术的啊
不错哦
不错哦
花了2天时间看完全部文档,写的好,node.js、angular、typescript都学过,感觉语言之间都大同小异,最近研究flutter,碰巧学习一下,好久没有更新了啊,您主要是做什么技术的啊
珠海路美食广场风味小吃值得一去123