Angularコンポーネント

Angularとは?

TypeScriptで実装されたNode.jsを拡張したフレームワーク。Node.jsの拡張では、Angularの他に、ReactやVueがあります。

Angularのドキュメントや実装を読み解くにあたって、見ただけでは意味のわからない用語や記号等が多くあり、ドキュメントを探すのも一苦労です。

本記事ではつまずきやすいポイントについて、Angularのどのドキュメントと紐づけ、逆引き的に使用できるナレッジとしてまとめます。

ディレクティブ

AngularのHTML内での独自の記述方法をディレクティブと呼ぶ。

ただし、一口にディレクティブと言っても以下に分類される用途があり、シーンによって使い方が全く異なるため、ドキュメントを読む際は注意する。

  • コンポーネント:コンポーネント単位でHTMLを記述し、親子関係を作ることで使いまわせるようにする。
  • 属性ディレクティブ:CSSをコントロールする。
  • 構造ディレクティブ:DOM構造を操作する。ngIfやngForなど

ng-container

フラグメント。このタグ自身は描画 (レンダリング)されない。 構造ディレクティブ (*ngIfとか*ngFor)とともに用いられることが多い。

<ng-container *ngFor="aminals">
  <span>{{aminal.name}}</span>
</ng-container>

■ng-containerとng-template

この2つはどちらもタグとしては表示されない便利なプロパティ。
ng-templateで、使いまわすためのテンプレートを作成し、ng-containerで呼び出すのが基本。

<!--テンプレート-->
<ng-template #kurikaesi>
<dl>
  <dt>name</dt>
  <dd>hoge</dd>
</dl>
<ng-template>

<!-- テンプレートを量産-->
<ng-container *ngTemplateOutlet="kurikaesi"></ng-container>
<ng-container *ngTemplateOutlet="kurikaesi"></ng-container>
<ng-container *ngTemplateOutlet="kurikaesi"></ng-container>

■ngIfとの組み合わせ

<ng-container *ngIf="条件式; else elseBlock">条件式が正の場合の値</ng-container>
<ng-template #elseBlock>elseの場合の値</ng-template>

アノテーション

SpringFrameworkのように、アノテーションを付与してコンポーネントに性質を付与できる。

@Input

子コンポーネントのcomponent.tsに設定し、親コンポーネントのmodelにアクセスする。modelのプロパティにアクセスする場合は以下のように記載する。

■親コンポーネント.component.tsで値を定義する

propName$: Observable <T>;
  //値を storeで監視する場合はpropName$: Observable <T>とする。
  //Tはmodel で定義されている必要がある

■親コンポーネント.component.htmlから値を引き渡す

<app-confirm-communication-component
  [propForChild]="(propName$ | async)" //(propName$ | async)? とすると失敗するので注意
>

※propNameがオブジェクトである場合、さらに下位層のプロパティにアクセスするには以下のように記述する

(propObj$ | async)?.propName

■子コンポーネント.component.tsで値を受け取る

@Input()
propForChild: ParentModel Name ["prop"];
// 親コンポーネントのモデルのプロパティにアクセスしている場合は ["prop"]でプロパティまで明示する

■子コンポーネント.component.htmlでpropForChildにアクセスできる

<p>{{ propForChild }}</p>

Form(フォーム)

参考:フォーム入力の検証(Angular公式)

angularのフォームはとても難解です。しかも、記号等で制御されるため、ドキュメントを探すことも困難を極めます。

以下のinputタグを見てください。

<input
  [formControl]="addressForm"
  [class.must]="address"
  [class.error]="address.invalid || adress.touched"
  (input)="next()"
  (blur)="validText()"
>

僕らが知っているinputタグとは次元が違いますよね。

javascriptで制御していた機能をHTML側で簡潔に書けるようにした半面、知っていないと書けない専門的な記述方法となっています。

少し解説します。

formControlの使い方

以下の部分です。

<input
  [formControl]="addressForm"
>

Angularでは、フォームに名前を付け、値をリアクティブに監視します。

HTMLでformControlを使用する場合は、TSファイルでその設定を行います。

■component.ts

export class SampleComponent implements OnInit {
  address: string;
  addressForm: FormControl;
  
  constructor() {
    this.addressForm = new FormControl(this.address , {  // 第一引数…①
      validators: [ // 好きなValidatorを設定する…②
        Validators.required,
        Validators.maxLength(40)
      ],
      updateOn: 'blur' // FormHooks…③
    });
  }
}

①:フォームの値に中間処理を挟みたい場合(特定のロジックで数値を計算するなど)は、第1引数にはバインドするプロパティを指定する。
特に指定しなくても問題は無く、その場合は第一引数を『”』とする。

②:フォームの値を検査する場合には検査内容に合ったValidatorを指定する。
参考:Validators

③:FormHooks。どのタイミングでフォームの変更を感知するかを決定する。change(デフォルト), blur, submitから選択する。
changeは値が変更された時、blurはフォーカスが外れた時、submitはリアクティブである意味がないので、あまり使う機会はないと思われる。
参考:NgFormAbstractControlOptions

[class.xxx]の意味

続いて以下の部分です。

<input
  [class.must]="address"
  [class.error]="address.invalid && address.touched"
>

これは、クラスバインディングと呼ばれるものです。

classと書いてあると難しいオブジェクトの話か、と構えてしまいがちですが、CSSの方のclassですので、そんなにおびえなくて良いところです。

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

左辺がフォームに設定されるCSSクラスで、右辺は適用条件を表しています。

2行目を言葉で表すと、address.invalidがtrue、かつ、address.touchedがtrueの場合に、errorというCSSクラスをinputタグに設定する、という意味です。

そのため、styles.cssにはerrorクラスの記述が必要です。

■styles.scss

input {
  &.must {
    background-color: blue;
  }
  &.error {
    background-color: red;
  }
}

ここでもう一つ疑問として挙がるのが、右辺の条件式です。

=”address” のみであれば、addressのプロパティの有無、ということで理解できますが、address.invalid とは何でしょうか。

これは、コントロールステータスというもので、フォーム部品の状態を表しています。フォームの状態とは、フォーカスが当たっているのか、値が変更されたのか、まだ触れられていないのか、などです。

参考:AbstractControl

address.invalid について言えば、フォームコントロールの挙動で設定した、Validatorに引っかかった状態のときにTRUEとなります。

クラスバインディングを紹介してきましたが、ドキュメントを見ると、スタイルバインディングも便利そうです。3項演算子を用いて、スタイルのオン・オフを切り替えることができます。

// これだけでエリアの表示・非表示を切り替えることができる
<section [style.display]="isExpanded ? 'block' : 'none'">

// これだけでボタンの活性・非活性を切り替えることができる
<button [disabled]="isCorrect ? 'false' : 'true'">

ここまで紹介してきたAngularのリンクすべてを解説することはできませんが、これまでのお話を拡張する有益なページですので、ぜひこれらの機能を使用する際にまた見に来てもらえればと思います。

ngClassの使い方

class.xxxでCSSを分岐するクラスバインディングについて触れましたが、もう一つよく使われるCSSの分岐に、ngClassがあります。

<div class="mb-2" [ngClass]="{
  'correct': this.form.value.phoneNo === '09012345678',
  'fault': this.form.value.phoneNo !== '09012345678' 
}">

上記は、this.form.value.phoneNoの値が’09012345678’である場合にはCSSのcorrectクラス、異なる場合はfaultクラスを設定する例です。

(event)の意味

最後に、以下の部分です。

<input
  (input)="next()"
  (blur)="validText()"
>

なんとなく見て意味は分かるかと思いますが、Angularでは、イベントリスナーをHTML側で設定できるようになっています。

component.ts に、next()関数と、validText()関数を用意しておいて、eventに応じて上記のHTMLでそれを呼び出している箇所です。

参考:イベントリスナーの追加

inputタグのシャープ

<input type="text" #phoneNo>
<button (click)="onClick(phoneNo.value)">CALL</button>

inputに入力された値を、ローカル変数として、HTML内で使用できます。

普通に書くと、js側で、document.getElementBy~みたいなのを書いて値を取得するが、そういったのが不要になります。

Appendix

パイプ構文

『|』でつながれた構文をパイプ構文と呼ぶ。使いどころによって処理内容が全然変わってくるため、 Angular を難解にしているポイントの一つ。

いくつか例を紹介する。

{{ "hello" | uppercase }}  // HELLO

uppercaseは、ビルドインパイプ (Angularがデフォルトで用意しているパイプ)の1つ。文字列を大文字に変換する。

{{ observableObj | async }}

asyncもビルドインパイプの1つ。同期処理をコンポーネント側で担保できる。

observableObjをサービス側で監視・購読 (subscribe) する場合、終了後に破棄 (unsubscribe) する必要があるが、テンプレート側で自動的に管理してくれるようになる。

openWindow (url: string): Window | null

ユニオン型(orのような意味)。こちらはTypeScripteの機能で、 どちらかの型を許容するというもの。

戻り値に、 Window型と nullのいずれかが許可されていることを表す。

クエスチョンマーク

TypeScriptの機能ですが、クエスチョンマークを使用することで、プロパティがnullであった場合のエラーを回避することができます。

例えば、以下のようなオブジェクト定義では、employerがnullであった場合はエラーとなります。

Employer: {{employer.companyName}}

ですが、employer?とすると、employerは必ず取得できるプロパティではないということを示し、エラーを回避することができます。

Employer: {{employer?.companyName}}

データバインディング

Angularでは、子コンポーネントを作ることができます。

■parent.component.html

突然、以下のchildCompような見たことのないタグが出てきたら、子コンポーネントである可能性が高いです。

[fullname]の部分もややこしいですが、これはプロパティバインディングと呼ばれるもので、子コンポーネントで使用されているプロパティを、親コンポーネントから渡す方法です。

<div>…</div>
<childComp
  [fullname]="大谷しょうへい"
>

■child.component.html

<div>
  <p>僕の名前は{{ fullname }}です。</p>
</div>

■child.component.ts

Inputアノテーションを使用して、親コンポーネントから値を受け取っています。

@Input() fullname = '';

双方向バインディング

鍵カッコの中にカッコが含まれているものは、双方向バインディングというものです。

<input [(ngModel)]="message">

これにより、HTMLからTypeScriptにデータを飛ばすだけでなく、TypeScriptで更新されたデータをHTMLに同期することができます。

参考:双方向バインディング

急に出てくる三点リーダ

引数や配列に突然『…』が出てきて、何なのか困ることがあります。

これはAngularというよりjavascriptの機能で、いくつかパターンがあり、用途が変わってきます。

以下のように引数で出てくる場合は、可変長引数(残余引数・レスト構文)というものです。

function sum(...args) {
  for (const arg of args) {
    console.log(arg);
  }
}

引数に配列を取り、その配列の長さが不明確であるときに使用されます。
参考:残余引数

一方で、以下のように、配列やオブジェクトの中で見かけた場合は、スプレッド構文となり、挙動が全く異なります。

const bird = {
  wing: 2,
  fly: 'can'
}

const niwatori = {
  …bird,
  fly: 'can not'
  name: 'niwatori'
}

この例では、niwatoriは、birdのプロパティを引き継ぐことができます。Javaにおける継承の簡易版みたいなものですね。

コメント

タイトルとURLをコピーしました