TIP: このガイドでは、基本概念のガイドを読んでいることを前提としています。Angularを初めて使う場合は、まずこちらをお読みください。
コンポーネントは、子要素を見つけてそのインジェクターから値を読み取るクエリを定義できます。
開発者は、クエリを使用して、子コンポーネント、ディレクティブ、DOM要素などの参照を取得することがほとんどです。
すべてのクエリ関数は、最新の結果を反映するシグナルを返します。
computedやeffectなどのリアクティブなコンテキストでも、シグナル関数を呼び出すことで結果を読み取れます。
クエリには、ビュークエリとコンテンツクエリの2つのカテゴリーがあります。
ビュークエリ
ビュークエリは、コンポーネントの_ビュー_(コンポーネント自身のテンプレートで定義された要素)内の要素から結果を取得します。viewChild関数を使用して単一の結果をクエリできます。
@Component({
selector: 'custom-card-header',
/* ... */
})
export class CustomCardHeader {
text: string;
}
@Component({
selector: 'custom-card',
template: '<custom-card-header>Visit sunny California!</custom-card-header>',
})
export class CustomCard {
header = viewChild(CustomCardHeader);
headerText = computed(() => this.header()?.text);
}
この例では、CustomCardコンポーネントは子CustomCardHeaderをクエリし、computedで結果を使用しています。
クエリが結果を見つけられない場合、その値はundefinedになります。これは、ターゲット要素が@ifによって非表示になっている場合に発生する可能性があります。Angularは、アプリケーションの状態が変化するにつれてviewChildの結果を最新の状態に保ちます。
viewChildren関数を使用して、複数結果をクエリできます。
@Component({
selector: 'custom-card-action',
/* ... */
})
export class CustomCardAction {
text: string;
}
@Component({
selector: 'custom-card',
template: `
<custom-card-action>Save</custom-card-action>
<custom-card-action>Cancel</custom-card-action>
`,
})
export class CustomCard {
actions = viewChildren(CustomCardAction);
actionsTexts = computed(() => this.actions().map((action) => action.text));
}
viewChildrenは、クエリ結果のArrayを含むシグナルを作成します。
**クエリはコンポーネントの境界を貫通することはありません。**ビュークエリは、コンポーネントのテンプレートからの結果のみを取得できます。
コンテンツクエリ
コンテンツクエリは、コンポーネントの_コンテンツ_(コンポーネントが使用されているテンプレート内でコンポーネントの中にネストされた要素)内の要素から結果を取得します。contentChild関数を使用して単一の結果をクエリできます。
@Component({
selector: 'custom-toggle',
/* ... */
})
export class CustomToggle {
text: string;
}
@Component({
selector: 'custom-expando',
/* ... */
})
export class CustomExpando {
toggle = contentChild(CustomToggle);
toggleText = computed(() => this.toggle()?.text);
}
@Component({
/* ... */
// CustomToggle is used inside CustomExpando as content.
template: `
<custom-expando>
<custom-toggle>Show</custom-toggle>
</custom-expando>
`,
})
export class UserProfile {}
クエリが結果を見つけられない場合、その値はundefinedになります。これは、ターゲット要素が存在しないか、@ifによって非表示になっている場合に発生する可能性があります。Angularは、アプリケーションの状態が変化するにつれてcontentChildの結果を最新の状態に保ちます。
デフォルトでは、コンテンツクエリはコンポーネントの_直接_の子のみを見つけ、子孫にはトラバースしません。
contentChildren関数を使用して、複数結果をクエリできます。
@Component({
selector: 'custom-menu-item',
/* ... */
})
export class CustomMenuItem {
text: string;
}
@Component({
selector: 'custom-menu',
/* ... */
})
export class CustomMenu {
items = contentChildren(CustomMenuItem);
itemTexts = computed(() => this.items().map((item) => item.text));
}
@Component({
selector: 'user-profile',
template: `
<custom-menu>
<custom-menu-item>Cheese</custom-menu-item>
<custom-menu-item>Tomato</custom-menu-item>
</custom-menu>
`,
})
export class UserProfile {}
contentChildrenは、クエリ結果のArrayを含むシグナルを作成します。
**クエリはコンポーネントの境界を貫通することはありません。**コンテンツクエリは、コンポーネント自体と同じテンプレートからの結果のみを取得できます。
必須クエリ
子クエリ(viewChildまたはcontentChild)が結果を見つけられない場合、その値はundefinedになります。これは、ターゲット要素が@ifや@forなどの制御フロー文によって非表示になっている場合に発生する可能性があります。このため、子クエリはundefinedを含む値型を持つシグナルを返します。
場合によっては、特にviewChildを使用する場合、特定の子が常に利用可能であることが確実な場合があります。他の場合では、特定の子が存在することを厳格に適用したい場合があります。これらの場合、_必須クエリ_を使用できます。
@Component({
/*...*/
})
export class CustomCard {
header = viewChild.required(CustomCardHeader);
body = contentChild.required(CustomCardBody);
}
必須クエリが一致する結果を見つけられない場合、Angularはエラーを報告します。これは結果が利用可能であることを保証するため、必須クエリは自動的にシグナルの値型にundefinedを含めません。
クエリロケーター
各クエリデコレーターの最初の引数は、そのロケーターです。
ほとんどの場合、ロケーターとしてコンポーネントまたはディレクティブを使用することをお勧めします。
テンプレート参照変数に対応する 文字列ロケーターも指定できます。
@Component({
/*...*/
template: `
<button #save>Save</button>
<button #cancel>Cancel</button>
`,
})
export class ActionBar {
saveButton = viewChild<ElementRef<HTMLButtonElement>>('save');
}
複数の要素が同じテンプレート参照変数を定義している場合、クエリは最初に一致する要素を取得します。
Angularは、CSSセレクターをクエリロケーターとしてサポートしていません。
クエリとインジェクターツリー
TIP: プロバイダーとAngularのインジェクションツリーについては、依存性の注入を参照してください。
より高度なケースでは、ロケーターとして任意のProviderTokenを使用できます。これにより、コンポーネントとディレクティブのプロバイダーに基づいて要素を見つけることができます。
const SUB_ITEM = new InjectionToken<string>('sub-item');
@Component({
/*...*/
providers: [{provide: SUB_ITEM, useValue: 'special-item'}],
})
export class SpecialItem {}
@Component({/*...*/})
export class CustomList {
subItemType = contentChild(SUB_ITEM);
}
上記の例では、ロケーターとしてInjectionTokenを使用していますが、任意のProviderTokenを使用して特定の要素を見つけることができます。
クエリオプション
すべてのクエリ関数は、第2引数としてオプションオブジェクトを受け取ります。これらのオプションは、クエリが結果を見つける方法を制御します。
要素のインジェクターからの特定の値の読み取り
デフォルトでは、クエリロケーターは、検索対象の要素と取得される値の両方を示します。代わりに、readオプションを指定して、ロケーターによって一致した要素から別の値を取得できます。
@Component({
/*...*/
})
export class CustomExpando {
toggle = contentChild(ExpandoContent, {read: TemplateRef});
}
上記の例では、ExpandoContentディレクティブを持つ要素を見つけて、
その要素に関連付けられたTemplateRefを取得します。
開発者は、readを使用してElementRefとTemplateRefを取得することが最も一般的です。
コンテンツの子孫
デフォルトでは、contentChildrenクエリはコンポーネントの直接の子要素のみを検索し、子孫要素にはトラバースしません。
一方、contentChildクエリはデフォルトで子孫要素も検索します。
@Component({
selector: 'custom-expando',
/* ... */
})
export class CustomExpando {
toggle = contentChildren(CustomToggle); // none found
// toggle = contentChild(CustomToggle); // found
}
@Component({
selector: 'user-profile',
template: `
<custom-expando>
<some-other-component>
<custom-toggle>Show</custom-toggle>
</some-other-component>
</custom-expando>
`
})
export class UserProfile { }
上記の例では、CustomExpando は <custom-expando> の直接の子要素ではないため、contentChildren を用いると <custom-toggle> を検出できません。descendants: true を設定することで、同じテンプレート内のすべての子孫を対象にクエリを実行するように設定できます。ただし、クエリはコンポーネントの境界を越えて、他のテンプレートの要素にアクセスすることは 決して ありません。
ビュークエリにはこのオプションはありません。これは、常に子孫をトラバースするためです。
デコレーターベースのクエリ
TIP: Angularチームは新規プロジェクトにはシグナルベースのクエリ関数の使用を推奨していますが、 元のデコレーターベースのクエリAPIは引き続き完全にサポートされています。
代わりに、対応するデコレーターをプロパティに追加することでもクエリを宣言できます。デコレーターベースのクエリは、下記で説明する点を除いて、シグナルベースのクエリと同じように動作します。
ビュークエリ
@ViewChildデコレーターを使用して、単一の結果をクエリできます。
@Component({
selector: 'custom-card-header',
/* ... */
})
export class CustomCardHeader {
text: string;
}
@Component({
selector: 'custom-card',
template: '<custom-card-header>Visit sunny California!</custom-card-header>',
})
export class CustomCard implements AfterViewInit {
@ViewChild(CustomCardHeader) header: CustomCardHeader;
ngAfterViewInit() {
console.log(this.header.text);
}
}
この例では、CustomCardコンポーネントは子CustomCardHeaderをクエリし、ngAfterViewInitで結果にアクセスしています。
Angularは、アプリケーションの状態が変化するにつれて@ViewChildの結果を最新の状態に保ちます。
**ビュークエリの結果はngAfterViewInitライフサイクルメソッドで使用可能になります。**この時点より前では、値はundefinedです。コンポーネントライフサイクルの詳細については、ライフサイクルセクションを参照してください。
@ViewChildrenデコレーターを使用して、複数の結果をクエリできます。
@Component({
selector: 'custom-card-action',
/* ... */
})
export class CustomCardAction {
text: string;
}
@Component({
selector: 'custom-card',
template: `
<custom-card-action>Save</custom-card-action>
<custom-card-action>Cancel</custom-card-action>
`,
})
export class CustomCard {
@ViewChildren(CustomCardAction) actions: QueryList<CustomCardAction>;
ngAfterViewInit() {
this.actions.forEach(action => {
console.log(action.text);
});
}
}
@ViewChildrenは、クエリ結果を含むQueryListオブジェクトを作成します。changesプロパティを使用して、時間の経過とともにクエリ結果の変更を購読できます。
コンテンツクエリ
@ContentChildデコレーターを使用して、単一の結果をクエリできます。
@Component({
selector: 'custom-toggle',
/* ... */
})
export class CustomToggle {
text: string;
}
@Component({
selector: 'custom-expando',
/* ... */
})
export class CustomExpando {
@ContentChild(CustomToggle) toggle: CustomToggle;
ngAfterContentInit() {
console.log(this.toggle.text);
}
}
@Component({
selector: 'user-profile',
template: `
<custom-expando>
<custom-toggle>Show</custom-toggle>
</custom-expando>
`
})
export class UserProfile { }
この例では、CustomExpandoコンポーネントは子CustomToggleをクエリし、ngAfterContentInitで結果にアクセスしています。
Angularは、アプリケーションの状態が変化するにつれて@ContentChildの結果を最新の状態に保ちます。
**コンテンツクエリの結果はngAfterContentInitライフサイクルメソッドで使用可能になります。**この時点より前では、値はundefinedです。コンポーネントライフサイクルの詳細については、ライフサイクルセクションを参照してください。
@ContentChildrenデコレーターを使用して、複数の結果をクエリできます。
@Component({
selector: 'custom-menu-item',
/* ... */
})
export class CustomMenuItem {
text: string;
}
@Component({
selector: 'custom-menu',
/* ... */
})
export class CustomMenu {
@ContentChildren(CustomMenuItem) items: QueryList<CustomMenuItem>;
ngAfterContentInit() {
this.items.forEach(item => {
console.log(item.text);
});
}
}
@Component({
selector: 'user-profile',
template: `
<custom-menu>
<custom-menu-item>Cheese</custom-menu-item>
<custom-menu-item>Tomato</custom-menu-item>
</custom-menu>
`
})
export class UserProfile { }
@ContentChildrenは、クエリ結果を含むQueryListオブジェクトを作成します。changesプロパティを使用して、時間の経過とともにクエリ結果の変更を購読できます。
デコレーターベースのクエリオプション
すべてのクエリデコレーターは、第2引数としてオプションオブジェクトを受け取ります。これらのオプションは、シグナルベースのクエリと同じように動作しますが、下記で説明する点を除きます。
静的クエリ
@ViewChildと@ContentChildデコレーターは、staticオプションを受け取ります。
@Component({
selector: 'custom-card',
template: '<custom-card-header>Visit sunny California!</custom-card-header>',
})
export class CustomCard {
@ViewChild(CustomCardHeader, {static: true}) header: CustomCardHeader;
ngOnInit() {
console.log(this.header.text);
}
}
static: trueを設定することで、このクエリのターゲットは_常に_存在し、条件付きでレンダリングされないことをAngularに保証します。これにより、ngOnInitライフサイクルメソッドで早く結果を利用できます。
静的クエリの結果は、初期化後に更新されません。
staticオプションは、@ViewChildrenと@ContentChildrenクエリでは使用できません。
QueryListの使用
@ViewChildrenと@ContentChildrenはどちらも、結果のリストを含むQueryListオブジェクトを提供します。
QueryListは、map、reduce、forEachなど、配列のような方法で結果を操作するための多くの便利なAPIを提供します。toArrayを呼び出すことで、現在の結果の配列を取得できます。
changesプロパティを購読して、結果が変更されるたびに何かを実行できます。
一般的なクエリの落とし穴
クエリを使用する際、一般的な落とし穴により、コードの理解と保守が難しくなる可能性があります。
複数のコンポーネント間で共有される状態については、常に単一の真実の源を維持してください。これにより、異なるコンポーネントで状態が繰り返し同期されなくなるシナリオを回避できます。
子コンポーネントに直接状態を書き込むことは避けてください。このパターンは、理解しにくく、ExpressionChangedAfterItHasBeenCheckedエラーが発生しやすい壊れやすいコードにつながる可能性があります。
親コンポーネントまたは祖先コンポーネントに直接状態を書き込むことは決してしないでください。このパターンは、理解しにくく、ExpressionChangedAfterItHasBeenCheckedエラーが発生しやすい壊れやすいコードにつながる可能性があります。