これは、Angular の公式ドキュメントの Attribute Directives の章 を意訳したものです。
駆け足で翻訳したので至らない点もありますが、あしからずご承知おきください。
バージョン 4.2.6 のドキュメントをベースにしています。
Attribute Directives
Attribute Directive は、DOM要素の見た目や動作を変更します。
ここから、Attribute Directive の example を見たり、ダウンロード ができます。
Directive(ディレクティブ)の概要
Angular には、3種類のDirectiveがあります。
- Components ... テンプレートの使用したDirective
- Structural Directives(構造ディレクティブ) ... DOM要素を追加・削除してDOMレイアウトを変更できるDirective
- Attribute Directive(属性ディレクティブ) ... 要素、コンポーネント、または別のディレクティブの外観や動作を変更できるDirective
Components は3種類のディレクティブの中で、最も一般的に使われます。
QuickStart Guide でコンポーネントを初めて見たことがありますよね?
構造ディレクティブは、ビューの構造を変更します。
2つ例を挙げると NgFor
と NgIf
です。Structural Directives のガイドでそれらについて学んでください。
属性ディレクティブは、要素の属性として使用されます。例えば Template Syntax ガイドの組み込み NgStyle
ディレクティブは、同時に複数の要素スタイルを変更することができます。
シンプルな属性ディレクティブを作ってみよう
attribute directive では、属性を識別するセレクタを指定する @Directive
でアノテーションが付けられたコントローラクラスを最低限構築する必要があります。
コントローラクラスで必要なディレクティブの振る舞いを実装します。
このページでは、ユーザーがその要素の上をhoverしたときに要素の背景色を設定する、シンプルな myHighlight
属性ディレクティブを構築する方法を記載しています。
これは下記のように書くことができます:
src/app/app.component.html(適用済み)
<p myHighlight>Highlight me!</p>
ディレクティブのコードを書く
setup の手順に沿って、属性ディレクティブという名前の新しいローカルプロジェクトを作成します。
指定されたフォルダに次のソースファイルを作成します。
src/app/highlight.directive.ts
import { Directive, ElementRef, Input } from '@angular/core';
@Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
}
import
ステートメントは、Angular コアのシンボルを指定します。
-
Directive
は@Directive
デコレータの機能を提供します。 -
ElementRef
は、コードがDOM要素にアクセスできるように、ディレクティブのコンストラクタに記述します。 -
Input
で、データがバインディング式からディレクティブに流れるようにします。
次に、@Directive
デコレータ関数には、構成オブジェクトのディレクティブメタデータが引数として含まれています。
@Directive
は、ディレクティブに関連付けられているテンプレート内のHTMLを識別するためにCSSセレクタを必要とします。
属性のCSSセレクタは、角括弧内の属性名です。ここでのディレクティブのセレクタは [myHighlight]
です。
Angularは、テンプレート内で myHighlight
という名前の属性を持つすべての要素を検索します。
ディレクティブ名を
highlight
としないのはなぜですか?
highlight
はmyHighlight
よりも簡潔な名前ですし、実際動作もするはずです。ただし、セレクタ名にプレフィックスを付けて、標準のHTML属性とコンフリクトしないよう、未然に防ぐことをお勧めします。また、
highlight
ディレクティブ名には、ngをつけないようにしてください。ng
接頭辞はAngularのために予約されており、使用すると調査が難しいバグが発生する可能性があるからです。
今回のようなシンプルなデモでは、短い接頭辞my
がカスタムディレクティブとして区別するのに役立ちます。
@Directive
メタデータの後には、ディレクティブのコントローラクラス(HighlightDirective
という)があります。このクラスには、ディレクティブのロジックが含まれています。 HighlightDirective
をエクスポートすると、他のコンポーネントからアクセスできるようになります。
Angular は、一致する要素ごとにディレクティブのコントローラクラスの新しいインスタンスを作成し、コンストラクタにAngular ElementRef
を注入します。
ElementRef
は、nativeElement
プロパティを使用してDOM要素に直接アクセスするサービスです。
属性ディレクティブを適用する
新しい HighlightDirective
を使用するには、ディレクティブを属性として段落(<p>
)要素に適用するテンプレートを作成します。 Angularの場合、<p>
要素は属性ホストです。
テンプレートを次のような app.component.html
ファイルに入れます。
src/app/app.component.html
<h1>My First Attribute Directive</h1>
<p myHighlight>Highlight me!</p>
今度は、このテンプレートを AppComponent
で参照してください:
src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html'
})
export class AppComponent {
color: string;
}
次に、import
ステートメントを追加して Highlight
ディレクティブをフェッチし、そのクラスを宣言の NgModule
メタデータに追加します。このようにして、Angular はテンプレート内で myHighlight
を検出したときにディレクティブを認識します。
src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HighlightDirective } from './highlight.directive';
@NgModule({
imports: [ BrowserModule ],
declarations: [
AppComponent,
HighlightDirective
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
アプリケーションが実行されると、myHighlight
ディレクティブは段落テキストを強調表示します。
作ったディレクティブが動かない?
@NgModule
の宣言属性にディレクティブを追加することを忘れていませんか?
忘れているかどうか確認するのは簡単です!開発者ツールでコンソールを開き、次のようなエラーを調べてください:EXCEPTION: Template parse errors: Can't bind to 'myHighlight' since it isn't a known property of 'p'.
Angular は何かにバインドしようとしていますが、このディレクティブはモジュールの
declarations
配列で見つけることができません。
declarations
の配列でHighlightDirective
を指定すると、このモジュールで宣言されたコンポーネントにディレクティブを適用できることがわかります。
まとめると、Angularは <p>
要素の myHighlight
属性を検出しました。
れは HighlightDirective
クラスのインスタンスを作成し、<p>
要素の背景スタイルを黄色に設定するディレクティブのコンストラクタに <p>
要素への参照を inject しました。
ユーザーが開始したイベントに対応する
現在、myHighlight
は要素の色を設定しています。このディレクティブはより動的になる可能性があります。これは、ユーザーが要素の中に出入りするときを検出し、ハイライトカラーを設定またはクリアすることによって応答することができます。
src/app/highlight.directive.ts (imports)
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
次に、マウスが入ったり出たりするときに応答する2つのイベントハンドラを追加します。それぞれのイベントハンドラは、HostListener
デコレータによって装飾されます。
@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
@HostListener
デコレータを使用すると、属性ディレクティブ(この場合は <p>
)をホストするDOM要素のイベントにサブスクライブできます。
もちろん、標準のJavaScriptを使用してDOMにアクセスし、イベントリスナーを手動でアタッチすることができます。そのアプローチには少なくとも3つの問題があります。
- リスナーを正しく記述する必要があります。
- メモリーリークを避けるため、ディレクティブが破棄されたときにリスナーを切り離す処理を記述しなければなりません。
- DOM APIに直接アクセスすることは、ベストプラクティスではありません。
ハンドラは、コンストラクタで宣言して初期化するDOM要素の色 el
を設定するヘルパーメソッドに委譲します。
src/app/highlight.directive.ts (constructor)
constructor(private el: ElementRef) { }
追記されたディレクティブが全部ここにあります:
src/app/highlight.directive.ts
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[myHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) { }
@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
アプリを実行して、マウスが <p>
の上に移動して外に出るときに消えたら、背景色が表示されることを確認します。
@Input データバインディングでディレクティブに値を渡す
現在、強調表示の色はディレクティブの記述内でハードコードされています。それは柔軟性がありません。このセクションでは、ディレクティブを適用しながらハイライトカラーを設定する権限を開発者に与える方法をご紹介します。
まず、highlightColor
プロパティを次のようにディレクティブクラスに追加します。
src/app/highlight.directive.ts (highlightColor)
@Input() highlightColor: string;
@Input プロパティへのバインディング
@Input
デコレータに注目してください。ディレクティブの highlightColor
プロパティをバインディングに使用できるように、クラスにメタデータを追加します。
これは、データがバインディング式からディレクティブに流れるため、入力プロパティと呼ばれます。その入力メタデータがなければ、Angularはバインディングを拒否します。詳細は以下を参照してください。
AppComponent
テンプレートに以下のディレクティブバインディングバリエーションを追加して試してください:
src/app/app.component.html (excerpt)
<p myHighlight highlightColor="yellow">Highlighted in yellow</p>
<p myHighlight [highlightColor]="'orange'">Highlighted in orange</p>
AppComponent
に color
プロパティを追加します。
src/app/app.component.ts (class)
export class AppComponent {
color = 'yellow';
}
それはプロパティバインディングでハイライトの色を制御します。
src/app/app.component.html (抜粋)
<p myHighlight [highlightColor]="color">Highlighted with parent component's color</p>
それは良いことですが、指示文を同時に適用し、このような同じ属性に色を設定するとよいでしょう。
src/app/app.component.html (color)
<p [myHighlight]="color">Highlight me!</p>
[myHighlight]
属性バインディングは、強調表示ディレクティブを <p>
要素に適用し、ディレクティブのハイライトカラーをプロパティバインディングで設定します。
両方の作業を行うには、ディレクティブの属性セレクタ([myHighlight]
)を再利用しています。それは明快でコンパクトな構文です。
ディレクティブの highlightColor
プロパティの名前を myHighlight
に変更する必要があります。これは、これが color
プロパティバインディングの名前になっているためです。
src/app/highlight.directive.ts (renamed to match directive selector)
@Input() myHighlight: string;
これはイヤですね。myHighlight
という単語は恐ろしいプロパティ名であり、プロパティの意図を伝えるものではありません。
@Input エイリアスでバインディング
幸いなことに、ディレクティブプロパティに任意の名前を付けて、バインディングの目的で別名を付けることができます。
元のプロパティ名を復元し、セレクタを @Input
の引数にエイリアスとして指定します。
src/app/highlight.directive.ts (color property with alias)
@Input('myHighlight') highlightColor: string;
このディレクティブの内部では、プロパティは highlightColor
として知られています。ディレクティブの外側で、バインドする場所は、myHighlight
と呼ばれます。
ここで、両者とも都合の良いベストなパターンを教えます!
あなたが望むプロパティ名とバインディング構文:
src/app/app.component.html (color)
<p [myHighlight]="color">Highlight me!</p>
highlightColor
にバインドするので、onMouseEnter()
メソッドを変更して使用してください。誰かが highlightColor
にバインドするのを怠った場合は、赤で強調表示します:
src/app/highlight.directive.ts (mouse enter)
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor || 'red');
}
ここにディレクティブクラスの最新バージョンがあります。
src/app/highlight.directive.ts (抜粋)
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[myHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) { }
@Input('myHighlight') highlightColor: string;
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor || 'red');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
それを試すハーネスを書く
この指令がどのように実際に動作するかは想像するのが難しいかもしれません。このセクションでは、AppComponent
をハーネスに変換し、ラジオボタンでハイライトカラーを選択し、色の選択肢をディレクティブにバインドします。
app.component.html を次のように更新します。
src/app/app.component.html (v2)
<h1>My First Attribute Directive</h1>
<h4>Pick a highlight color</h4>
<div>
<input type="radio" name="colors" (click)="color='lightgreen'">Green
<input type="radio" name="colors" (click)="color='yellow'">Yellow
<input type="radio" name="colors" (click)="color='cyan'">Cyan
</div>
<p [myHighlight]="color">Highlight me!</p>
AppComponent.colorを初期値がないように修正します。
vsrc/app/app.component.ts (class)
export class AppComponent {
color: string;
}
実際のハーネスと指令があります。
2番目のプロパティにバインドする
このハイライトディレクティブには、カスタマイズ可能なプロパティが1つあります。実際のアプリでは、もっと必要な場合があります。
現時点では、ユーザがハイライトカラーを選択するまでのデフォルトカラー、つまり「赤」としてハードコードされています。テンプレートの実装担当者にデフォルトの色を設定しているカたちですね。
HighlightDirective
に defaultColor
という2番目の入力プロパティを追加します。
src/app/highlight.directive.ts (defaultColor)
@Input() defaultColor: string;
ディレクティブの onMouseEnter
を変更して、highlightColor
で強調表示し、次にdefaultColor
で強調表示し、両方のプロパティが未定義の場合は "red"
に戻します。
src/app/highlight.directive.ts (mouse-enter)
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor || this.defaultColor || 'red');
}
myHighlight
属性名にバインドしているときに、どのようにして2番目のプロパティにバインドしますか?
コンポーネントの場合と同様に、必要に応じて、テンプレート内で文字列を組み合わせることによって、多くのディレクティブプロパティバインディングを追加できます。開発者はAppComponent.color
にバインドし、デフォルトの色として "violet"
にフォールバックするために、次のテンプレートHTMLを書き込むことができます。
src/app/app.component.html (defaultColor)
<p [myHighlight]="color" defaultColor="violet">
Highlight me too!
</p>
Angularは、DefaultColor
バインディングが @Input
デコレータで公開されているため、HighlightDirective
に属していることを認識しています。
一通りのコーディングが完了したら、実際に機能するハーネスは次のとおりです。
まとめ
このページでは次の要点を解説しました。
- 要素の動作を変更する属性ディレクティブを作成します。
- ディレクティブをテンプレートの要素に適用します。
- ディレクティブの動作を変更するイベントに応答します。
- ディレクティブに値をバインドします。
最終的なソースコードは次のとおりです。
ここから、実際のソースコードを見たり、ダウンロードしたりすることもできます。
Appendix: @Inputを追加する理由
このデモでは、hightlightColor
プロパティは HighlightDirective
の入力プロパティです。エイリアスなしで適用されたことがわかりました。
src/app/highlight.directive.ts (color)
@Input() highlightColor: string;
エイリアスとしてセットするのも紹介しました:
src/app/highlight.directive.ts (color)
@Input('myHighlight') highlightColor: string;
どちらの方法でも、@Input
デコレータはAngularに、このプロパティはパブリックであり、親コンポーネントによるバインドに使用可能であることを伝えます。 @Input
を指定しない場合、Angularはプロパティにバインドすることを拒否します。
@Input
を使用する前に、テンプレートHTMLをコンポーネントプロパティにバインドしました。違いはなんでしょうか?
違いは信頼の問題です。 Angularは、コンポーネントのテンプレートをコンポーネントに属するものとして扱います。コンポーネントとそのテンプレートは、暗黙のうちに互いに信頼し合います。したがって、コンポーネント自身のテンプレートは、@Input
デコレータの有無にかかわらず、そのコンポーネントの任意のプロパティにバインドできます。
しかし、コンポーネントや指令は、他のコンポーネントや指令を盲目的に信頼すべきではありません。デフォルトでは、コンポーネントまたはディレクティブのプロパティはバインディングから隠されています。Angularバインディングの観点からは非公開です。 @Input
デコレータで装飾されると、プロパティはAngularバインディングの観点からパブリックになります。その後、他のコンポーネントやディレクティブにバインドすることができます。
@Input
がバインディング内のプロパティ名の位置によって必要であるかどうかを知ることができます。
- 等号 (=) の右側のテンプレート式に現れると、それはテンプレートのコンポーネントに属し、
@Input
デコレータを必要としません。 - 等号 (=) の左を角括弧([])で囲っている場合、このプロパティは他のコンポーネントまたはディレクティブに属します。そのプロパティは
@Input
デコレータで飾られなければなりません。
次に、その推論を次の例に適用します。
src/app/app.component.html (color)
<p [myHighlight]="color">Highlight me!</p>
- 右側の式の
color
プロパティはテンプレートのコンポーネントに属します。テンプレートとそのコンポーネントはお互いを信頼します。color
プロパティは@Input
デコレータを必要としません。 - 左側の
myHighlight
プロパティは、テンプレートのコンポーネントのプロパティではなく、HighlightDirective
のエイリアス化されたプロパティを参照します。信頼の問題があります。したがって、ディレクティブプロパティは@Input
デコレータを保持する必要があります。