0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Angularを学ぶ テンプレート

Posted at

テンプレート

テンプレートとは、コンポーネントが画面に描画する HTML の断片を定義したもの。単なる HTML に加えて、Angular 独自の構文が含まれており、データバインディングやイベントリスニング、条件分岐、ループなど、動的・双方向なインターフェースを表現できる。テンプレートは通常、.component.ts ファイルの template プロパティまたは .component.html ファイル内にある。

バインディング

バインディングとは、主に 「コンポーネント」と「テンプレート」間でデータやイベントをやり取りする仕組み。これにより、コンポーネント内のデータの変化が即座に画面(ビュー)に反映され、ユーザーの操作も簡単にコード側へ反映できる。

テキスト補間

テンプレートで動的なテキストをバインドするには、二重中括弧 {{}} を使用する。これは Angular の 「テキスト補間」 と呼ばれ、コンポーネントのプロパティやメソッドの値をテンプレート内に埋め込むための基本的な書き方。

@Component({
    template: `
        <p>Your color preference is {{ theme }}.</p>
    `,
    ...
})
export class AppComponent {
    theme = 'dark';
}
<p>Your color preference is dark.</p>

動的なプロパティと属性のバインディング

Angular では、角括弧 [] を使用して、動的な値をオブジェクトのプロパティと HTML 属性にバインドすることができる。

ネイティブ要素

すべての HTML 要素は、ブラウザ内では JavaScript の「DOM オブジェクト」として扱われる。プロパティバインディングを使うことで、HTMLの要素(たとえばボタン)のプロパティ(たとえば disabled)にコンポーネント内の変数の値を簡単に反映できる。

<button [disabled]="isFormValid">Save</button>

コンポーネントとディレクティブ

プロパティバインディングは、独自コンポーネントやディレクティブのプロパティにも適用できる。

コンポーネント
<my-listbox [value]="mySelection" />
ディレクティブ
<img [ngSrc]="profilePhotoUrl" alt="The current user's profile photo">

属性

対応する DOM プロパティを持たない HTML 属性(ARIA 属性や SVG 属性など)を設定したい場合、attr. プレフィックスを使用できる。

<ul [attr.role]="listRole">

プロパティと属性でのテキスト補間

プロパティと属性にテキスト補間構文を使用できる。この構文を使用すると、Angular は代入をプロパティバインディングとして扱う。つまり、角括弧[] が必要ない。

<img src="profile-photo.jpg" alt="Profile photo of {{ firstName }}" >
<button attr.aria-label="Save changes to {{ objectType }}">

CSS クラスとスタイルプロパティのバインディング

Angular は、要素に CSS クラスと CSS スタイルプロパティへのバインドをサポートしている。

CSS クラス

CSSクラスバインディングを使用して、バインドされた値の真偽値によって、要素に CSS クラスを追加または削除できる。

<ul [class.expanded]="isExpanded">

class プロパティに直接バインドすることもできる。

@Component({
    template: `
        <ul [class]="listClasses"> ... </ul>
        <section [class]="sectionClasses"> ... </section>
        <button [class]="buttonClasses"> ... </button>
    `,
    ...
})
export class UserProfile {
    listClasses = 'full-width outlined';
    sectionClasses = ['expandable', 'elevated'];
    buttonClasses = {
        highlighted: true,
        embiggened: false,
    };
}

スタイルプロパティ

要素に直接 CSS スタイルプロパティをバインドできる。

<section [style.display]="isExpanded ? 'block' : 'none'">

単位を受け付ける CSS プロパティについては、さらに単位を指定できる。

<section [style.height.px]="sectionHeightInPixels">

複数のスタイル値を1つのバインディングで設定もできる。

@Component({
    template: `
        <ul [style]="listStyles"> ... </ul>
        <section [style]="sectionStyles"> ... </section>
    `,
    ...
})
export class UserProfile {
    listStyles = 'display: flex; padding: 8px';
    sectionStyles = {
        border: '1px solid black',
        'font-weight': 'bold',
    };
}

イベントリスナーの追加

イベント名とイベントが発生するたびに実行したい処理をを括弧で囲むことで、テンプレート内の要素にイベントリスナーを定義することをサポートしている。

ネイティブイベントのリスナー

HTML 要素にイベントリスナーを追加するときは、イベント名を括弧 () で囲み、イベントが起きた時に呼び出したい処理を指定する。

@Component({
    template: `
        <input type="text" (keyup)="updateField()" />
    `,
    ...
})
export class AppComponent{
    updateField(): void {
        console.log('Field is updated!');
    }
}

イベント変数へのアクセス

すべてのイベントリスナーで、イベントオブジェクトへの参照を含む $event という名前の変数を使用できる。

@Component({
    template: `
        <input type="text" (keyup)="updateField($event)" />
    `,
    ...
})
export class AppComponent {
    updateField(event: KeyboardEvent): void {
        console.log(`The user pressed: ${event.key}`);
    }
}

双方向バインディング

双方向バインディングは、要素に値をバインドすると同時に、その要素が変更をバインディングを通じて伝播できるようにする、簡潔な方法のこと。

構文

双方向バインディングの構文は、角括弧 [] と丸括弧 () を組み合わせた [()]。これはプロパティバインディングの構文 [] とイベントバインディングの構文 () を組み合わせたもの。Angularコミュニティでは、この構文を非公式に「バナナインボックス」と呼んでいる。

フォームにおける双方向バインディング

ユーザーがコントロールを操作したときに、コンポーネントデータとフォームコントロールを同期させるために、双方向バインディングを頻繁に使用する。たとえば、ユーザーがテキスト入力に入力すると、コンポーネントの状態が更新される。

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
    imports: [FormsModule],
    template: `
        <main>
            <h2>Hello {{ firstName }}!</h2>
            <input type="text" [(ngModel)]="firstName" />
        </main>
  `
})
export class AppComponent {
    firstName = 'Ada';
}

コンポーネント間における双方向バインディング

親コンポーネントと子コンポーネント間の双方向バインディングを活用するには、フォーム要素と比較して、より多くの設定が必要になる。

親コンポーネント
// ./app.component.ts
import { Component } from '@angular/core';
import { CounterComponent } from './counter/counter.component';
@Component({
    selector: 'app-root',
    imports: [CounterComponent],
    template: `
        <main>
            <h1>Counter: {{ initialCount }}</h1>
            <app-counter [(count)]="initialCount"></app-counter>
        </main>
    `,
})
export class AppComponent {
    initialCount = 18;
}
子コンポーネント
// './counter/counter.component.ts';
import { Component, model } from '@angular/core';
@Component({
    selector: 'app-counter',
    template: `
        <button (click)="updateCount(-1)">-</button>
        <span>{{ count() }}</span>
        <button (click)="updateCount(+1)">+</button>
    `,
})
export class CounterComponent {
    count = model<number>(0);
    updateCount(amount: number): void {
        this.count.update(currentCount => currentCount + amount);
    }
}

子コンポーネントは model プロパティを含んでいる必要がある。親コンポーネントは子コンポーネントの model を双方向バインディング構文で囲み、プロパティまたはシグナルを割り当てる。

デコレーターベースも紹介する。子コンポーネントは、@Input() プロパティと @Input() プロパティと同じ名前で、最後に「Change」が追加された対応する @Output() イベントエミッターが必要となる。

親コンポーネント
// ./app.component.ts
import { Component } from '@angular/core';
import { CounterComponent } from './counter/counter.component';
@Component({
    selector: 'app-root',
    imports: [CounterComponent],
    template: `
        <main>
            <h1>Counter: {{ initialCount }}</h1>
            <app-counter [(count)]="initialCount"></app-counter>
        </main>
    `,
})
export class AppComponent {
    initialCount = 18;
}
子コンポーネント
// './counter/counter.component.ts';
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
    selector: 'app-counter',
    template: `
        <button (click)="updateCount(-1)">-</button>
            <span>{{ count }}</span>
        <button (click)="updateCount(+1)">+</button>
    `,
})
export class CounterComponent {
    @Input() count: number;
    @Output() countChange = new EventEmitter<number>();
    updateCount(amount: number): void {
        this.count += amount;
        this.countChange.emit(this.count);
    }
}

制御フロー

Angularテンプレートは制御フローブロックをサポートしており、要素の表示、非表示、繰り返しを行うことができる。

@if @else if @else

@if ブロックは、条件式が真の場合に、そのコンテンツを表示する。

@if (a > b) {
    <p>{{a}} is greater than {{b}}</p>
}

代替コンテンツを表示したい場合は、任意の数の @else if ブロックと単一の @else ブロックを指定できる。

@if (a > b) {
    {{a}} is greater than {{b}}
} @else if (b > a) {
    {{a}} is less than {{b}}
} @else {
    {{a}} is equal to {{b}}
}

条件式の参照

@if の条件式は、ブロック内で再利用できるように、結果を変数に保存できる。

@if (user.profile.settings.startDate; as startDate) {
    {{ startDate }}
}

@for

@for ブロックは、コレクションをループし、ブロックのコンテンツを繰り返しレンダリングする。 コレクションは、任意の JavaScript の反復可能オブジェクトにできるが、Angularは Array に対して追加のパフォーマンス最適化を行っている。

@for (item of items; track item.id) {
    {{ item.name }}
}

continue や break はサポートしていない

track

track を使うと、Angularは「どのデータアイテムがどの DOM ノードに対応しているか」を正確に把握でき、データが更新されたときでも必要な変更だけを DOM に適用する。たとえばアイテムの追加や削除、順番の入れ替えがあっても、全体の DOM を再作成するのではなく、変更があった部分だけを効率的に更新する。そのため、アプリケーションの描画パフォーマンスが大幅に向上する。具体的には、track にはコレクションの中でアイテムを一意に特定できるプロパティ(一般的に id や uuid など)を指定することが推奨される。一意の識別子が用意されていない場合は、代わりに要素のインデックス($index)を使用することもできるが、これはコレクションが固定されている場合に限られる。

React のキーと同じ役割

@for ブロック内のコンテキスト変数

@for ブロック内では、いくつかの変数が使用できる。

変数 意味
$count 反復処理されたコレクション内のアイテム数
$index 現在の行のインデックス(0から始まる)
$first 現在の行が最初の行かどうか(true / false
$last 現在の行が最後の行かどうか(true / false
$even 現在の行インデックスが偶数かどうか(true / false
$odd 現在の行インデックスが奇数かどうか(true / false

@empty

@empty ブロックのコンテンツは、コレクションが空の場合に表示される。

@for (item of items; track item.name) {
    <li> {{ item.name }}</li>
} @empty {
    <li aria-hidden="true"> There are no items.</li>
}

@switch

@switch ブロックは条件付きでデータをレンダリングするための代替構文。

@switch (userPermissions) {
    @case ('admin') {
        <app-admin-dashboard />
    }
    @case ('reviewer') {
        <app-reviewer-dashboard />
    }
    @case ('editor') {
        <app-editor-dashboard />
    }
    @default {
        <app-viewer-dashboard />
    }
}

パイプ

テンプレート内で表示するデータを、「見た目」や「形式」の観点から変換・加工する仕組み。コンポーネントが持つ生データを、テンプレートで表示する直前にユーザーが見やすい形式に整える役割を担う。

import { Component } from '@angular/core';
import { CurrencyPipe, DatePipe, TitleCasePipe } from '@angular/common';
@Component({
    selector: 'app-root',
    imports: [CurrencyPipe, DatePipe, TitleCasePipe],
    template: `
        <main>
            <h1>Purchases from {{ company | titlecase }} on {{ purchasedOn | date }}</h1>
            <p>Total: {{ amount | currency }}</p>
        </main>
    `,
})
export class ShoppingCartComponent {
    amount = 123.45;
    company = 'acme corporation';
    purchasedOn = '2024-07-08';
}
<main>
    <h1>Purchases from Acme Corporation on Jul 8, 2024</h1>
    <p>Total: $123.45</p>
</main>

パイプの使用

パイプ演算子はテンプレート式内で縦棒文字 | を使用する。これは、Unix パイプから着想を得ている。

<p>Total: {{ amount | currency }}</p>

複数のパイプを組み合わせる

複数のパイプ演算子を使用することで、値に複数の変換を適用できる。

<p>The event will occur on {{ scheduledOn | date | uppercase }}.</p>

パイプにパラメーターを渡す

一部のパイプに、変換をするためのパラメータを渡すことができる。パラメータを指定するには、パイプ名の後にコロン :とパラメータ値を付ける。

<p>The event will occur at {{ scheduledOn | date:'hh:mm' }}.</p>
<p>The event will occur at {{ scheduledOn | date:'hh:mm':'UTC' }}.</p>

DatePipe に、日付を特定の方法でフォーマットするためのパラメータを渡すことができる。また、複数のパラメーターを渡す場合、コロン : で区切って指定する。

パイプの優先順位

パイプ演算子は、+, -, *, /, %, &&, ||, ?? などの他の二項演算子よりも優先順位が低い。そして、三項演算子よりも優先順位が高い。

組み込みパイプ

組み込みのパイプが提供されている。

パイプ名 説明
AsyncPipe Promise または Observable から値を読み取り、テンプレートに表示する。
CurrencyPipe ロケールルールに従って、数値を通貨表記の文字列に変換する。
DatePipe Date 値をロケールに従った日付形式の文字列に変換する。
DecimalPipe 数値をロケールに従って、小数点を含む形式の文字列に変換する。
I18nPluralPipe 値に応じて複数形の文字列をロケールに従って選択する。
I18nSelectPipe 指定したキーに応じて、カスタムな文字列を選択して表示する。
JsonPipe オブジェクトを JSON.stringify() による文字列で表示(デバッグ用)。
KeyValuePipe オブジェクトや Map をキーと値のペアの配列に変換する。
LowerCasePipe 文字列をすべて小文字に変換する。
PercentPipe 数値をロケールに従ってパーセント表記の文字列に変換する。
SlicePipe 配列や文字列の一部(スライス)を抽出して新しい配列・文字列を作成する。
TitleCasePipe 各単語の先頭文字を大文字にしてタイトルケースに変換する(英語のみ対応)。
UpperCasePipe 文字列をすべて大文字に変換する。

カスタムパイプの作成

@Pipe デコレーターを使用して TypeScript クラスを実装することで、カスタムパイプを定義できる。パイプには次の2つの要素が必要となる。

  • パイプデコレーターで指定された名前
  • 値を変換するtransformという名前のメソッド

さらに PipeTransform インターフェースを実装して、パイプの型シグネチャを満たす必要がある。

kebab-case.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
    name: 'kebabCase',
})
export class KebabCasePipe implements PipeTransform {
    transform(value: string): string {
        return value.toLowerCase().replace(/ /g, '-');
    }
}

パラメーターを追加する

transform メソッドに引数を追加することで、パラメーターを追加することができる。

import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
    name: 'myCustomTransformation',
})
export class MyCustomTransformationPipe implements PipeTransform {
    transform(value: string, format: string): string {
        let msg = `My custom transformation of ${value}.`
        if (format === 'uppercase') {
            return msg.toUpperCase()
        } else {
            return msg
        }
    }
}

テンプレートフラグメント

テンプレートフラグメントとは、Angular のテンプレート内で「すぐには画面に表示しないけれど、あとで必要に応じて動的に表示したり、コードで制御して使い回したりできるテンプレートの一部分」 を指す。<ng-template> 要素は、そのテンプレートフラグメントを定義するためのタグ。

テンプレートフラグメントの作成

<ng-template> 要素を使用して、テンプレート内のどこにでもテンプレートフラグメントを作成できる。

<p>これは通常の要素です</p>
<ng-template>
    <p>これはテンプレートフラグメントです</p>
</ng-template>

上記のコードをレンダリングすると、<ng-template> 要素の内容はページにレンダリングされない。代わりに、テンプレートフラグメントへの参照を取得して、コードを記述して動的にレンダリングできる。

テンプレートフラグメントの参照

テンプレートフラグメントへの参照は、いくつか方法で取得できる。

テンプレート参照変数を使う

テンプレート参照変数を <ng-template> 要素に追加することで、同じテンプレートファイル内の他の部分でそのテンプレートフラグメントを参照できる。

<p>これは通常の要素です</p>
<ng-template #myFragment>
    <p>これはテンプレートフラグメントです</p>
</ng-template>

クエリを使う

ビュークエリを使用して、テンプレートフラグメントへの参照を取得できる。たとえば、テンプレートにテンプレートフラグメントが1つだけ含まれている場合は、@ViewChild クエリを使用して TemplateRef オブジェクトを直接クエリできる。

@Component({
    /* ... */,
    template: `
        <p>これは通常の要素です</p>
        <ng-template>
            <p>これはテンプレートフラグメントです</p>
        </ng-template>
    `,
})
export class ComponentWithFragment {
    @ViewChild(TemplateRef) myFragment: TemplateRef<unknown> | undefined;
}

テンプレートに複数のフラグメントが含まれている場合は、各 <ng-template> 要素にテンプレート参照変数を追加して、その名前でフラグメントをクエリすることで、各フラグメントを参照することができる。

@Component({
    /* ... */,
    template: `
        <p>これは通常の要素です</p>
        <ng-template #fragmentOne>
            <p>これはテンプレートフラグメントの 1 つです</p>
        </ng-template>
        <ng-template #fragmentTwo>
            <p>これは別のテンプレートフラグメントです</p>
        </ng-template>
    `,
})
export class ComponentWithFragment {
    @ViewChild('fragmentOne', {read: TemplateRef}) fragmentOne: TemplateRef<unknown> | undefined;
    @ViewChild('fragmentTwo', {read: TemplateRef}) fragmentTwo: TemplateRef<unknown> | undefined;
}

テンプレートフラグメントの注入

ディレクティブは、そのディレクティブが <ng-template> 要素に直接適用されている場合、TemplateRef を注入できる。

@Directive({
    selector: '[myDirective]'
})
export class MyDirective {
    private fragment = inject(TemplateRef);
}
<ng-template myDirective>
    <p>これはテンプレートフラグメントの 1 つです</p>
</ng-template>

レンダリング

TemplateRef オブジェクトへの参照を取得したら、NgTemplateOutlet ディレクティブを使用してテンプレート内でフラグメントをレンダリングするか、ViewContainerRef を使用して TypeScript コード内でフラグメントをレンダリングできる。

NgTemplateOutlet を使う

NgTemplateOutlet ディレクティブは、テンプレート参照変数を受け取り、そのテンプレートフラグメントの内容を表示箇所にレンダリングする。これにより、あらかじめ <ng-template> 要素で定義したテンプレートの中身を、任意の場所に動的に挿入して表示可能。通常、NgTemplateOutlet<ng-container> 要素に対して使う。<ng-container> はレンダリング時に実際の DOM 要素を生成しないため、不要なラッパー要素を増やすことなくテンプレートを挿入できる。

<p>これは通常の要素です</p>
<ng-template #myFragment>
    <p>これはフラグメントです</p>
</ng-template>
<ng-container *ngTemplateOutlet="myFragment"></ng-container>
<p>これは通常の要素です</p>
<p>これはフラグメントです</p>

ViewContainerRef を使う

コンポーネントやディレクティブで ViewContainerRef を注入し、その createComponent メソッドを使って、指定した場所にコンポーネントを挿入できる。ViewContainerRef で新しいコンポーネントを作成すると、 Angularはそのコンポーネントを、次の兄弟としてDOMに追加する。

@Component({
    /* ... */,
    selector: 'component-with-fragment',
    template: `
        <h2>フラグメントを含むコンポーネント</h2>
        <ng-template #myFragment>
            <p>これがフラグメントです</p>
        </ng-template>
        <my-outlet [fragment]="myFragment" />
    `,
})
export class ComponentWithFragment { }
@Component({
    /* ... */,
    selector: 'my-outlet',
    template: `<button (click)="showFragment()">表示</button>`,
})
export class MyOutlet {
    private viewContainer = inject(ViewContainerRef);
    fragment = input<TemplateRef<unknown> | undefined>();
    showFragment() {
        if (this.fragment()) {
            this.viewContainer.createEmbeddedView(this.fragment());
        }
    }
}
<component-with-fragment>
    <h2>フラグメントを含むコンポーネント>
    <my-outlet>
        <button>表示</button>
    </my-outlet>
    <p>これがフラグメントです</p>
</component-with-fragment>

レンダリング時にパラメーターを渡す

<ng-template> を使用してテンプレートフラグメントを宣言する際、フラグメントで受け入れられるパラメータを宣言できる。各パラメータは、let- プレフィックスを付けた属性として記述する。

NgTemplateOutlet の場合
<ng-template #myFragment let-pizzaTopping="topping">
  <p>選択したのは: {{pizzaTopping}}</p>
</ng-template>
<ng-container
  [ngTemplateOutlet]="myFragment"
  [ngTemplateOutletContext]="{topping: '玉ねぎ'}"
/>
ViewContainerRef の場合
this.viewContainer.createEmbeddedView(this.myFragment, {topping: '玉ねぎ'});

要素のグループ化

<ng-container> はAngular の特別な要素で、複数の要素をグループ化したり、DOM に実際の要素をレンダリングせずにテンプレート内の場所をマークしたりできる。

<!-- コンポーネントテンプレート -->
<section>
    <ng-container>
        <h3>User bio</h3>
        <p>Here's some info about the user</p>
    </ng-container>
</section>
<!-- レンダリングされた DOM -->
<section>
    <h3>User bio</h3>
    <p>Here's some info about the user</p>
</section>

コンポーネントのレンダリング

<ng-container> は動的なコンテンツをレンダリングするためのプレースホルダーとして機能する。NgComponentOutlet ディレクティブを使用して、<ng-container> の場所にコンポーネントを動的にレンダリングできる。

@Component({
    template: `
        <h2>Your profile</h2>
        <ng-container [ngComponentOutlet]="profileComponent()" />
    `
})
export class UserProfile {
    isAdmin = input(false);
    profileComponent = computed(() => this.isAdmin() ? AdminProfile : BasicUserProfile);
}

テンプレートフラグメントのレンダリング

NgTemplateOutlet ディレクティブを使用して、<ng-container> の場所にテンプレートフラグメントを動的にレンダリングできる。

@Component({
    template: `
        <h2>Your profile</h2>
        <ng-container [ngTemplateOutlet]="profileTemplate()" />
        <ng-template #admin>This is the admin profile</ng-template>
        <ng-template #basic>This is the basic profile</ng-template>
    `
})
export class UserProfile {
    isAdmin = input(false);
    adminTemplate = viewChild('admin', {read: TemplateRef});
    basicTemplate = viewChild('basic', {read: TemplateRef});
    profileTemplate = computed(() => this.isAdmin() ? this.adminTemplate() : this.basicTemplate());
}

テンプレート内の変数

テンプレート内で2種類の変数宣言がある。

ローカルテンプレート変数

@let 構文を使用すると、ローカル変数を定義し、テンプレート全体で再利用できる。

@let name = user.name;
@let greeting = 'Hello, ' + name;
@let data = data$ | async;
@let pi = 3.1459;
@let coordinates = {x: 50, y: 100};
@let longExpression = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit ' +
                      'sed do eiusmod tempor incididunt ut labore et dolore magna ' +
                      'Ut enim ad minim veniam...';

参照

@let で変数を宣言したら、同じテンプレート内で再利用できる。

@let user = user$ | async;
@if (user) {
    <h1>Hello, {{user.name}}</h1>
    <user-avatar [photo]="user.photo"/>
    <ul>
        @for (snack of user.favoriteSnacks; track snack.id) {
            <li>{{snack.name}}</li>
        }
    </ul>
    <button (click)="update(user)">Update profile</button>
}

代入

@let と JavaScriptの let の主な違いは、@let は宣言後に再代入できないこと。ただし、Angular は指定された式を使用して変数の値を自動的に最新の状態に保つ。

@let value = 1;
<!-- Invalid - This does not work! -->
<button (click)="value = value + 1">Increment the value</button>

スコープ

@let 宣言は、現在のビューとその子孫にスコープされる。

@let topLevel = value;
<div>
    @let insideDiv = value;
</div>
{{topLevel}} <!-- Valid -->
{{insideDiv}} <!-- Valid -->
@if (condition) {
    {{topLevel + insideDiv}} <!-- Valid -->
    @let nested = value;
    @if (condition) {
        {{topLevel + insideDiv + nested}} <!-- Valid -->
    }
}
{{nested}} <!-- Error, not hoisted from @if -->

テンプレート参照変数

テンプレート参照変数は、テンプレート内の要素の値を参照する変数を宣言する方法。テンプレート内の要素に、ハッシュ記号 # と変数名の後に続く属性を追加することで、変数を宣言できる。

<!-- "taskInput" という名前のテンプレート参照変数を、HTMLInputElementに関連付ける -->
<input #taskInput placeholder="Enter task name">

コンポーネントに宣言した場合、変数はコンポーネントインスタンスを参照する。

<my-datepicker #startDate />

変数を <ng-template> 要素に宣言した場合、変数はテンプレートを表す TemplateRef インスタンスを参照する。

<ng-template #myFragment>
  <p>This is a template fragment</p>
</ng-template>

変数を DOM 要素に宣言した場合、HTMLElement インスタンスを参照する。

<input #taskInput placeholder="Enter task name">

クエリを使ったテンプレート参照変数の使用

テンプレート内の特定の要素をクエリする場合、その要素にテンプレート変数を宣言し、変数名に基づいて要素をクエリできる。

@Component({
    /* ... */,
    template: `<input #description value="Original description">`,
})
export class AppComponent {
    @ViewChild('description') input: ElementRef | undefined;
}

遅延読み込み

遅延可能ビュー(@defer ブロック)は、ページの初期レンダリングに厳密に必要ないコードの読み込みを遅らせることで、アプリケーションの初期バンドルサイズを削減する。これにより、多くの場合、初期読み込みが高速化される。

@defer {
    <large-component />
}

@deferブロック内のコンポーネント、ディレクティブ、パイプのコードは、別の JavaScript ファイルに分割され、残りのテンプレートがレンダリングされた後、必要な場合にのみ読み込まれる。遅延させるためには2つの条件がある。

1. スタンドアロンであること

  • 遅延させたいコンポーネント・ディレクティブ・パイプは、スタンドアロンである必要がある
  • NgModule によるグループ管理のものや、スタンドアロンでないものは遅延できない
  • その場合、@defer ブロック内にあっても、通常の初期バンドルに含まれて先に読み込まれる

2.@defer ブロックの外側で参照されていないこと

  • 同じファイル内で、@defer ブロックの外からその依存関係を使ったり参照したりしてはいけない
  • もし外から参照されていたり、ViewChild などで参照済みだと、その依存関係は初期ロードされてしまう

@defer

遅延読み込みされるコンテンツのセクションを定義するブロック。最初はレンダリングされず、指定されたトリガーが発生するか、when 条件が満たされたときに読み込まれてレンダリングされる。デフォルトでは、ブラウザの状態が idleになるとトリガーされます。

@defer {
    <large-component />
}

@placeholder

デフォルトでは、@defer ブロックはトリガーされる前にコンテンツをレンダリングしない。@placeholder は、@defer ブロックがトリガーされる前に表示するコンテンツを宣言するオプションのブロック。

@defer {
    <large-component />
} @placeholder {
    <p>プレースホルダーコンテンツ</p>
}

プレースホルダーコンテンツの最小表示時間を指定できる。これにより、遅延読み込みするコンテンツがはやく取得で着た際にプレースホルダーコンテンツがちらつくのを防ぐことができる。

@defer {
    <large-component />
} @placeholder (minimum 500ms) {
    <p>プレースホルダーコンテンツ</p>
}

@loading

遅延コンテンツが読み込まれている間に表示するコンテンツを宣言するオプションのブロック。これは、読み込みがトリガーされるとプレースホルダーコンテンツと置き換わる。

@defer {
    <large-component />
} @loading {
    <img alt="読み込み中..." src="loading.gif" />
} @placeholder {
    <p>プレースホルダーコンテンツ</p>
}

ローディングコンテンツの最小表示時間と、ローディングが開始されてローディングコンテンツを表示するまでの待機時間を指定できる。

@defer {
    <large-component />
} @loading (after 100ms; minimum 1s) {
    <img alt="読み込み中..." src="loading.gif" />
}

@error

遅延読み込みが失敗した場合に表示するオプションのブロック。

@defer {
    <large-component />
} @error {
    <p>大型コンポーネントの読み込みに失敗しました。</p>
}

on

遅延読み込みが作動するトリガーを指定する。

idle

ブラウザが requestIdleCallback に基づいてアイドル状態に達すると、遅延コンテンツを読み込む。

@defer {
    <large-cmp />
} @placeholder {
    <div>大型コンポーネントのプレースホルダー</div>
}

viewport

指定されたコンテンツがビューポートに入ると、遅延コンテンツを読み込む。デフォルトではプレースホルダーコンテンツがビューポートに入っているかどうかを監視する。

@defer (on viewport) {
    <large-cmp />
} @placeholder {
    <div>大型コンポーネントのプレースホルダー</div>
}

ビューポートに入っているかどうかが監視される要素としてテンプレート参照変数を指定できる。

<div #greeting>こんにちは!</div>
@defer (on viewport(greeting)) {
    <greetings-cmp />
}

interaction

ユーザーが click または keydown イベントを通じて指定された要素と対話すると、遅延コンテンツを読み込む。デフォルトでは、プレースホルダーが対話要素として機能する。

@defer (on interaction) {
    <large-cmp />
} @placeholder {
    <div>大型コンポーネントのプレースホルダー</div>
}

対話要素としてテンプレート参照変数を指定できる。

<div #greeting>こんにちは!</div>
@defer (on interaction(greeting)) {
    <greetings-cmp />
}

hover

マウスが mouseover イベントと focusin イベントを通じてトリガーされた領域にホバーすると、遅延コンテンツを読み込む。デフォルトでは、プレースホルダーが対話要素として機能する。

@defer (on hover) {
    <large-cmp />
} @placeholder {
    <div>大型コンポーネントのプレースホルダー</div>
}

対話要素としてテンプレート参照変数を指定できる。

<div #greeting>こんにちは!</div>
@defer (on hover(greeting)) {
    <greetings-cmp />
}

immediate

他のすべての遅延されていないコンテンツのレンダリングが完了するとすぐに遅延コンテンツを読み込む。

@defer (on immediate) {
    <large-cmp />
} @placeholder {
    <div>大型コンポーネントのプレースホルダー</div>
}

timer

指定された時間後に遅延コンテンツを読み込む。

@defer (on timer(500ms)) {
    <large-cmp />
} @placeholder {
    <div>大型コンポーネントのプレースホルダー</div>
}

when

カスタムの条件式を受け取り、真の場合に遅延コンテンツを読み込む。

@defer (when condition) {
    <large-cmp />
} @placeholder {
    <div>大型コンポーネントのプレースホルダー</div>
}

prefetch

@defer ブロックで関連付けられた JavaScript を遅延コンテンツが表示される前に読み込むことができる。これにより、リソースをより高速に利用できるようになるなど、より高度な動作が可能になる。

@defer (on interaction; prefetch on idle) {
    <large-cmp />
} @placeholder {
    <div>大型コンポーネントのプレースホルダー</div>
}

空白

Angular では、連続する空白文字を1つの空白文字にするようにしている。しかし、preserveWhitespaces: true を指定することで、空白を保持することができる。

@Component({
    /* ... */,
    preserveWhitespaces: true,
    template: `
        <p>Hello         world</p>
    `
})

絶対に必要な場合を除いて設定すべきではない。アプリケーションが遅くなる可能性がある。Angular 独自の &ngsp; も使用できるため、こちらを使ったほうが良いかも。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?