angular11源码探索十[viewChild和viewChildren到几个不常用的生命周期用处]
完整的生命周期
constructorngOnChangesngOninitngDoCheck * ngAfterContentInit* ngAfterContentChecked* ngAfterViewInit* ngAfterViewCheckedngOnDestroy
ngOnChanges
每一次
@Input
都会执行一次
ngOnInit
初始化数据的加载
ngOnDestroy
在原件销毁之前
ngDoCheck
每一次执行变更,都会自动执行
ngDoCheck
事件
下面几个生命周期应该属于ngDocheck 里面的
子元件事件
ngAfterViewInit
- 当View里面所有元件都初始化完成后 触发的事件
ngAfterViewChecked
- 当view里面所有元件都完成变更侦测机制后触发的事件
ngAfterViewChecked父组件里面放入一个子组件,子组件里面有个视图改变操作<input [(ngModel)]="hello">hello = 'hello';我们在父组件里面监听变化ngAfterViewChecked() { console.log(1);}我们发现每次子组件input输入的值发生变化的时候,ngAfterViewChecked 都会执行
这样写好像没什么意义,那我们可以升级下写法
父组件直接拿到变化的值 // 拿到子组件这个函数 @ViewChild(AComponent) a:AComponent ngAfterViewChecked() { console.log(this.a.hello); }
内容元件事件 ng-content
ngAfterContentInit
- 当content里面所有的元件都初始化完成后触发事件
<app-a><app-b></app-b></app-a>
父
ng-content
把子组件的内容映射到父组件app-a <ng-content></ng-content> 内容投影子app-b通过ng-content拿到子组件内容父组件直接拿到<ng-content>的子组件export class AComponent implements AfterContentInit{@ContentChild(BComponent) B: BComponent;ngAfterContentInit() { console.log(this.B);}}
ngAfterContentChecked
- 当content里面所有元件都完成后侦测机制后触发事件
viewChild 和viewChildren区别
viewChild
ViewChild(selector: string | Function | Type<any>, opts: { read?: any; static: boolean; }): any
两个参数,Aselector
和opts
selector
: 字符串,类型或者字符串或类型的函数,默认查找与选择器匹配的第一个元素
opts
: 有两个选项
static
确定查找何时解析,查询指定的字符串解析,true
初始化视图时,false
如果你希望在每次更改检测后解决后解决它
获取angular 组件内呈现的DOM元素的引用,可以进行DOM元素的操作
<div #someElement>Sample Code</div>@ViewChild('someElement') some;ngAfterViewInit 生命周期可以拿到这个引用 ngAfterViewInit(): void { this.some.nativeElement }
视图查询
- 拿到任何带有
@Component
或@Directive
装饰器的类当前视图使用的 - 字符串的模板引用变量,就是上面的dom引用
- 当前组件的子组件定义的提供商
@ViewChild(SomeService) someService: SomeService )
- 任何通过字符串令牌定义的提供商(比如
@ViewChild('someToken') someTokenVal: any
(刚开始我不懂,其实就是你子组件使用啦,就是可以使用)
找到字符串令牌
@Directive({ selector: '[appDir]' , providers: [ {provide: 'token', useValue: 'test'}, {provide: TOKEN_URL, useValue: 'test1'}, TestOneService ]})页面使用 <div appDir>dfsdsfdsfdsf</div>ts查找 @ViewChild('token',{static:true}) token:string; @ViewChild(TOKEN_URL,{static:true}) token1:any; @ViewChild(TestOneService) test:TestOneService; ngAfterViewInit() { console.log(this.token); console.log(this.token1); console.log(this.test); }
viewChildren
new(selector: Type<any>|InjectionToken<unknown>|Function|string, opts?: {descendants?: boolean, read?: any}): Query;
opts
- descendants 包含所有后代时为true,否则仅包括直系子代。
- read 用于从查询的元素中读取不同的令牌。
区别
@ViewChildren
元素引用列表,而不是单个引用
<input type="text" [(ngModel)]="a"><input type="text" [(ngModel)]="b"><input type="text" [(ngModel)]="c"> a = 1 b = 2 c = 2; @ViewChildren(NgModel) model: QueryList<NgModel> ngAfterViewInit() { console.log(this.model.length);// 可以拿到这三个值 }
<div appDir [dir]="['aaa']"></div><div appDir [dir]="{sex:'男'}"></div>父@ViewChildren(DirDirective) directives!: QueryList<DirDirective>; ngAfterViewInit() { this.directives.forEach(val=>{ console.log(val); }) }
可能会对QueryList
返回的不怎么理解
ViewChild和ViewChildren可用于访问子组件的属性和方法。使用ViewChild和ViewChildren,我们可以获取子组件的引用,从而进一步提供对所有属性和方法的访问。这可以使父组件访问子组件并启用它们之间的通信。
父组件对子组件的值的修改父<button (click)="clickDown()">Click</button><app-b #app></app-b> @ViewChild('app') app:BComponent; clickDown() { this.app.num=20; }子num=10
父传子的一种方式
<app-a *ngFor="let config of configs" [config]="config"></app-a> configs = [ {opacity: 0, duration: 500}, {opacity: 1, duration: 600}, ];子 @Input('config') config;
案例
拿到组件实例
<app-a #ccc></app-a><app-b #ccc></app-b>父export class TwoComponent implements OnInit, AfterViewInit, AfterViewChecked,AfterContentInit { @ViewChild('ccc') C:AComponent; @ViewChildren('ccc') cRen:QueryList<AComponent|BComponent> constructor() {} ngAfterViewInit() { console.log(this.C);// 默认拿到第一个 console.log(this.cRen);//默认拿到两个组件的集合 console.log(this.cRen.first); //拿到第一个 console.log(this.cRen.last);// 拿到最后一个 } ngOnInit(): void {}}
拿到DOM
<p #ccc></p><div #ccc></div> @ViewChild('ccc') C:ElementRef; @ViewChildren('ccc') cRen:QueryList<ElementRef>ngAfterViewInit() { console.log(this.C);// 默认拿到第一个 console.log(this.cRen.first); //拿到第一个 }
支持多个引用
<p #ccc #aaa></p> @ViewChild('ccc') C:ElementRef; @ViewChild('aaa') A:ElementRef; ngAfterViewInit() { console.log(this.C);// 默认拿到第一个 console.log(this.A); } 同理 ViewChildren也是一样的
拿到template的内容
用ViewContainerRef
填充到页面
<ng-template #aaa> <h1>aaaa</h1> <h1>bbb</h1></ng-template> @ViewChild('aaa') A: TemplateRef; constructor(private vRef: ViewContainerRef) { } ngAfterViewInit() { this.vRef.createEmbeddedView(this.A) }
多级查找
<app-a #q> <app-b #contentQuery></app-b> <app-c #contentQuery></app-c></app-a>大盒子 @ViewChild('q') A:AComponent ngAfterViewInit() { console.log(this.A); }app-a<ng-content></ng-content>怎么拿到这两个子组件的列表呢 @ContentChild(BComponent) B; @ContentChild(CComponent) C; @ContentChildren('contentQuery') D!:QueryList; ngAfterContentInit() { console.log(this.B); console.log(this.C); console.log(this.D); //拿到两个啦 }
dom形式类似
<app-a> <div #contentQuery>xxx</div> <div #contentQuery>bbb</div></app-a>app-a <ng-content></ng-content> @ContentChildren('contentQuery') contentQuery; ngAfterViewInit() { console.log(this.contentQuery); }
ViewChild 的get/set操作
两个要一起用不然会报错,也可以直接使用set,也就是说get是可选的,但是set是必传的
可以拿到修改的记录<span #foo></span> _foo!: ElementRef; @ViewChild('foo') get foo(): ElementRef { return this._foo; } set foo(value: ElementRef) { this._foo = value; } ngAfterViewInit() { let text=this.renderer.createText('xxxx') this.renderer.appendChild(this._foo.nativeElement,text) }
指令
<div [appTestDir]="'aaa'"></div>export class TwoComponent implements OnInit, AfterViewInit, AfterViewChecked, AfterContentInit { _textDir!: TestDirDirective; @ViewChild(TestDirDirective, {static: true}) //true 初始化视图时 get textDir(): TestDirDirective { return this._textDir; } set textDir(value: TestDirDirective) { console.log(value.appTestDir); //222 this._textDir = value; } ngAfterViewInit() { console.log(this._textDir.appTestDir);//aaa }}
ng-template 把一个组件传入到另一个组件里
<app-b [content]="app"></app-b><ng-template #app> <app-a></app-a></ng-template>app-b @Input('content') content; <ng-container *ngTemplateOutlet="content"></ng-container>
升级第二种方式
<app-a> <ng-template #contentQuery> <h1>dddddd</h1> </ng-template> <ng-template #contentQuery> <h1>ggggg</h1> </ng-template></app-a>app-a<ng-content></ng-content><!--第一个--><div *ngTemplateOutlet="contentQuery.first"></div> @ContentChildren('contentQuery') contentQuery:QueryList<TemplateRef<any> | null>; ngAfterContentInit() { console.log(this.contentQuery); }
其实指令也是同理可以拿到的
<sub-comp> <div some-dir></div> <div some-dir></div></sub-comp> @Directive({selector: '[some-dir]'}) class SomeDir { }大盒子可以直接查到 @ViewChild(SubComp) subComp!: SubComp;sub-comp<ng-content></ng-content> @ContentChildren(SomeDir) foo!: QueryList<SomeDir>;
动态检测子代的变化
<app-a> <div *ngIf="showing" #cmp>h1 h1</div></app-a><button (click)="showing=!showing"> </button> showing: boolean=true;====app-a <ng-content></ng-content> @ContentChildren('cmp',{descendants:false}) cmp!:QueryList<ElementRef> ngAfterContentChecked() { console.log(this.cmp.length); }
另一个方式
<button (click)="showing=!showing"> </button><div *ngIf="showing" #foo> <h1>我是谁</h1></div> @ViewChildren('foo') foos!: QueryList<any>; showing: boolean=true; ngAfterViewInit() { this.foos.changes.subscribe(value=>{ console.log(value); }) }
赞 (0)