Angular5是一个非常强大的前端框架,学习曲线较陡,掌握了不同模块(主要指Component)之间的通信机制,就等于掌握了Angular5。下面详尽列举各种通信方式供您参考:
一、从Component代码到Template
1. 作为Html内容
使用场景:页面加载,自动显示title的值,字号为h3。
html标签,花括号里面的是模板表达式(template expression):
<h3>{{title}}</h3>
ts代码,写在Component类内部:
title = "前端编程"。
2. 作为DOM property的值
使用场景:页面加载,自动显示图片。
<img scr="{{imageUrl}}" style="height:30px">
ts代码:
imageUrl="http://xxx.xxx.com/1.png";
二、响应事件
1. 标准Html tag的标准事件
使用场景:如按钮的点击事件:
<button (click)="onSave($event)">保存</button>
ts代码:
onSave(event) {
....
}
三、父子Component间通信
1. 通过@Input绑定输入属性
使用场景:利用第三方Component展示某个对象的细节
Angular5不允许绑定到一个不同Component的属性,除非使用@Input或者(以后说)@Output显式地申明。
比如传递一个当前的收货地址到一个地址细节的Component:
<app-address-detail [address]="currentAddress"></app-address-detail>
ts代码:
@Input() address: Address;
2. 通过@ViewChild并且使用Component方式,父---->子
使用场景:页面一部分一个点击事件,导致另一部分产生变化
比如:点击一个按钮,显示点击次数,其中显示点击次数的逻辑由另外一个Component负责。
子Component代码:
@Component({
selector: 'app-number',
template: `<b>{{message}}</b>`
})
export class NumberComponent {
message: string = '';
count: number = 0;
increaseByOne() {
this.count = this.count + 1;
this.message = "次数: " + this.count;
}
父template:
<button type="button" (click)="increase()">增加</button>
<app-number></app-number>
父Component代码:
export class NumberParentComponent {
@ViewChild(NumberComponent)
private numberComponent: NumberComponent;
increase() {
this.numberComponent.increaseByOne();
}
}
3. @ViewChild和ElementRef,通过Directive
使用场景:点击页面一部分,另一部分颜色发生变化
需要获取Directive所在的整个元素(element),在Directive方法内部,修改那个元素的Dom属性,颜色。
子Directive代码:
@Directive({
selector: '[chColor]'
})
export class ChColorDirective implements AfterViewInit{
constructor(private elementRef: ElementRef) {
}
ngAfterViewInit(){
this.elementRef.nativeElement.style.color = 'green';
}
change(changedColor: String){
this.elementRef.nativeElement.style.color = changedColor;
}
}
父Template代码:
<p chColor>改变我的颜色</p>
<div>
修改颜色:
<input type="button" name="red" (click)="changeColor('red')"> 红色
</div>
4. @ViewChildren,通过Component
使用场景:比如删除一组子Component,每个子Com前面有checkbox。
父Component的Template:
<section>
<h4 *ngIf="todos.getAll().length">Todo列表</h4>
<todo-item *ngFor="let todo of todos.getAll()" [todo]="todo"></todo>
</section>
父component代码:
export class TodoAppComponent implements AfterViewInit {
@ViewChildren(TodoComponent) todoComponents: QueryList<TodoComponent>;
constructor(private todos: TodoList) {}
ngAfterViewInit() {
// viewChildren在这个地方变得可用
}
因为Angular的DOM编译器会先处理父Component,然后再处理children,这样在初始化的时候,todosComponent是未定义的,undefined。他们的值在ngAfterViewInit函数里面设置。比如把获取到的todosComponent编程数组再赋值给todos。
获得了子Component列表之后,就可以按照需求处理了。
5. 往一个模板里插入一块动态内容,单槽
使用场景:父Component往子Component里面插入一块Html
往什么地方插呢?这是由<ng-content>标签定义了插槽。
子Card Component的Template:
<div class="card">
<!--单槽插入点-->
<ng-content></ng-content>
</div>
父Component的Template:
<card>
<!--下面是动态内容-->
<div class="card-content">
<p>This is dynamic content</p>
</div>
</card>
6. 往一个模板里插入多块内容,多槽
使用场景:父Component往子Component里面插入多块Html内容,插入地点由selector匹配
子Card Component的Template:
<div>
<!--tag-->
<ng-content select="header"></ng-content>
<!--css 选择器-->
<ng-content select="div.body"></ng-content>
<!--attribute-->
<ng-content select="[card][body]"></ng-content>
<!--带值的attribute-->
<ng-content select="[card-type=body]"></ng-content>
</div>
父Component的Template:
<card>
<header>...</header>
<div class="body">我是body</div>
<div body card>...</div>
<div card-type="body">...</div>
</card>
7. 当Template涉及三个Components,@ContentChild
使用场景:在第二个children里面获取第三个Component信息
@Component({
selector: 'app-footer',
template: '<ng-content></ng-content>'
})
class FooterComponent{}
@Component(...)
class TodoAppComponent implements AfterContentInit {
@ContentChild(FooterComponent) footer: FooterComponent;
ngAfterContentInit() {
// this.footer now points to the instance of 'FooterComponent'
}
}
@Component({
selector: 'demo-app',
template: `
<content>
<todo-app>
<app-footer>
<small>Yet another todo app!</small>
</app-footer>
</todo-app>
</content>
`
})
export class AppComponent{}
这里AppComponent使用TodoAppComponent并在它的一对tag之间传递FooterComponent给它,我们称FooterComponent是TodoAppComponent的content child。
8. 内层Component向父Component发事件
使用场景:父Component删除子Component,但是删除操作在子Component上,比如一个按钮。
子Component暴露一个EventEmitter为属性,然后绑定到父Component的一个函数,绑定之后,父Component就开始侦听这个事件了。
子Component:
@Component({
template: `
<div>
<button (click)="delete()">删除</button>
</div>`
})
export class HeroDetailComponent{
// this component makes a request but it can't actually delete a hero
deleteRequest = new EventEmitter<Hero>();
delete() {
this.deleteRequest.emit(this.hero);
}
}
父Component的Template:
<app-hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero">
当子Component触发事件时,Angular调用父Component的deleteHero方法,把要传递的hero作为$event变量传递出来。
四、同一个Component,修改Html另一部分的Native属性
1. 通过@ViewChild和模板引用变量访问Native Element
使用场景:获取一行子Html并修改该Html属性,颜色
Template代码:
<div>
姓名:<input type="text" #name><br/>
</div>
ts代码:
export class ExampleComponent implements AfterViewInit {
@ViewChild('name')
private name: ElementRef;
ngAfterViewInit() {
this.name.nativeElement.style.color = 'red';
}
}
五、非父子Component间通信
1. 通过路由传递参数(Observable)
使用场景:地址列表,还有两个按钮(上一个,下一个地址)浏览历史地址,不希望每切换一个地址都重新创建一个新的地址Component,而是复用一个地址Component。
当router创建新的Component时,会调用ngOnInit()函数,这样写:
this.address = this.route.paramMap
.switchMap(params: ParamMap) =>
this.service.getAddress(params.get('id')));
这里this.service.getAddress返回一个Observable<Address>对象。
2. 通过路由传递参数(非Observable)
使用场景:地址列表,从列表中每选择一个,切换为该地址的详细信息,如果想显示另外一个地址的详细信息,必须先回到列表页面;这页意味着每选择一个地址都重新创建一个新的地址Component实例。
let id = this.route.snapshot.paramMap.get('id');
this.address = this.service.getAddress(id);
3. 复杂参数通过可选参数传递(Optional Parameters)
使用场景:需要传递参数的场景太多,不可能每一个参数都对应一个route,如果那样的话路由配置会相当复杂,这时候可选参数就派上用场了,不需要更改路由配置。
观察地址栏,可选参数不同于路由参数,也不是查询参数,而是矩阵参数,矩阵参数也是一个标准,并非angular发明的,形式如下:
;id=15;foo=foo
发送端ts代码:
this.router.navigate(['/list', {id: addressId, foo: 'foo'}]);
接收端还是通过ActivatedRoute获取地址Id:
this.addresses = this.route.paramMap
.switchMap(params: ParamMap) =>
// (+) before 'params.get()' turns the string into a number
this.selectedId = +params.get('id');
return this.service.getAddresses();
});
六、DOM和Directive之间的通信
1. Directive更改Host元素的外观和行为,@HostListener
使用场景:一个Html tag,比如p,应用了一个Directive,当用户鼠标移到这个p上面,高亮这个p元素;鼠标移开,取消高亮。
分析这个需求,要求Directive能监听到Host元素的事件(Hover, Leave等),这是@HostListener的作用,把@HostListener放到某个函数前面,当事件发生时就会调用这个函数。
Template:
<p appHighlight>点亮我</p>
Directive代码:
@HostListener('mouseenter') onmouseenter() {
this.hightlight('yellow');
}
@HostListener('mouseleave') onmouseleave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
以上设置背景颜色的方式假定了我们的程序运行在浏览器环境下。但是Angular瞄准的是跨平台,所以Angular提供了一种平台独立的方式来设置因素的属性,Renderer2正是用于这种目的。
this.render.setStyle(el.nativeElement, 'width', '200px');
2. @HostBinding更改Host元素属性
使用场景:更改Host元素的外观或者行为,通过把Directive内部的属性和Host元素的属性绑定起来,实现了,内部属性一发生变化,Host元素相应的属性就会发生变化。
Directive代码:
@HostBinding('class.card-outline')private isHovering: boolean;
然后在Directive内部的事件处理函数中更改isHovering的值,从而间接改变Host元素的属性。
3. Resolver
使用场景:由一个Component导航到另外一个Component,可能要事先到服务器取出数据,如果数据存在,则继续导航;如果不存在,则取消导航,Resolver就是取数据的。
路由配置:
path: 'crisis-center',
component: CrisisCenterComponent,
children: [
{
path: '',
Component: CrisisListComponent,
children: [
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard],
resolve: {
crisis: CrisisDetailResolver
}
}
}
];