albatrosary's blog

UI/UXとエンタープライズシステム

Angular の Component.queries(ViewChild(ren), ContentChild(ren)) を評価する

ViewChild(ren) と ContentChild(ren) は各々 AfterViewInit, AfterContentInit より後でアクセス可能と成りますと「Angularデベロッパーズガイド」に書いてますが、実際にサンプルを動かすと「???」でしたのでメモを書き留めます

本のサンプルに若干のログを追加する

本に説明が無かったのだが、QueriesChildコンポーネントとAppコンポーネントで app-view-child タグと app-content-child タグを2つ記載しているのは QueryList で2つオブジェクトを抱えるというのを示したかったためと想像します。以下コード:

import {
  Component,
  OnInit,
  AfterContentInit,
  AfterViewInit,
  QueryList,
  ViewChild,
  ViewChildren,
  ContentChild,
  ContentChildren
} from '@angular/core';

@Component({
  selector: 'app-view-child',
  template: `<div>This is ViewChild</div>`
})
export class ViewChildComponent implements OnInit {
  ngOnInit() {
    console.log('ViewChildComponent', 'ngOnInit()');
  }
  outline(): string {
    return 'ViewChildComponent.outlint()';
  }
}

@Component({
  selector: 'app-content-child',
  template: `<div>This is ContentChild</div>`
})
export class ContentChildComponent implements OnInit {
  ngOnInit() {
    console.log('ContentChildComponent', 'ngOnInit()');
  }
  outline(): string {
    return 'ContentChildComponent.outlint()';
  }
}

@Component({
  selector: 'app-queries-child',
  template: `
    <app-view-child></app-view-child>
    <app-view-child></app-view-child>
    <ng-content></ng-content>
  `,
  queries: {
    viewChild: new ViewChild(ViewChildComponent),
    viewChildren: new ViewChildren(ViewChildComponent),
    contentChild: new ContentChild(ContentChildComponent),
    contentChildren: new ContentChildren(ContentChildComponent),
  }
})
export class QueryComponent implements OnInit, AfterContentInit, AfterViewInit {
  viewChild: ViewChildComponent;
  viewChildren: QueryList<ViewChildComponent>;
  contentChild: ContentChildComponent;
  contentChildren: QueryList<ContentChildComponent>;

  constructor() {
    console.log('QueryComponent', 'constructor()', 'ViewChild', this.viewChild);
    console.log('QueryComponent', 'constructor()', 'ViewChildren', this.viewChildren);
    console.log('QueryComponent', 'constructor()', 'ContentChild', this.contentChild);
    console.log('QueryComponent', 'constructor()', 'ContentChildren', this.contentChildren);
  }
  
  ngOnInit() {
    console.log('QueryComponent', 'ngOnInit()', 'ViewChild', this.viewChild.outline());
    console.log('QueryComponent', 'ngOnInit()', 'ViewChildren', this.viewChildren);
    console.log('QueryComponent', 'ngOnInit()', 'ContentChild', this.contentChild.outline());
    console.log('QueryComponent', 'ngOnInit()', 'ContentChildren', this.contentChildren);
  }

  ngAfterContentInit() {
    console.log('QueryComponent', 'ngAfterContentInit()', 'ContentChild', this.contentChild);
    console.log('QueryComponent', 'ngAfterContentInit()','ContentChildren', this.contentChildren);
  }

  ngAfterViewInit() {
    console.log('QueryComponent', 'ngAfterViewInit()', 'ViewChild', this.viewChild);
    console.log('QueryComponent', 'ngAfterViewInit()', 'ViewChildren', this.viewChildren);
  }
}

@Component({
  selector: 'app-root',
  template: `
    <app-queries-child>
      <app-content-child></app-content-child>
      <app-content-child></app-content-child>
    </app-queries-child>
  `
})
export class AppComponent { }

実際に動かしてみる

angular-cli でプロジェクトを作り上記コードを動かすと結果はこうだ:

f:id:albatrosary:20180118144411p:plain

気になるのはこの行

QueryComponent ngOnInit() ViewChild ViewChildComponent.outlint()
QueryComponent ngOnInit() ContentChild ContentChildComponent.outlint()

アクセスできてますよね?ただ確かに ViewChildren と ContentChildren についてはアクセスできてない。これって QueryList じゃない?

angular.io の解説

https://angular.io/api/core/Directive#queries では次のように説明している。

Content queries are set before the ngAfterContentInit callback is called. View queries are set before the ngAfterViewInit callback is called.

ただ、サンプルが Children のものしか書かれてない。

class SomeDir {
  contentChildren: QueryList<ChildDirective>,
  viewChildren: QueryList<ChildDirective>

  ngAfterContentInit() {
    // contentChildren is set
  }

  ngAfterViewInit() {
    // viewChildren is set
  }

Angular のソースも読まずググる

https://netbasal.com/understanding-viewchildren-contentchildren-and-querylist-in-angular-896b0c689f6e

これによると

The QueryList is initialized only before the ngAfterViewInit lifecycle hook, therefore, is available only from this point.

であり

The QueryList is initialized only before the ngAfterContentInit lifecycle hook, therefore, is available only from this point.

である。The QueryList is だって。

最後に

angular のソースを読んでみる(まだ読んでいない)

作ったコードはこちら

https://github.com/albatrosary/component-queries-sample