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

两个参数,Aselectoropts

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);    })  }

来源:https://www.icode9.com/content-1-798801.html

(0)

相关推荐