Angular Components 101 —概要
公開: 2022-03-11コンポーネントは最初からAngularで利用可能です。 ただし、多くの人は依然としてコンポーネントを誤って使用していることに気づきます。 私の仕事では、属性ディレクティブの代わりにコンポーネントを作成するなど、人々がそれらをまったく使用していないのを見てきました。 これらは、私も含めて、ジュニアとシニアのAngular開発者が同様に作成する傾向がある問題です。 したがって、この投稿は、私がAngularを学んでいたときの私のような人々を対象としています。これは、コンポーネントに関する公式ドキュメントも非公式ドキュメントも、コンポーネントの使用方法と使用時期をユースケースで説明していないためです。
この記事では、Angularコンポーネントを使用する正しい方法と間違った方法を例を挙げて特定します。 この投稿はあなたに以下についての明確な考えを与えるはずです:
- Angularコンポーネントの定義。
- 個別のAngularコンポーネントを作成する必要がある場合。 と
- 個別のAngularコンポーネントを作成するべきではない場合。
Angularコンポーネントの正しい使用法に飛び込む前に、コンポーネントの一般的なトピックに簡単に触れたいと思います。 一般的に、すべてのAngularアプリケーションには、デフォルトで少なくとも1つのコンポーネント( root component
)があります。 そこから、アプリケーションの設計方法は私たち次第です。 通常、個別のページのコンポーネントを作成すると、各ページに個別のコンポーネントのリストが保持されます。 経験則として、コンポーネントは次の基準を満たす必要があります。
- データとロジックを保持するクラスを定義する必要があります。 と
- エンドユーザーの情報を表示するHTMLテンプレートに関連付ける必要があります。
次のタスクと完了したタスクの2つのページがあるアプリケーションがあるシナリオを想像してみましょう。 [今後のタスク]ページでは、今後のタスクを表示し、それらを「完了」としてマークして、最後に新しいタスクを追加できます。 同様に、[完了したタスク]ページでは、完了したタスクを表示して「未完了」とマークできます。 最後に、ページ間を移動できるナビゲーションリンクがあります。 そうは言っても、次のページをルートコンポーネント、ページ、再利用可能なコンポーネントの3つのセクションに分割できます。
上のスクリーンショットに基づいて、アプリケーションの構造が次のようになっていることがはっきりとわかります。
── myTasksApplication ├── components │ ├── header-menu.component.ts │ ├── header-menu.component.html │ ├── task-list.component.ts │ └── task-list.component.html ├── pages │ ├── upcoming-tasks.component.ts │ ├── upcoming-tasks.component.html │ ├── completed-tasks.component.ts │ └── completed-tasks.component.html ├── app.component.ts └── app.component.html
それでは、コンポーネントファイルを上記のワイヤーフレーム上の実際の要素にリンクしましょう。
-
header-menu.component
とtask-list.component
は再利用可能なコンポーネントであり、ワイヤーフレームのスクリーンショットに緑色の境界線で表示されます。 -
upcoming-tasks.component
とcompleted-tasks.component
はページであり、上のワイヤーフレームのスクリーンショットに黄色の境界線で表示されます。 と - 最後に、
app.component
はルートコンポーネントであり、ワイヤーフレームのスクリーンショットに赤い境界線で表示されます。
つまり、コンポーネントごとに個別のロジックと設計を指定できます。 上記のワイヤーフレームに基づいて、1つのコンポーネントtask-list.component
を再利用する2つのページがあります。 問題が発生します。特定のページに表示するデータの種類をどのように指定するのでしょうか。 幸い、コンポーネントを作成するときに入力変数と出力変数を指定することもできるため、これについて心配する必要はありません。
入力変数
入力変数は、親コンポーネントから一部のデータを渡すためにコンポーネント内で使用されます。 上記の例では、 task-list.component
の2つの入力パラメーター( tasks
とlistType
)を持つことができます。 したがって、 tasks
は各文字列を別々の行に表示する文字列のリストになりますが、 listType
は今後または完了し、チェックボックスがオンになっているかどうかを示します。 以下に、実際のコンポーネントがどのように見えるかを示す小さなコードスニペットを示します。
// task-list.component.ts import { Component, Input } from '@angular/core'; @Component({ selector: 'app-task-list', templateUrl: 'task-list.component.html' }) export class TaskListComponent { @Input() tasks: string[] = []; // List of tasks which should be displayed. @Input() listType: 'upcoming' | 'completed' = 'upcoming'; // Type of the task list. constructor() { } }
出力変数
入力変数と同様に、出力変数を使用してコンポーネント間で情報を渡すこともできますが、今回は親コンポーネントに渡します。 たとえば、 task-list.component
の場合、変数itemChecked
を出力できます。 アイテムがチェックされているかチェックされていないかを親コンポーネントに通知します。 出力変数はイベントエミッターである必要があります。 以下に、コンポーネントが出力変数でどのように見えるかを示す小さなコードスニペットを示します。
// task-list.component.ts import { Component, Input, Output } from '@angular/core'; @Component({ selector: 'app-task-list', templateUrl: 'task-list.component.html' }) export class TaskListComponent { @Input() tasks: string[] = []; // List of tasks which should be displayed. @Input() listType: 'upcoming' | 'completed' = 'upcoming'; // Type of the task list. @Output() itemChecked: EventEmitter<boolean> = new EventEmitter(); @Output() tasksChange: EventEmitter<string[]> = new EventEmitter(); constructor() { } /** * Is called when an item from the list is checked. * @param selected---Value which indicates if the item is selected or deselected. */ onItemCheck(selected: boolean) { this.itemChecked.emit(selected); } /** * Is called when task list is changed. * @param changedTasks---Changed task list value, which should be sent to the parent component. */ onTasksChanged(changedTasks: string[]) { this.taskChange.emit(changedTasks); } }
task-list.component.ts
コンテンツ子コンポーネントの使用法と変数のバインド
親コンポーネント内でこのコンポーネントを使用する方法と、さまざまなタイプの変数バインディングを実行する方法を見てみましょう。 Angularでは、入力変数をバインドする方法が2つあります。プロパティを角かっこで囲む必要があることを意味する一方向のバインド[]
と、プロパティを角かっこで囲む必要があることを意味する双方向のバインドです。 [()]
。 以下の例を見て、コンポーネント間でデータを渡すことができるさまざまな方法を確認してください。
<h1>Upcoming Tasks</h1> <app-task-list [(tasks)]="upcomingTasks" [listType]="'upcoming'" (itemChecked)="onItemChecked($event)"></app-task-list>
upcoming-tasks.component.html
コンテンツ各パラメータを見てみましょう。
-
tasks
パラメータは、双方向バインディングを使用して渡されます。 つまり、tasksパラメーターが子コンポーネント内で変更された場合、親コンポーネントはそれらの変更をupcomingTasks
のTasks変数に反映します。 双方向のデータバインディングを可能にするには、テンプレート「[inputParameterName] Change」(この場合はtasksChange
)に続く出力パラメーターを作成する必要があります。 -
listType
パラメーターは、一方向バインディングを使用して渡されます。 つまり、子コンポーネント内で変更できますが、親コンポーネントには反映されません。 コンポーネント内のパラメーターに値'upcoming'
を割り当て、代わりにそれを渡すことができたので、違いはありません。 - 最後に、
itemChecked
パラメーターはリスナー関数であり、onItemCheckがtask-list.component
で実行されるたびに呼び出されます。 アイテムがチェックされている場合、$event
は値true
を保持しますが、チェックされていない場合、値false
を保持します。
ご覧のとおり、一般的に、Angularは複数のコンポーネント間で情報を渡したり共有したりするための優れた方法を提供するため、それらを使用することを恐れないでください。 それらを賢く使用し、使いすぎないようにしてください。
別のAngularコンポーネントを作成するタイミング
前述のように、Angularコンポーネントを使用することを恐れてはいけませんが、確実に賢く使用する必要があります。
では、いつAngularコンポーネントを作成する必要がありますか?
-
task-list.component
ように、コンポーネントを複数の場所で再利用できる場合は、常に別のコンポーネントを作成する必要があります。 それらを再利用可能なコンポーネントと呼びます。 - コンポーネントによって親コンポーネントが読みやすくなり、テストカバレッジを追加できるようになる場合は、別のコンポーネントの作成を検討する必要があります。 それらをコード編成コンポーネントと呼ぶことができます。
- 頻繁に更新する必要のないページの一部があり、パフォーマンスを向上させたい場合は、常に別のコンポーネントを作成する必要があります。 これは、変更検出戦略に関連しています。 それらを最適化コンポーネントと呼ぶことができます。
これらの3つのルールは、新しいコンポーネントを作成する必要があるかどうかを識別するのに役立ち、コンポーネントの役割の明確なビジョンを自動的に提供します。 理想的には、コンポーネントを作成するときに、アプリケーション内でのその役割がどのようなものになるかをすでに知っている必要があります。
再利用可能なコンポーネントの使用法についてはすでに見てきたので、コード編成コンポーネントの使用法を見てみましょう。 登録フォームがあり、フォームの下部に利用規約のボックスがあるとします。 通常、リーガルは非常に大きくなる傾向があり、多くのスペースを占有します。この場合は、HTMLテンプレートです。 それでは、この例を見て、それをどのように変更できるかを見てみましょう。
最初に、登録フォームや利用規約自体を含むすべてを保持する1つのコンポーネントregistration.component
があります。
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <div class="terms-and-conditions-box"> Text with very long terms and conditions. </div> <button (click)="onRegistrate()">Registrate</button>
テンプレートは小さく見えますが、 「非常に長い契約条件のテキスト」を1000語を超える実際のテキストに置き換えると、ファイルの編集が難しく不快になります。 そのための迅速な解決策があります。新しいコンポーネントterms-and-conditions.component
発明することができます。これは、契約条件に関連するすべてのものを保持します。 それでは、 terms-and-conditions.component
のHTMLファイルを見てみましょう。

<div class="terms-and-conditions-box"> Text with very long terms and conditions. </div>
これで、 registration.component
を調整し、その中terms-and-conditions.component
を使用できます。
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <app-terms-and-conditions></app-terms-and-conditions> <button (click)="onRegistrate()">Registrate</button>
おめでとう! registration.component
のサイズを数百行減らし、コードを読みやすくしました。 上記の例では、コンポーネントのテンプレートに変更を加えましたが、同じ原則をコンポーネントのロジックに適用できます。
最後に、最適化コンポーネントについては、変更の検出を理解するために必要なすべての情報と、それを適用できる特定のケースを提供するため、この投稿を読むことを強くお勧めします。 頻繁に使用することはありませんが、場合によっては、必要のないときに複数のコンポーネントの定期的なチェックをスキップできる場合は、パフォーマンスの面でメリットがあります。
そうは言っても、必ずしも個別のコンポーネントを作成する必要はないので、個別のコンポーネントを作成しないようにする必要がある場合を見てみましょう。
個別のAngularコンポーネントの作成を回避する場合
別のAngularコンポーネントを作成することを提案する3つの主要なポイントがありますが、別のコンポーネントを作成しない場合もあります。
繰り返しになりますが、個別のコンポーネントを作成すべきでない場合を簡単に理解できる箇条書きを見てみましょう。
- DOM操作用のコンポーネントを作成しないでください。 それらについては、属性ディレクティブを使用する必要があります。
- コードが混乱する場合は、コンポーネントを作成しないでください。 これは、コード編成コンポーネントの反対です。
それでは、詳しく見て、例で両方のケースを確認してみましょう。 クリックされたときにメッセージをログに記録するボタンが必要だと想像してみてください。 これは誤っている可能性があり、特定のボタンとアクションを保持するこの特定の機能用に別のコンポーネントが作成される可能性があります。 まず、間違ったアプローチを確認しましょう。
// log-button.component.ts import { Component, Input, Output } from '@angular/core'; @Component({ selector: 'app-log-button', templateUrl: 'log-button.component.html' }) export class LogButtonComponent { @Input() name: string; // Name of the button. @Output() buttonClicked: EventEmitter<boolean> = new EventEmitter(); constructor() { } /** * Is called when button is clicked. * @param clicked - Value which indicates if the button was clicked. */ onButtonClick(clicked: boolean) { console.log('I just clicked a button on this website'); this.buttonClicked.emit(clicked); } }
log-button.component.ts
したがって、このコンポーネントには次のhtmlビューが付属しています。
<button (click)="onButtonClick(true)">{{ name }}</button>
log-button.component.html
ご覧のとおり、上記の例は機能し、ビュー全体で上記のコンポーネントを使用できます。 技術的には、あなたが望むことをするでしょう。 ただし、ここでの正しい解決策は、ディレクティブを使用することです。 これにより、作成する必要のあるコードの量を減らすだけでなく、ボタンだけでなく、必要な要素にこの機能を適用する可能性を追加することもできます。
import { Directive } from '@angular/core'; @Directive({ selector: "[logButton]", hostListeners: { 'click': 'onButtonClick()', }, }) class LogButton { constructor() {} /** * Fired when element is clicked. */ onButtonClick() { console.log('I just clicked a button on this website'); } }
logButton
ディレクティブ。任意の要素に割り当てることができますこれで、ディレクティブを作成したら、アプリケーション全体でそれを使用して、必要な要素に割り当てることができます。 たとえば、 registration.component
を再利用しましょう。
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <app-terms-and-conditions></app-terms-and-conditions> <button (click)="onRegistrate()" logButton>Registrate</button>
logButton
ディレクティブここで、個別のコンポーネントを作成するべきではない2番目のケースを見てみましょう。これは、コード最適化コンポーネントの反対です。 新しく作成されたコンポーネントによってコードがより複雑で大きくなる場合は、作成する必要はありません。 例として、 registration.component
を取り上げましょう。 そのようなケースの1つは、大量の入力パラメーターを使用して、ラベルと入力フィールド用に別個のコンポーネントを作成することです。 この悪い習慣を見てみましょう。
// form-input-with-label.component.ts import { Component, Input} from '@angular/core'; @Component({ selector: 'app-form-input-with-label', templateUrl: 'form-input-with-label.component.html' }) export class FormInputWithLabelComponent { @Input() name: string; // Name of the field @Input() id: string; // Id of the field @Input() label: string; // Label of the field @Input() type: 'text' | 'password'; // Type of the field @Input() model: any; // Model of the field constructor() { } }
form-input-with-label.component
そして、これはこのコンポーネントのビューである可能性があります。
<label for="{{ id }}">{{ label }}</label><br /> <input type="{{ type }}" name="{{ name }}" [(ngModel)]="model" /><br />
form-input-with-label.component
確かに、 registration.component
でコードの量は減りますが、コードの全体的なロジックがより理解しやすく、読みやすくなりますか? コードが以前よりも不必要に複雑になっていることがはっきりとわかると思います。
次のステップ:Angularコンポーネント102?
要約すると、コンポーネントを使用することを恐れないでください。 達成したいことについて明確なビジョンを持っていることを確認してください。 上にリストしたシナリオは最も一般的なものであり、最も重要で一般的なシナリオであると私は考えています。 ただし、シナリオは一意である可能性があり、十分な情報に基づいて決定を下すのはあなた次第です。 あなたが良い決断をするのに十分なことを学んだことを願っています。
Angularの変更検出戦略とOnPush戦略について詳しく知りたい場合は、Angular変更検出とOnPush戦略を読むことをお勧めします。 これはコンポーネントと密接に関連しており、すでに投稿で述べたように、アプリケーションのパフォーマンスを大幅に向上させることができます。
コンポーネントはAngularが提供するディレクティブの一部にすぎないため、属性ディレクティブと構造ディレクティブについても理解しておくと便利です。 すべてのディレクティブを理解すると、プログラマーがより優れたコードを完全に記述しやすくなる可能性があります。