angular11源碼探索十[viewChild和viewChildren到幾個不經常使用的生命週期用處]

完整的生命週期

constructor
ngOnChanges
ngOninit
ngDoCheck 
	* ngAfterContentInit
	* ngAfterContentChecked
	* ngAfterViewInit
	* ngAfterViewChecked
ngOnDestroy

ngOnChangesapp

每一次@Input 都會執行一次dom

ngOnInitide

初始化數據的加載函數

ngOnDestroythis

在原件銷燬以前spa

ngDoCheckcode

每一次執行變動,都會自動執行ngDoCheck 事件blog

下面幾個生命週期應該屬於ngDocheck 裏面的

子元件事件token

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);
    })
  }
相關文章
相關標籤/搜索