组件
# 装饰器
@Component可以把一个类标记为 Angular 组件,常用元数据配置如下,此外还继承Directive,
| 选项 | 用法 |
|---|---|
| changeDetection | 更改变更策略 |
| templateUrl | 模板URL |
| template | 组件内模板 |
| styleUrls | 样式URl,string:[] |
| styles | 内联样式,string:[] |
| animations | 动画相关 |
| encapsulation | 模板和css的样式封装策略 |
# 组件周期
组件的声明周期需要实现@angular/core库中的接口,可以对组件或指令进行操作,以下为常用的周期,按照执行顺序排序
# 常用周期
| 钩子 | 用法 |
|---|---|
| ngOnchanges | 当有输入属性发生改变时,可执行多次 |
| ngOnInit | 当初始化完成后,执行一次 |
| ngAfterViewInit | 视图初始化完成,执行一次 |
| ngAfterContentInit | 插槽的内容初始化完成,执行一次 |
| ngOnDestroy | 组件或指令销毁,释放资源的地方 |
# 组件交互
# 父传子
使用
@Input装饰器标记字段为输入属性
@Input() hero: Hero; // 定义输入属性
@Input('master') masterName: string; // 自定义别名
// 父组件
<son [hero]="hero"></son>
<son [master]="master"></son>
2
3
4
5
6
使用set 拦截
// set拦截输入属性
@Input() set hero(hero){
this._hero = hero;
}
2
3
4
通过ngOnchanges获取输入属性的变化
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
let log: string[] = [];
for (let propName in changes) {
let changedProp = changes[propName];
// do something
}
}
2
3
4
5
6
7
# 子传父
使用
@Output()装饰器定义一个输出属性为EventEmitter实例
// 定义一个输出属性voted,值为EventEmitter实例,泛型为Boolean类型
@Output() voted:EventEmitter = new EventEmitter<boolean>();
didVote = false;
vote(agreed: boolean) {
this.voted.emit(agreed);
this.didVote = true;
}
2
3
4
5
6
7
8
EventEmitter<T>继承自 RXJS的Subject
EventEmitter:在带有@Output指令的组件中使用,以同步或异步方式发送自定义事件,并通过订阅实例为这些事件注册处理程序,因此接收事件传递也可以用一下方法,由父组件获取子组件实例
this.voted.emit(parmas);
this.voted.subscribe(...); // 订阅子组件的事件
2
# 父子组件简单互动
通过本地变量
在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法,注意此方法仅能在父组件模板中使用
<app-son #son></app-son>
<button (click)="son.method()"></button>
<p>{{son.props}}</p>
2
3
使用
@ViewChild、@ViewChildren来获取一个或一组的子组件实例
# 元数据属性:
- selector - 用于查询的指令类型或名字
- read - 从查询到的元素中读取另一个令牌
- static- 如果为true,则在运行更改检测之前解析查询结果;如果为false,则在更改检测之后解析。 默认为false
# 使用方法
- 任何带有
@Component或@Directive装饰器的类 - 字符串形式的模板引用变量(比如可以使用
@ViewChild('cmp')来查询<my-component #cmp></my-component> - 组件树中任何当前组件的子组件所定义的提供商(比如
@ViewChild(SomeService) someService: SomeService) - 任何通过字符串令牌定义的提供商(比如
@ViewChild('someToken') someTokenVal: any) TemplateRef(比如可以用@ViewChild(TemplateRef) template;来查询<ng-template></ng-template>)
# 例子
// <my-component #cmp></my-component>
@ViewChild('cmp') public cmp:component;
// <my-component app-trim></my-component>
@ViewChild(TrimDirective) public dir:TrimDirective;
// <my-component #cmp></my-component>
@ViewChildren('cmp') public dom:QueryList<SonFormComponent>;
// <my-component app-trim></my-component>
@ViewChildren(TrimDirective) public dom:QueryList<TrimDirective>;
2
3
4
5
6
7
8
QueryList:一个不可修改的条目列表,当应用状态变化时,Angular 会保证它是最新的
# 非父子组件
使用单例服务共享信息
单例服务在应用中只会实例化一次(根模块注入),可以此来设置全局共享变量
使用subject来广播事件
组件共享同一个服务,利用该服务在组件家族内部实现双向通讯,
@Injectable()
export class MissionService {
// Observable string sources
private missionAnnouncedSource = new Subject<string>();
// Observable string streams
missionAnnounced$ = this.missionAnnouncedSource.asObservable();
// Service message commands
announceMission(mission: string) {
this.missionAnnouncedSource.next(mission);
}
}
2
3
4
5
6
7
8
9
10
11
12
使用ngrx状态管理工具进行数据管理
# 组件样式
# 范围化
在
@Component的元数据中指定的样式只会对该组件的模板生效
@Component({
selector: 'app-root',
template: `
<h1>Tour of Heroes</h1>
<app-hero-main [hero]="hero"></app-hero-main>
`,
styles: ['h1 { font-weight: normal; }']
})
2
3
4
5
6
7
8
# 样式引入
- css
@import语法引入相对路径,例@import './hero-details-box.css'; - 全局样式需要在
angular.json中配置
# 视图封装模式
通过修改元数据encapsulation,来配置组件的视图封装模式
| enum:ViewEncapsulation | 用法 |
|---|---|
| Emulated(默认) | 全局样式能进来,组件样式不能出 |
| None | 全局样式能进来,组件样式能出去 |
| shadowDom | 全局样式进不来,组件样式出不去 |
| Native | 使用原生shadowDom |
# 动态组件
一般来讲,切换不同的组件仅需要*NgIf,*NgSwitch等指令来切换组件
而对于动态组件,则是通过动态加载组件来展示
# 1:确认组件容器
通过指令,在模板中指定组件的插入点,使用ViewContainerRef来获取插入点视图的访问权
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[dynamic-component-container]',
})
export class AdDirective {
constructor(
public viewRef: ViewContainerRef
) { }
ngOnInit(){}
}
// <div dynamic-component-container></div>
// 这样就表明动态组件的容器为此div,
// 当然使用<ng-template></ng-template>是最佳选择,它不会渲染任何额外的输出
2
3
4
5
6
7
8
9
10
11
12
13
14
ViewContainerRef可以对该容器中的组件进行一系列操作
clear():清除容器中的视图createComponent():实例化一个组件,并把它插入到指定index,多次执行会append多个组件
# 2:组件映射
使用ComponentFactoryResolver映射一个componentFactory类
constructor(
public viewRef: ViewContainerRef,
+ public factory:ComponentFactoryResolver
) { }
+ ngOnInit(){
+ const component = this.factory.resolveComponentFactory(SonComponent)
+ }
2
3
4
5
6
7
# 3:挂载组件实例
ngOnInit(){
const component = this.factory.resolveComponentFactory(SonFormComponent)
+ const son:ComponentRef<SonFormComponent> = this.viewRef.createComponent(component);
}
2
3
4
至此就可将SonComponent 添加至<div> 容器中
# 4:完整样例
import { Directive, ViewContainerRef,ComponentFactoryResolver } from '@angular/core';
@Directive({
selector: '[dynamic-component-container]',
})
export class AdDirective {
constructor(
public viewRef: ViewContainerRef,
public factory:ComponentFactoryResolver
) { }
ngOnInit(){
// 创建一个组件实例
const componentFactory = this.factory.resolveComponentFactory(component);
// 清除当前视图
this.viewRef.clear();
// 挂载组件实例
const son:ComponentRef<SonFormComponent> = this.viewRef.createComponent(componentFactory);
// 获取组件的变量或方法
son.instance.id;
son.instance.methods();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 需要注意
ViewContainerRef:获取视图权限,操作动态添加进行视图的组件ComponentRef:ViewContainerRef实例化组件的返回类型ViewRef:ComponentRef的hostView属性,ViewContainerRef的get()操作方法返回都是此类型ComponentFactoryResolver:将一个组件映射为ComponentFactory类ComponentFactory:ViewContainerRef挂载组件所需要的参数