「Angular」の版間の差分
(→機能) |
|||
(同じ利用者による、間の183版が非表示) | |||
1行目: | 1行目: | ||
+ | | [[AngularJS]] | [[TypeScript]] | [[Google Cloud Platform]] | [[Bootstrap]] | [[Flask]] | [https://www.typea.info/blog/index.php/category/angular/ ブログカテゴリ(Angular)] | | ||
+ | {{amazon|4774191302}} | ||
==[[Angular]]== | ==[[Angular]]== | ||
− | [ | + | ===Angularに関連するブログエントリ==== |
− | + | *[https://www.typea.info/blog/index.php/category/angular/ Angularに関連するブログエントリ] | |
+ | |||
==[[Angular]] CLI== | ==[[Angular]] CLI== | ||
===準備=== | ===準備=== | ||
11行目: | 14行目: | ||
> cd quickstart | > cd quickstart | ||
> npm install | > npm install | ||
+ | *実行 | ||
+ | > npm start | ||
+ | [[File:angular_quickstart.png]] | ||
+ | ====要素==== | ||
+ | {|class="wikitable" | ||
+ | |+ルートモジュール | ||
+ | !要素名!!説明 | ||
+ | |- | ||
+ | |コンポーネント||UI部品 | ||
+ | |- | ||
+ | |サービス||ビジネスロジック | ||
+ | |- | ||
+ | |パイプ||表示値の加工、演算 | ||
+ | |- | ||
+ | |ディレクティブ||文書ツリーの操作 | ||
+ | |} | ||
+ | |||
+ | *モジュール | ||
+ | **Angularにおけるモジュールの実態は、TypeScriptのクラス | ||
+ | **定義しただけではモジュールとみなされず、@NgModuleデコレータで宣言が必要 | ||
+ | |||
+ | ====設定ファイル==== | ||
+ | {|class="wikitable" | ||
+ | !設定ファイル!!概要 | ||
+ | |- | ||
+ | |package.json||利用するライブラリ情報 | ||
+ | |- | ||
+ | |tsconfig.json||TypeScriptコンパイラーの動作を | ||
+ | |- | ||
+ | |systemjs.config.js||モジュールローダー(SystemJS)の設定 | ||
+ | |} | ||
+ | |||
===インストール=== | ===インストール=== | ||
>[[npm]] install -g @angular/cli | >[[npm]] install -g @angular/cli | ||
18行目: | 53行目: | ||
===アプリケーションの実行=== | ===アプリケーションの実行=== | ||
> ng serve | > ng serve | ||
+ | |||
===[[Angular]] CLI の主なコマンド=== | ===[[Angular]] CLI の主なコマンド=== | ||
+ | ---- | ||
*https://github.com/angular/angular-cli/wiki | *https://github.com/angular/angular-cli/wiki | ||
{|class="wikitable" | {|class="wikitable" | ||
31行目: | 68行目: | ||
|- | |- | ||
|ビルドして起動 | |ビルドして起動 | ||
− | |ng serve | + | |[https://angular.io/cli/serve ng serve] |
|- | |- | ||
|ビルド | |ビルド | ||
61行目: | 98行目: | ||
|- | |- | ||
|} | |} | ||
− | =====ng generate サブコマンド===== | + | =====オブジェクト生成 ng generate サブコマンド===== |
+ | ---- | ||
{|class="wikitable" | {|class="wikitable" | ||
!要素 | !要素 | ||
94行目: | 132行目: | ||
|- | |- | ||
|} | |} | ||
+ | |||
+ | ==ng-bootstrap== | ||
+ | **https://ng-bootstrap.github.io/ | ||
+ | |||
==ngx-bootstrap== | ==ngx-bootstrap== | ||
*https://valor-software.com/ngx-bootstrap | *https://valor-software.com/ngx-bootstrap | ||
118行目: | 160行目: | ||
[[File:0167_bootstrap_alert.jpg]] | [[File:0167_bootstrap_alert.jpg]] | ||
+ | |||
==[[Angular]] Material== | ==[[Angular]] Material== | ||
*https://material.angular.io/ | *https://material.angular.io/ | ||
+ | |||
+ | <pre> | ||
+ | $ ng add @angular/material | ||
+ | </pre> | ||
+ | |||
+ | ===カスタムテーマ=== | ||
+ | * https://www.creative-tim.com/product/material-dashboard-angular2 | ||
+ | * [https://zenn.dev/fusho_takahashi/articles/20d044c4a2d459b5c2ca Angular Material でカスタムカラーを使う] | ||
+ | * [http://mcg.mbitson.com/#!?mcgpalette0=%233f51b5 カラーパレット生成] | ||
+ | * [https://material.io/design/color/the-color-system.html カラーシステム] | ||
+ | * [https://qiita.com/daikiojm/items/7b77edb49306d29f389d Angular Materialのカスタムテーマを設定] | ||
+ | * [https://github.com/angular/components/blob/c1f6ea19cb317d30af79dc431d39ed8b962e146d/src/lib/core/theming/_palette.scss Angular Material _palette.scss] | ||
+ | |||
==非同期通信== | ==非同期通信== | ||
=====app.module.ts===== | =====app.module.ts===== | ||
141行目: | 197行目: | ||
|- | |- | ||
|コンポーネント -> ビュー | |コンポーネント -> ビュー | ||
− | |補間 | + | |Interpolation(補間) |
− | | | + | |<pre>{{...}}</pre> |
|- | |- | ||
|コンポーネント -> ビュー | |コンポーネント -> ビュー | ||
|プロパティ/属性バインディング | |プロパティ/属性バインディング | ||
− | |[property]='value' | + | |<pre>[property]='value'</pre> |
|- | |- | ||
|ビュー -> コンポーネント | |ビュー -> コンポーネント | ||
|イベントバインディング | |イベントバインディング | ||
− | |(event)='handler' | + | |<pre>(event)='handler'</pre> |
|- | |- | ||
|コンポーネント <-> ビュー | |コンポーネント <-> ビュー | ||
|双方向バインディング | |双方向バインディング | ||
− | |[(target)]='value' | + | |<pre>[(target)]='value'</pre> |
+ | |- | ||
+ | |フォーム要素 | ||
+ | |input/textarea/selectなど, | ||
+ | フォーム要素をバインドするには、ngModel を利用する | ||
+ | |<pre><textarea [(ngModel)]="memo"></textarea></pre> | ||
|- | |- | ||
|} | |} | ||
+ | |||
+ | |||
====テンプレート参照変数==== | ====テンプレート参照変数==== | ||
− | *#変数名 | + | *テンプレート内の特定の要素を参照するための変数 |
+ | *与えられた変数経由でプロパティなどにアクセス | ||
+ | <pre> | ||
+ | #変数名 | ||
+ | </pre> | ||
*(change)="0"は、イベントトリガーで値を更新するため必要 | *(change)="0"は、イベントトリガーで値を更新するため必要 | ||
+ | <pre> | ||
<input #txtHoge type="text" (change)="0"/> | <input #txtHoge type="text" (change)="0"/> | ||
<div>{{txtHoge.value}}</div> | <div>{{txtHoge.value}}</div> | ||
+ | </pre> | ||
+ | |||
===双方向バインディング=== | ===双方向バインディング=== | ||
+ | ---- | ||
+ | *ビューの値とコンポーネントの値を双方向に同期させる | ||
+ | *AngularでForm要素を操作するためには、FormModuleが必要 | ||
*import と @NgModule の imports に FormModuleを追加 | *import と @NgModule の imports に FormModuleを追加 | ||
=====ルートモジュール(app.modules.ts)===== | =====ルートモジュール(app.modules.ts)===== | ||
177行目: | 250行目: | ||
*このためには、name属性の指定が必須 | *このためには、name属性の指定が必須 | ||
*ngModelを[(ngModel)]とする | *ngModelを[(ngModel)]とする | ||
+ | <pre> | ||
<select name="selAcion" [(ngModel)]="selectedAction"> | <select name="selAcion" [(ngModel)]="selectedAction"> | ||
<option *ngFor="let item of testActions" value="{{item}}" >{{item}}</option> | <option *ngFor="let item of testActions" value="{{item}}" >{{item}}</option> | ||
</select> | </select> | ||
+ | </pre> | ||
+ | |||
=====コンポーネント===== | =====コンポーネント===== | ||
export class AccountComponent implements OnInit { | export class AccountComponent implements OnInit { | ||
191行目: | 267行目: | ||
[ngModel] = "hogeName" | [ngModel] = "hogeName" | ||
(ngModelChange) = "hogeName=$event.toUpperCase()" /> | (ngModelChange) = "hogeName=$event.toUpperCase()" /> | ||
+ | |||
+ | ===イベントバインディング=== | ||
+ | ---- | ||
+ | *ビューからコンポーネントに情報を引き渡す仕組み | ||
+ | *イベントとイベントハンドラーの紐付け | ||
+ | <pre> | ||
+ | <element (event)="exp"></element> | ||
+ | </pre> | ||
+ | *主なevent:click,dblclick,mousedown,mouseup,mouseenter,mousemove,mouseleave,focus,blur,keydown,keypress,keyup,input,select,reset,submit | ||
+ | |||
+ | =====イベント情報を取得する $event===== | ||
+ | *イベントハンドラーを呼び出す際に$eventを明示的に渡す必要がある | ||
+ | <pre> | ||
+ | <input type="button" (click)="show($event)" value="hoge" /> | ||
+ | </pre> | ||
+ | *イベント処理後デフォルトの動作をキャンセルするには、event.preventDefault | ||
+ | *イベントバブリングを止めるには、event.stopPropagation | ||
+ | |||
+ | *app.component.ts | ||
+ | import { Component } from '@angular/core'; | ||
+ | import { componentFactoryName } from '@angular/compiler'; | ||
+ | |||
+ | @Component({ | ||
+ | selector: 'app-root', | ||
+ | templateUrl: './app.component.html', | ||
+ | styleUrls: ['./app.component.sass'] | ||
+ | }) | ||
+ | export class AppComponent { | ||
+ | title = 'Hot Dog Status'; | ||
+ | status = ''; | ||
+ | save(e: any) { | ||
+ | this.status = "Save"; | ||
+ | } | ||
+ | load(e: any) { | ||
+ | this.status = "Load"; | ||
+ | } | ||
+ | } | ||
+ | . | ||
+ | *appcomponent.html | ||
+ | <pre> | ||
+ | <h1 id="hotDogStatus">{{title}}:{{status}}</h1> | ||
+ | <input type="textField" id="latestHotDogStatus" /> | ||
+ | <button (click)="save($event)">Save</button> | ||
+ | <button (click)="load($event)">Load</button> | ||
+ | |||
+ | <router-outlet></router-outlet> | ||
+ | </pre> | ||
+ | [[File:Angular_event.png]] | ||
+ | |||
+ | ===属性/クラス/スタイルバインディング=== | ||
+ | *コンポーネント -> ビューへのバインディングはプロパティバインディングが基本 | ||
+ | *状況によりプロパティバインディングができない場合、またコードが冗長になる場合に備え以下が準備されている | ||
+ | ====属性バインディング==== | ||
+ | *HTML属性にバインド | ||
+ | |||
+ | <pre> | ||
+ | [attr.name] = "exp" | ||
+ | </pre> | ||
+ | *name:属性名 | ||
+ | *exp:任意の式 | ||
+ | |||
+ | ====クラスバインディング==== | ||
+ | *スタイルクラスに特化し、スタイルの着脱をシンプルに表現できる | ||
+ | <pre> | ||
+ | [class.name] = "exp" | ||
+ | </pre> | ||
+ | *name:スタイルクラス名 | ||
+ | *exp:任意の式 | ||
+ | |||
+ | ====スタイルバインディング==== | ||
+ | *スタイル属性をバインディングできる | ||
+ | |||
+ | <pre> | ||
+ | [style.name] = "exp" | ||
+ | </pre> | ||
+ | *name:スタイルプロパティ名 | ||
+ | *exp:任意の式 | ||
+ | |||
+ | *単位付きスタイルプロパティ | ||
+ | <pre> | ||
+ | [style.name.unit] = "exp" | ||
+ | </pre> | ||
+ | *unit:単位 | ||
+ | |||
+ | *例 | ||
+ | <pre> | ||
+ | [style.font-size.%] = "size" | ||
+ | </pre> | ||
+ | |||
+ | ===Form実装例=== | ||
+ | ====ラジオボタン==== | ||
+ | [[File:Angular form radio.png]] | ||
+ | *テンプレート | ||
+ | <pre> | ||
+ | <form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)"> | ||
+ | <ng-container *ngFor="let itm of radioSample; index as i"> | ||
+ | <label> | ||
+ | <input type="radio" name="radioSample" | ||
+ | [(ngModel)]="selected" | ||
+ | [value]="item.value" [checked]="selected == itm.value" | ||
+ | (change)="onRadioChange(i)">{{ itm.label }} | ||
+ | </label> | ||
+ | </ng-container> | ||
+ | <button>submit</button> | ||
+ | </form> | ||
+ | </pre> | ||
+ | *コンポーネント | ||
+ | selected = "3"; | ||
+ | radioSample = [ | ||
+ | {label:'One', value:'1'}, | ||
+ | {label:'Twe', value:'2'}, | ||
+ | {label:'Three', value:'3'}, | ||
+ | {label:'Four', value:'4'}, | ||
+ | {label:'Five', value:'5'} | ||
+ | ]; | ||
+ | onRadioChange(index:number) { | ||
+ | console.log(index); | ||
+ | } | ||
+ | onSubmit(form: NgForm) { | ||
+ | console.log(form.value); | ||
+ | } | ||
+ | |||
+ | ==ディレクティブ== | ||
+ | *標準的なHTMLに対して、ngFor、ngStyleなどの独自要素、属性を追加することで機能を付与 | ||
+ | *大きく以下の3種類に分類 | ||
+ | ===分類と主なディレクティブ=== | ||
+ | <table class="wikitable" ><tr><td>種類</td><td>概要</td></tr> | ||
+ | <tr><td>コンポーネント</td><td>テンプレートをともなう</td></tr><tr><td>構造ディレクティブ</td><td>要素を追加削除することで、文書ツリーを操作</td></tr><tr><td>属性ディレクティブ</td><td>属性形式で、要素、コンポーネントの見た目や動作を変更</td></table> | ||
+ | |||
+ | <table class="wikitable" ><tr><td>分類</td><td>名前</td><td>概要</td></tr> | ||
+ | <tr><td rowspan="5">構造</td><td>ngIf</td><td>真偽により表示切り替え</td></tr> | ||
+ | <tr><td>ngSwitch</td><td>値により表示切り替え</td></tr> | ||
+ | <tr><td>ngFor</td><td>配列のループ処理</td></tr> | ||
+ | <tr><td>ngTemplateOutlet</td><td>用意されたテンプレートの内容をインポート</td></tr> | ||
+ | <tr><td>ngComponentOutlet</td><td>用意されたコンポーネントをインポート</td></tr> | ||
+ | <tr><td rowspan="3">属性</td><td>ngStyle</td><td>要素にスタイルプロパティを付与</td></tr> | ||
+ | <tr><td>ngClass</td><td>要素にスタイルクラスを着脱</td></tr> | ||
+ | <tr><td>ngPlural</td><td>数値に応じて出力切り替え</td> | ||
+ | </table> | ||
+ | |||
+ | ===ngIf=== | ||
+ | ---- | ||
+ | *https://angular.jp/api/common/NgIf | ||
+ | *https://qiita.com/KojiTakahara/items/b3fa4e33255e7abc292b | ||
+ | <pre> | ||
+ | <element *ngIf="condition"> | ||
+ | contents | ||
+ | </element> | ||
+ | </pre> | ||
+ | *element:任意の要素 | ||
+ | *condition:条件式 | ||
+ | *contents:条件式が真の時に表示されるコンテンツ | ||
+ | *ngIf=falseは、要素を生成しない(頻繁に表示、非表示を切り替える場合、スタイルバインディングでdisplayを利用する) | ||
+ | |||
+ | *例 | ||
+ | *template | ||
+ | <pre> | ||
+ | <mat-toolbar *ngIf="isToolbar">...</mat-toolbar> | ||
+ | </pre> | ||
+ | *component | ||
+ | <pre> | ||
+ | export class AppComponent { | ||
+ | isToolbar = true; | ||
+ | : | ||
+ | </pre> | ||
+ | ====テンプレートを切り替える==== | ||
+ | *templateはng-templateで宣言できる | ||
+ | <pre> | ||
+ | <div *ngIf="flag; then trueContents; else elseContents>この部分は表示されない</div> | ||
+ | <ng-template #trueContentas>trueの場合表示される</ng-template> | ||
+ | <ng-template #elseContentas>falseの場合表示される</ng-template> | ||
+ | </pre> | ||
+ | ===ngSwitch=== | ||
+ | ---- | ||
+ | *指定された式の値に応じて、表示すべきコンテンツを切り替え | ||
+ | *合致がない場合、ngSwitchDefaultを尿字 | ||
+ | <pre> | ||
+ | <parent [ngSwitch]="exp"> | ||
+ | <child *ngSwitchCase="value1"> ... </child> | ||
+ | <child *ngSwitchCase="value2"> ... </child> | ||
+ | : | ||
+ | <child *ngSwitchDefault> ... </child> | ||
+ | </parent> | ||
+ | </pre> | ||
+ | |||
+ | ===ngFor=== | ||
+ | ---- | ||
+ | *指定された配列から順に要素を取り出し内容をループする | ||
+ | *指定された要素を繰り返すが、ng-container というダミーのコンテナに適用すると、含まれる要素一式を繰り返すことができる | ||
+ | *要素の追加、削除をトラッキングするには、トラッキング式(trackBy関数)を利用し、要素を識別させる | ||
+ | <pre> | ||
+ | <element *ngFor="let tmp of list"> ... </element> | ||
+ | </pre> | ||
+ | |||
+ | ====ngFor配下で利用できる特殊変数==== | ||
+ | *使用する場合、式の中で、index as i のようにローカル変数に代入する必要がある | ||
+ | |||
+ | <table class="wikitable" ><tr><td>変数</td><td>概要</td></tr><tr><td>index</td><td>ループ回数</td></tr><tr><td>first</td><td>最初の要素か</td></tr><tr><td>last</td><td>最後の要素か</td></tr><tr><td>even</td><td>indexが偶数か</td></tr><tr><td>odd</td><td>indexが奇数か</td></table> | ||
+ | |||
+ | ===ngStyle=== | ||
+ | ---- | ||
+ | *複数スタイルをまとめて設定できる | ||
+ | *スタイル指定は、キャメルケースもしくは、ハイフン付き表現を引用符で囲む | ||
+ | <pre> | ||
+ | <element [ngStyle]="objStyle"> ... </element> | ||
+ | </pre> | ||
+ | |||
+ | *例 | ||
+ | <pre> | ||
+ | @Compnent({ | ||
+ | : | ||
+ | template: '<div [ngStyle]="style"></div>' | ||
+ | }) | ||
+ | export class AppComponent { | ||
+ | style = { | ||
+ | backgroundColor: '#f00', | ||
+ | 'font-weight': 'bold' | ||
+ | }; | ||
+ | } | ||
+ | </pre> | ||
+ | ===ngClass=== | ||
+ | ---- | ||
+ | *スタイルクラスを着脱する | ||
+ | <pre> | ||
+ | <element [ngClass]="clazz"> ... </element> | ||
+ | </pre> | ||
+ | |||
+ | *引数(clazz部分)に指定できる形式 | ||
+ | <table class="wikitable" ><tr><td>変数</td><td>概要</td></tr><tr><td>文字列</td><td>スタイルクラス名(空白区切りで複数指定可)</td></tr><tr><td>配列</td><td>スタイルクラス名のリスト</td></tr><tr><td>オブジェクト</td><td>[スタイルクラス名:有効/無効]形式</td></table> | ||
+ | |||
+ | ===ngPlural=== | ||
+ | ---- | ||
+ | *式の値がnumに一致する場合、ng-template配下のメッセージを表示する | ||
+ | <pre> | ||
+ | <element [ngPlural]="exp"> | ||
+ | <ng-template ngPluralCase="num">message</ng-template> | ||
+ | : | ||
+ | </element> | ||
+ | </pre> | ||
+ | *num : =0,=1,other などを指定できる | ||
+ | |||
+ | ===ngTemplateOutlet=== | ||
+ | ---- | ||
+ | *予め用意されたテンプレートをコンポーネントの任意の位置に挿入する | ||
+ | <pre> | ||
+ | <ng-container *ngTemplateOutlet="exp; context : ctx"></ng-container> | ||
+ | </pre> | ||
+ | *exp : テンプレート | ||
+ | *ctx : テンプレートに反映させるオブジェクト | ||
+ | |||
+ | === ngComponentOutlet=== | ||
+ | ---- | ||
+ | *予め用意したコンポーネントを動的にビューにインポートする | ||
+ | <pre> | ||
+ | <ng-container *ngComponentOutlet="exp"></ng-container> | ||
+ | </pre> | ||
+ | |||
+ | ==フォーム== | ||
+ | *Angularでは標準のform/input要素が拡張されている | ||
+ | *利用するためには、FormsModuleをインポートする | ||
+ | ===ngForm=== | ||
+ | ---- | ||
+ | *Angularでは標準formが拡張されている | ||
+ | *以下のような属性を宣言しておく | ||
+ | <table class="wikitable" ><tr><td>属性</td><td>概要</td></tr><tr><td>#myForm='"ngForm'"</td><td>ngFormディレクティブを変数にセット</td></tr><tr><td>(ngSubmit)='"show()'"</td><td>サブミット時に呼び出す処理</td></tr><tr><td>novalidate</td><td>HTML5の検証機能を無効化</td></table> | ||
+ | |||
+ | ===input=== | ||
+ | ---- | ||
+ | *input/rextareaなどのフォーム要素も拡張されている | ||
+ | <pre> | ||
+ | <input id="main" name="mail" type="email" | ||
+ | [(ngModel)]="user.mail" | ||
+ | required email | ||
+ | #mail="ngModel" /> | ||
+ | </pre> | ||
+ | |||
+ | <table class="wikitable" ><tr><td>属性</td><td>概要</td></tr><tr><td>ngModelディレクティブ</td><td>コンポーネントプロパティとバインド、検証機能を有効化するために必須</td></tr><tr><td>name属性</td><td>Angularが内部的にフォーム要素を識別するためのキー、必ず指定</td></tr><tr><td>#mail='"ngModel'"</td><td>テンプレート参照変数、後からフォーム要素の状態にアクセスできる</td></tr><tr><td>required</td><td>必須</td></tr><tr><td>minlength</td><td>文字列最小値</td></tr><tr><td>maxlenght</td><td>文字列最大値</td></tr><tr><td>email</td><td>メールアドレス形式</td></tr><tr><td>pattern</td><td>正規表現パターンにマッチするか</td></tr><tr><td>min</td><td>数値最小値</td></tr><tr><td>max</td><td>数値最大値</td></table> | ||
+ | |||
+ | ====errorsオブジェクト==== | ||
+ | *検証の成否を参照 | ||
+ | |||
+ | <pre> | ||
+ | 入力要素名.errors?.検証型 | ||
+ | </pre> | ||
+ | |||
+ | *例 | ||
+ | <pre> | ||
+ | mail.errors?.required | ||
+ | </pre> | ||
+ | ===状態検知=== | ||
+ | <table class="wikitable" ><tr><td>内容</td><td>書式</td></tr><tr><td>検証項目単位でのエラー有無</td><td>入力要素名.error.検証型</td></tr><tr><td>フォーム/入力項目単位でのエラー有無</td><td>入力要素名.valid、入力要素名.invalid</td></tr><tr><td>フォーム/入力項目項目が変更されていない</td><td>入力要素名.pristine</td></tr><tr><td>フォーム/入力項目項目が変更された</td><td>入力要素名.dirty</td></tr><tr><td>フォーム/入力項目項目にフォーカスが当たった</td><td>入力要素名.touched</td></tr><tr><td>フォーム/入力項目項目にフォーカスが当たっていない</td><td>入力要素名.untouched</td></table> | ||
+ | |||
+ | *Angularでは上記状態に応じて以下のスタイルクラスが設定される | ||
+ | <table class="wikitable" ><tr><td>スタイルクラス</td><td>概要</td></tr><tr><td>ng-valid</td><td>入力値が妥当</td></tr><tr><td>ng-invalid</td><td>入力値が不正</td></tr><tr><td>ng-pristine</td><td>初期値から変更されていない</td></tr><tr><td>ng-dirty</td><td>初期値から変更された</td></tr><tr><td>ng-touched</td><td>フォーカスが当たったことがある</td></tr><tr><td>ng-untouched</td><td>フォーカスが当たったことがない</td></tr><tr><td>ng-submitted</td><td>サブミットされた</td></table> | ||
+ | |||
+ | *スタイルシートを準備(初期状態でエラーとならないように、ng-dirty & ng-invalid) | ||
+ | <pre> | ||
+ | input.ng-dirty.ng-invalid { background-color: #fee; } | ||
+ | </pre> | ||
+ | |||
+ | *CSSでA,Bどちらも指定されている時に有効:.A.B | ||
+ | *CSSでA,Bどちらかが指定されている時に有効:.A,.B | ||
==ルーティング== | ==ルーティング== | ||
258行目: | 636行目: | ||
<a routerLink="/account">Account</a> | <a routerLink="/account">Account</a> | ||
<router-outlet></router-outlet> | <router-outlet></router-outlet> | ||
+ | ===クエリパラメータ=== | ||
+ | *http://localhost:4200/?fullscreen=true | ||
+ | <pre> | ||
+ | import { ActivatedRoute } from '@angular/router'; | ||
+ | : | ||
+ | export class AppComponent implements OnInit { | ||
+ | isToolbar: boolean = true; | ||
+ | |||
+ | ngOnInit(): void { | ||
+ | let outer = this; | ||
+ | this.route.queryParamMap.subscribe( | ||
+ | paramsMap => { | ||
+ | const fullscreen = Boolean(paramsMap.get("fullscreen")); | ||
+ | outer.isToolbar = !fullscreen; | ||
+ | }); | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ==モジュール== | ||
+ | *ある程度の規模の場合、コードをモジュールに分割し、関連クラスをまとめておく | ||
+ | *アプリ構成の見通しがよくなる | ||
+ | *機能の着脱が容易になる | ||
+ | *起動時に呼び出されるモジュールを、ルートモジュール、またはメインモジュールという(QuickStartのapp.module.tsなど) | ||
+ | |||
+ | ===定義=== | ||
+ | *@NgModuleデコレーターを宣言することで、モジュールと見做される | ||
+ | |||
+ | *デコレーターの主なパラメーター | ||
+ | <table class="wikitable" ><tr><td>パラメーター名</td><td>概要</td></tr><tr><td>imports</td><td>現在のモジュールで利用する他のモジュール</td></tr><tr><td>exports</td><td>現在のモジュールから外部へ公開するコンポーネントなど</td></tr><tr><td>declarations</td><td>現在のモジュールに属するコンポーネントなど</td></tr><tr><td>bootstrap</td><td>アプリで最初に起動すべき最上位のコンんポーネント</td></tr><tr><td>id</td><td>モジュールのID値</td></table> | ||
+ | |||
==コンポーネント== | ==コンポーネント== | ||
+ | *ページを構成するUI部品 | ||
+ | *Angularアプリとは1つ以上のコンポーネントの集合体といえる | ||
+ | *一般的には複数のコンポーネントを組み合わせてページを構成する | ||
+ | |||
+ | ===定義=== | ||
+ | ---- | ||
+ | *@Componentデコレーターで宣言する | ||
+ | |||
+ | <pre> | ||
+ | import { Component } from '@angular/core'; | ||
+ | |||
+ | @Component({ | ||
+ | selector: 'my-app', | ||
+ | template: `<h1>Hello {{name}}&kt;/h1>`, | ||
+ | }) | ||
+ | export class AppComponent { | ||
+ | name = 'Angular'; | ||
+ | } | ||
+ | </pre> | ||
+ | *templateに含まれる {{...}} は、Interpolation(補間)と呼ばれ、ビュー変数を埋め込むためのプレースホルダーとして機能する | ||
+ | *Angularでは、コンポーネントの側でデータ(ビュー変数)を用意しておき、テンプレート側でデータを埋め込む場所や表示方法を定義するのが基本となる | ||
+ | *ビュー変数の役割をコンポーネントのプロパティが担う(上記では、nameがプロパティでテンプレートから自由に参照できる変数となる) | ||
+ | |||
+ | |||
+ | {|class="wikitable" | ||
+ | !パラメータ名!!概要 | ||
+ | |- | ||
+ | |selector||コンポーネントを適用すべき要素を表す | ||
+ | |- | ||
+ | |template||コンポーネントに適用するビュー | ||
+ | |} | ||
+ | |||
===ライフサイクル=== | ===ライフサイクル=== | ||
+ | ----- | ||
+ | *ライフサイクルメソッドを利用する際は対応するインターフェースを実装する | ||
+ | *インターフェース名は、以下の一覧から ngを除いたもの | ||
+ | |||
{|class="wikitable" | {|class="wikitable" | ||
!ライフサイクル | !ライフサイクル | ||
270行目: | 714行目: | ||
| | | | ||
|- | |- | ||
− | |ngOnChanges | + | |[https://angular.jp/api/core/OnChanges ngOnChanges] |
|@Input経由で入力値が設定/再設定された | |@Input経由で入力値が設定/再設定された | ||
|- | |- | ||
|ngOnInit | |ngOnInit | ||
− | | | + | |ngOnChangesで入力値(@Inputp)が処理された後、コンポーネントの初期化時(最初のngOnChangesメソッドの後で一度だけ) |
+ | 入力プロパティに基づく処理は、コンストラクタでなく、ここで行う必要がある | ||
+ | 一般的にコンポーネントの初期化処理はOnInitに集約する | ||
|- | |- | ||
|ngDoCheck | |ngDoCheck | ||
293行目: | 739行目: | ||
|ngOnDestroyed | |ngOnDestroyed | ||
|コンポーネントが破棄される時 | |コンポーネントが破棄される時 | ||
+ | タイマーのクリア、Observableの購読解除など | ||
|- | |- | ||
|コンポーネント破棄 | |コンポーネント破棄 | ||
298行目: | 745行目: | ||
|- | |- | ||
|} | |} | ||
+ | |||
+ | ====@ViewChildrenデコレーター : 子コンポーネントを取得==== | ||
+ | ---- | ||
+ | *https://stackoverflow.com/questions/32693061/how-can-i-select-an-element-in-a-component-template | ||
+ | *現在のビューに配置された子コンポーネントを取得する | ||
+ | * ViewChildrenデコレーターを適用したプロパティにはQueryListがセットされます。QueryListはいわゆるコレクションクラスとなっており、検出された複数の要素をより効率的に参照できるようなAPIを提供しています | ||
+ | *[https://angular.io/api/core/QueryList QueryListが提供するAPI] | ||
+ | <pre> | ||
+ | @ViewChildren(child) prop | ||
+ | </pre> | ||
+ | *child: 取得する子コンポーネント、prop:プロパティ名 | ||
+ | |||
+ | ====@ViewChildデコレーター : 単一の子コンポーネントを取得==== | ||
+ | ---- | ||
+ | *取得すべきコンポーネントが単一であることが分かっている場合は、配列を返す@ViewChildrenではなく、@ViewChildを利用できる | ||
+ | *child: 取得する子コンポーネント、prop:プロパティ名 | ||
+ | <pre> | ||
+ | @ViewChild(child) prop | ||
+ | </pre> | ||
+ | |||
+ | ===コンポーネントの連携=== | ||
+ | ---- | ||
+ | *複数のコンポーネントが互いに連携して1つの機能を構成 | ||
+ | *一般的なアプリケーションでは、複数のコンポーネントが連携して1つの機能(ページ)を構成する | ||
+ | *@Input() と @Output() を使うことで、 親のコンテキストと子のディレクティブやコンポーネントとの間でデータをシェアすることができます。 | ||
+ | *@Input() プロパティは書き込み可能である一方、@Output() プロパティは観測可能です。 | ||
+ | ====@Input デコレーター : コンポーネントを入れ子に配置 ==== | ||
+ | ---- | ||
+ | *https://angular.jp/guide/inputs-outputs | ||
+ | *プロパティ定義に@Inputデコレーターを付与することで、コンポーネントの属性として値を受け取ることができる | ||
+ | |||
+ | *親側から入れ子のコンポーネント属性に値を設定 | ||
+ | <pre> | ||
+ | @Component({ | ||
+ | selector: 'parent', | ||
+ | template: `<div><app-feed-item [feed]="feed"></app-feed-item></div>` | ||
+ | }) | ||
+ | export class Parent implements OnInit { | ||
+ | feed: Feed; | ||
+ | ngOnInit(): void { | ||
+ | this.feeds = new Feeds(); | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | *入れ子になったコンポーネントのプロパティに、@Inputを付与することで、親側でセットした値を受け取ることができる | ||
+ | <pre> | ||
+ | @Component({ | ||
+ | selector: 'app-feed-item', | ||
+ | template: `<div>{{feed.title}}</div>` | ||
+ | }) | ||
+ | export class FeedItemComponent { | ||
+ | @Input() feed: Feed; | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | *セッター、ゲッターに対してデコレーターを付与することもできる | ||
+ | <pre> | ||
+ | export class FeedItemComponent { | ||
+ | private feed: Feed; | ||
+ | |||
+ | @Input() | ||
+ | set Feed(feed:Feed) { | ||
+ | this.feed = feed; | ||
+ | } | ||
+ | |||
+ | get Feed() { | ||
+ | return this.feed; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ====@Outputデコレーター : 子コンポーネントからイベントを受け取る==== | ||
+ | ---- | ||
+ | *https://angular.jp/guide/inputs-outputs | ||
+ | *Outputデコレーターを使用することによって、子コンポーネントのイベントを親コンポーネントへ通知できる | ||
+ | |||
+ | <pre> | ||
+ | @Component({ | ||
+ | selector: 'app-feed-item', | ||
+ | template: `<div>{{feed.title}}</div>` | ||
+ | }) | ||
+ | export class FeedItemComponent { | ||
+ | @Input() feed: Feed; | ||
+ | @Output() edited = new EventEmitter<Feed>(); | ||
+ | |||
+ | onSubmit() { | ||
+ | this.edited.emit(this.feed); | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | *親側では、以下で受けられる。 | ||
+ | *ここで、$event は、上記Feedが渡される | ||
+ | <pre> | ||
+ | <element (edited)="onEdited($event)"> ... </element> | ||
+ | </pre> | ||
+ | |||
+ | *別名を与えることもできる | ||
+ | <pre> | ||
+ | @Output('changed') edited = new EventEmitter<Feed>(); | ||
+ | </pre> | ||
+ | *その場合、受け側は以下 | ||
+ | <pre> | ||
+ | <element (changed)="onEdited($event)"> ... </element> | ||
+ | </pre> | ||
+ | ===コンポーネント配下のコンテンツをテンプレートに反映させる ng-content=== | ||
+ | ---- | ||
+ | * <ng-content> を使用することで呼び出し元で指定したコンテンツをテンプレートに埋め込むことが可能になる | ||
+ | |||
+ | ===コンポーネントのスタイル定義=== | ||
+ | ---- | ||
+ | *@Componentデコレーターの属性に指定する | ||
+ | ** styles : スタイルシートの文字列 | ||
+ | ** styleUrls : cssファイルのパス | ||
+ | *コンポーネントで指定されたコンポーネントスタイルは原則としてコンポーネントの中だけで有効 | ||
+ | *コンポーネントスタイルで使用されているセレクター式はコンポーネントローカル | ||
+ | |||
+ | *styles | ||
+ | <pre> | ||
+ | @Component({ | ||
+ | selector: 'app-nav-menu', | ||
+ | templateUrl: './nav-menu.component.html', | ||
+ | styles: [` | ||
+ | a.navbar-brand { | ||
+ | white-space: normal; | ||
+ | text-align: center; | ||
+ | word-break: break-all; | ||
+ | } | ||
+ | `,` | ||
+ | html { | ||
+ | font-size: 14px; | ||
+ | } | ||
+ | `,` | ||
+ | @media (min-width: 768px) { | ||
+ | html { | ||
+ | font-size: 16px; | ||
+ | } | ||
+ | } | ||
+ | `,` | ||
+ | .box-shadow { | ||
+ | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); | ||
+ | } | ||
+ | `] | ||
+ | }) | ||
+ | export class NavMenuComponent { | ||
+ | </pre> | ||
+ | |||
+ | *styleUrls | ||
+ | <pre> | ||
+ | @Component({ | ||
+ | selector: 'app-nav-menu', | ||
+ | templateUrl: './nav-menu.component.html', | ||
+ | styleUrls: ['./nav-menu.component.css'] | ||
+ | }) | ||
+ | export class NavMenuComponent { | ||
+ | </pre> | ||
+ | *その他の定義方法 | ||
+ | **テンプレートに style タグを埋め込む | ||
+ | **テンプレートに link タグを埋め込みcssファイルを指定 | ||
+ | **スタイルシートから@Importディレクティブによるインポート | ||
+ | |||
+ | ====コンポーネントスタイルで利用できる特殊セレクター==== | ||
+ | =====コンポーネント本体にスタイルを適用するhost擬似セレクター===== | ||
+ | ---- | ||
+ | *host擬似セレクターはコンポーネント自体を表す | ||
+ | *テンプレートはコンポーネント配下に展開され、コンポーネントそのものはテンプレートの一部ではない | ||
+ | *コンポーネント要素へはhostセレクター以外ではアクセスできない | ||
+ | <pre> | ||
+ | @Component({ | ||
+ | selector: 'app-fetch-data', | ||
+ | templateUrl: './fetch-data.component.html', | ||
+ | styles: [ | ||
+ | `:host { | ||
+ | display: block; | ||
+ | border: 3px solid red; | ||
+ | background-color: Blue; | ||
+ | }` | ||
+ | ] | ||
+ | }) | ||
+ | export class FetchDataComponent { | ||
+ | : | ||
+ | </pre> | ||
+ | [[File:angular_host_selector.png | 400px]] | ||
+ | |||
+ | *:host(スタイルクラス名): { ... } : 特定のスタイルクラス名を持つ場合だけ有効 | ||
+ | |||
+ | =====コンポーネントの内部状態に応じてにスタイルを適用するhost-context擬似セレクター===== | ||
+ | ---- | ||
==サービス== | ==サービス== | ||
304行目: | 940行目: | ||
===登録=== | ===登録=== | ||
*モジュールにサービスを登録する。 | *モジュールにサービスを登録する。 | ||
− | * | + | *コンポーネントにも登録できる(@Componentデコレーターのprovidersパラメータ)。この場合コンポーネントと子コンポーネントのみで利用できる。 |
+ | |||
import { HogeSer[[vi]]ce } from './hoge.ser[[vi]]ce'; | import { HogeSer[[vi]]ce } from './hoge.ser[[vi]]ce'; | ||
312行目: | 949行目: | ||
: | : | ||
}) | }) | ||
+ | |||
===依存性注入=== | ===依存性注入=== | ||
*方法を宣言するのは、@NgModule/@Component デコレータの pro[[vi]]dersパラメータ | *方法を宣言するのは、@NgModule/@Component デコレータの pro[[vi]]dersパラメータ | ||
*サービスを提供するためのPro[[vi]]derオブジェクトを登録する | *サービスを提供するためのPro[[vi]]derオブジェクトを登録する | ||
+ | |||
+ | ====使用する==== | ||
+ | *使用するためには、コンストラクタに対応する引数を追加するだけ | ||
+ | *以下の例の場合、this.hogeService.メソッド で利用可能 | ||
+ | export class FooComponent { | ||
+ | constructor( | ||
+ | private hogeService: HogeService | ||
+ | ){} | ||
+ | } | ||
+ | |||
=====Pro[[vi]]derであることの条件は以下のプロパティを持つこと===== | =====Pro[[vi]]derであることの条件は以下のプロパティを持つこと===== | ||
{|class="wikitable" | {|class="wikitable" | ||
378行目: | 1,026行目: | ||
} | } | ||
] | ] | ||
− | + | ==パイプ== | |
+ | *テンプレート上に埋め込まれたデータを加工・整形する | ||
+ | *与えられた式の値を加工/整形する | ||
+ | |||
+ | ===呼び出し構文=== | ||
+ | <pre> | ||
+ | {{exp | pipe [:param1 [:param2 ..]]}} | ||
+ | </pre> | ||
+ | *式とパイプは、| で区切る | ||
+ | *パイプのパラメーターは、: で列記 | ||
+ | *| を重ねることで複数パイプを適用 | ||
+ | |||
+ | *例 | ||
+ | <pre> | ||
+ | {{ price | currency : '¥' }} | ||
+ | </pre> | ||
+ | |||
+ | ===標準パイプ=== | ||
+ | ---- | ||
+ | {|class="wikitable" | ||
+ | !パイプ | ||
+ | !内容 | ||
+ | |- | ||
+ | |lowercase | ||
+ | |小文字変換 | ||
+ | |- | ||
+ | |uppercase | ||
+ | |大文字変換 | ||
+ | |- | ||
+ | |titlecase | ||
+ | |単語の先頭を大文字変換 | ||
+ | |- | ||
+ | |slice | ||
+ | |文字列から部分文字列を切り出し | ||
+ | |- | ||
+ | |date | ||
+ | |日付・時刻整形 | ||
+ | |- | ||
+ | |number | ||
+ | |数値を桁区切り | ||
+ | |- | ||
+ | |percent | ||
+ | |パーセント形式 | ||
+ | |- | ||
+ | |currency | ||
+ | |通貨形式 | ||
+ | |- | ||
+ | |json | ||
+ | |オブジェクトをJSON変換 | ||
+ | |- | ||
+ | |i18nPlural | ||
+ | |数値によって表示文字列をへんか | ||
+ | |- | ||
+ | |i18nSelect | ||
+ | |文字列に応じて出力を切替 | ||
+ | |- | ||
+ | |async | ||
+ | |Observable/Promiseによる非同期処理の結果を取得 | ||
+ | |} | ||
+ | |||
+ | ===作成=== | ||
+ | ---- | ||
+ | *小文字に変換してブランクを指定文字に置き換える | ||
+ | <pre> | ||
+ | import { Pipe, PipeTransform } from '@angular/core'; | ||
+ | |||
+ | @Pipe({ | ||
+ | name: 'globCategory' | ||
+ | }) | ||
+ | export class GlobCategoryPipe implements PipeTransform { | ||
+ | |||
+ | transform(value: string, ...args: string[]): string { | ||
+ | if (value != null) { | ||
+ | const replacement = ((args != null && args[0] != null)?args[0]:'-'); | ||
+ | value = (value.toLowerCase()).replace(/ /g, replacement); | ||
+ | } | ||
+ | return value; | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | *使用 | ||
+ | <pre> | ||
+ | <a *ngFor="let category of feed.categories" href="https://www.typea.info/blog/index.php/category/{{category | globCategory :'_'}}" target="_blank" class="category-link"> | ||
+ | {{category}} | ||
+ | </a> | ||
+ | </pre> | ||
+ | |||
==Modules== | ==Modules== | ||
===Http=== | ===Http=== | ||
*https://angular.io/guide/http | *https://angular.io/guide/http | ||
− | ==[ | + | ==RxJs== |
+ | *RxJsライブラリ | ||
+ | *https://angular.jp/guide/rx-library | ||
+ | |||
+ | リアクティブプログラミングは、データストリームと変更の伝播に関する非同期プログラミングのパラダイムです。 | ||
+ | RxJS (Reactive Extensions for JavaScript) は、非同期またはコールバックベースのコード (RxJS Docs) の作成を容易にする | ||
+ | observables を使用したリアクティブプログラミング用のライブラリ | ||
+ | |||
+ | *非同期処理の既存のコードを observables に変換する | ||
+ | *ストリーム内の値を反復処理する | ||
+ | *異なる型への値のマッピング | ||
+ | *ストリームのフィルタリング | ||
+ | *複数のストリームの作成 | ||
+ | |||
+ | ===Observable 作成関数=== | ||
+ | |||
+ | ベント、タイマー、promise などから observables を作成するプロセスを簡素化 | ||
+ | |||
+ | ====promise から observable を作成==== | ||
+ | import { from } from 'rxjs'; | ||
+ | |||
+ | // promise から Observable を作成 | ||
+ | const data = from(fetch('/api/endpoint')); | ||
+ | // 非同期の結果の購読を開始 | ||
+ | data.subscribe({ | ||
+ | next(response) { console.log(response); }, | ||
+ | error(err) { console.error('Error: ' + err); }, | ||
+ | complete() { console.log('Completed'); } | ||
+ | }); | ||
+ | |||
+ | ====カウンターから observable を作成する==== | ||
+ | import { interval } from 'rxjs'; | ||
+ | |||
+ | // 一定間隔でで値を発行するObservable を作成 | ||
+ | const secondsCounter = interval(1000); | ||
+ | // 発行される値の購読を開始 | ||
+ | secondsCounter.subscribe(n => | ||
+ | console.log(`It's been ${n} seconds since subscribing!`)); | ||
+ | |||
+ | ====イベントから observable を作成する==== | ||
+ | import { fromEvent } from 'rxjs'; | ||
+ | |||
+ | const el = document.getElementById('my-element'); | ||
+ | |||
+ | // マウスの移動を発行する Observable の作成 | ||
+ | const mouseMoves = fromEvent(el, 'mousemove'); | ||
+ | |||
+ | // マウス移動イベントを購読開始 | ||
+ | const subscription = mouseMoves.subscribe((evt: MouseEvent) => { | ||
+ | console.log(`Coords: ${evt.clientX} X ${evt.clientY}`); | ||
+ | |||
+ | // スクリーンの左端をマウスが越えたら購読中止 | ||
+ | if (evt.clientX < 40 && evt.clientY < 40) { | ||
+ | subscription.unsubscribe(); | ||
+ | } | ||
+ | }); | ||
+ | |||
+ | ====AJAX リクエストから observable を作成==== | ||
+ | |||
+ | import { ajax } from 'rxjs/ajax'; | ||
+ | |||
+ | // AJAXリクエストを生成する Observable の作成 | ||
+ | const apiData = ajax('/api/data'); | ||
+ | // リクエストの生成を購読 | ||
+ | apiData.subscribe(res => console.log(res.status, res.response)); | ||
+ | |||
+ | ===オペレーター=== | ||
+ | *オペレーターは、コレクションの高度な操作を可能にするために、observables 基盤上に構築される関数です。たとえば RxJS は map()、filter()、concat()、flatMap() のようなオペレーターを定義 | ||
+ | *オペレーターは設定オプションをとり、ソースとなる observable を受け取る関数を返します。この返された関数を実行するとき、オペレーターは observable が出力する値を観測、変換し、変換された値の新しい observable を返します | ||
+ | *[https://christina04.hatenablog.com/entry/2017/02/25/111715 RxのflatMapの使い方] | ||
+ | ====Map operator==== | ||
+ | import { map } from 'rxjs/operators'; | ||
+ | |||
+ | const nums = of(1, 2, 3); | ||
+ | |||
+ | const squareValues = map((val: number) => val * val); | ||
+ | const squaredNums = squareValues(nums); | ||
+ | |||
+ | squaredNums.subscribe(x => console.log(x)); | ||
+ | |||
+ | // Logs | ||
+ | // 1 | ||
+ | // 4 | ||
+ | // 9 | ||
+ | |||
+ | ===パイプ=== | ||
+ | |||
+ | パイプを使用するとオペレーターをリンクすることができます。パイプを使用すると、複数の機能を1つの機能にまとめることができます。pipe() 関数は、結合する関数を引数としてとり、実行時に順次関数を実行する新しい関数を返します。 | ||
+ | |||
+ | observable に適用される�オペレーターのセットは、レシピ、つまり関心のある値を生成するための一連の命令です。それだけではレシピは何もしません。レシピを通して結果を出すには subscribe() を呼び出す必要があります。 | ||
+ | |||
+ | ====単独利用==== | ||
+ | |||
+ | import { filter, map } from 'rxjs/operators'; | ||
+ | |||
+ | const nums = of(1, 2, 3, 4, 5); | ||
+ | |||
+ | // Observable を受け入れる関数の生成 | ||
+ | const squareOddVals = pipe( | ||
+ | filter((n: number) => n % 2 !== 0), | ||
+ | map(n => n * n) | ||
+ | ); | ||
+ | |||
+ | // filter と map 関数を実行する Observable を生成 | ||
+ | const squareOdd = squareOddVals(nums); | ||
+ | |||
+ | // 結合された関数の実行を購読 | ||
+ | squareOdd.subscribe(x => console.log(x)); | ||
+ | |||
+ | ====Observable.pip==== | ||
+ | |||
+ | pipe() 関数は RxJS Observable のメソッドでもあるため、短く書き換え | ||
+ | |||
+ | import { filter, map } from 'rxjs/operators'; | ||
+ | |||
+ | const squareOdd = of(1, 2, 3, 4, 5) | ||
+ | .pipe( | ||
+ | filter(n => n % 2 !== 0), | ||
+ | map(n => n * n) | ||
+ | ); | ||
+ | |||
+ | squareOdd.subscribe(x => console.log(x)); | ||
+ | |||
===コンポーネントからサービスのデータを購読する=== | ===コンポーネントからサービスのデータを購読する=== | ||
+ | ---- | ||
*https://angular.io/guide/component-interaction#!#bidirectional-ser[[vi]]ce | *https://angular.io/guide/component-interaction#!#bidirectional-ser[[vi]]ce | ||
=====Ser[[vi]]ce===== | =====Ser[[vi]]ce===== | ||
415行目: | 1,272行目: | ||
} | } | ||
: | : | ||
+ | |||
+ | ==DI== | ||
+ | *https://angular.jp/guide/dependency-injection-in-action | ||
+ | |||
+ | ===依存関係を @Optional として @Host で検索を制限=== | ||
+ | *コンポーネントが依存関係を要求すると、Angularはそのコンポーネントのインジェクターから開始し、 最初の適切なプロバイダーが見つかるまでインジェクターツリーを検索 | ||
+ | *@Optional プロパティデコレーターは、依存関係が見つからない場合にnullを返す | ||
+ | *@Host プロパティデコレーターは、上方向への検索を ホストコンポーネント で停止== | ||
+ | |||
+ | ===@Inject を使用してカスタムプロバイダーを設定=== | ||
+ | *カスタムプロバイダーを使用すると、組み込みのブラウザAPIなど、暗黙的な依存関係に対する具体的な実装を提供できる | ||
+ | |||
+ | ===コンポーネントの DOM 要素を注入=== | ||
+ | |||
+ | ===プロバイダーを定=== | ||
+ | *値プロバイダー: useValue | ||
+ | *クラスプロバイダー: useClass | ||
+ | *エイリアスプロバイダー: useExisting | ||
+ | *ファクトリープロバイダー: useFactory | ||
==UI== | ==UI== | ||
421行目: | 1,297行目: | ||
====レスポンシブレイアウト==== | ====レスポンシブレイアウト==== | ||
*https://material.angular.io/cdk/layout/over[[vi]]ew | *https://material.angular.io/cdk/layout/over[[vi]]ew | ||
+ | |||
==テスト== | ==テスト== | ||
===ユニットテスト=== | ===ユニットテスト=== | ||
426行目: | 1,303行目: | ||
*https://karma-runner.github.io/2.0/index.html | *https://karma-runner.github.io/2.0/index.html | ||
*karma.conf.js | *karma.conf.js | ||
+ | |||
+ | ==[[Firebase]]== | ||
+ | ===Firestoreの利用=== | ||
+ | *[https://www.typea.info/blog/index.php/2020/04/04/firebase-hosting-firestore-release/ Firebase Hosting に Angular を統合して Firestore に接続] | ||
+ | |||
==[[Tips]]== | ==[[Tips]]== | ||
===機能=== | ===機能=== | ||
+ | ====router-outlet で @Outputしたい===== | ||
+ | @Outputはできないが、onActivateでコンポーネント参照が取得できる | ||
+ | <pre> | ||
+ | <router-outlet (activate)="onActivate($event)"></router-outlet> | ||
+ | </pre> | ||
+ | *コンポーネント参照のメソッドを呼ぶなりする | ||
+ | <pre> | ||
+ | onActivate(component: any /*Component*/) { | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | |||
+ | |||
====Chrome拡張機能==== | ====Chrome拡張機能==== | ||
augury | augury | ||
436行目: | 1,332行目: | ||
====Hello.js 認証をまとめる[[JavaScript]]ライブラリ==== | ====Hello.js 認証をまとめる[[JavaScript]]ライブラリ==== | ||
*http://adodson.com/hello.js/#scope | *http://adodson.com/hello.js/#scope | ||
+ | |||
===文法=== | ===文法=== | ||
====[[Java]]scriptのグローバルオブジェクトを利用==== | ====[[Java]]scriptのグローバルオブジェクトを利用==== | ||
451行目: | 1,348行目: | ||
*https://angular.io/guide/http | *https://angular.io/guide/http | ||
*[http://typea.info/blg/glob/2018/01/angular-httpclientxsrfmodule-flask-csrf.html Angular + HttpClientXsrfModule + Flask で CSRF] | *[http://typea.info/blg/glob/2018/01/angular-httpclientxsrfmodule-flask-csrf.html Angular + HttpClientXsrfModule + Flask で CSRF] | ||
+ | ===CORS対応=== | ||
+ | *[https://www.typea.info/blog/index.php/2020/11/06/angluar_other_domain_read_xml/ Angular に 別ドメインのブログRSSを読み込み(CORS対応など)] | ||
===参照=== | ===参照=== | ||
*[http://apoc.jp/angular-cli-subdir/ Angular CLIでサブディレクトリにビルドする方法] | *[http://apoc.jp/angular-cli-subdir/ Angular CLIでサブディレクトリにビルドする方法] | ||
+ | |||
+ | ===Firebase=== | ||
+ | *[https://www.typea.info/blog/index.php/2020/05/11/angluarfirebase-auth-google-twitter/ Angluar:Firebase auth でGoogle/Twtterログインを試す] | ||
+ | *[https://www.typea.info/blog/index.php/2020/04/04/firebase-hosting-firestore-release/ Firebase Hosting に Angular を統合して Firestore に接続] |
2022年8月22日 (月) 06:31時点における最新版
| AngularJS | TypeScript | Google Cloud Platform | Bootstrap | Flask | ブログカテゴリ(Angular) |
目次
- 1 Angular
- 2 Angular CLI
- 3 ng-bootstrap
- 4 ngx-bootstrap
- 5 Angular Material
- 6 非同期通信
- 7 データバインディング
- 8 ディレクティブ
- 9 フォーム
- 10 ルーティング
- 11 モジュール
- 12 コンポーネント
- 13 サービス
- 14 パイプ
- 15 Modules
- 16 RxJs
- 17 DI
- 18 UI
- 19 テスト
- 20 Firebase
- 21 Tips
Angular
Angularに関連するブログエントリ=
Angular CLI
準備
- Node.jsのインストール
Quickスタート
- ダウンロード
> git clone https://github.com/angular/quickstart.git quickstart
- パッケージのインストール
> cd quickstart > npm install
- 実行
> npm start
要素
要素名 | 説明 |
---|---|
コンポーネント | UI部品 |
サービス | ビジネスロジック |
パイプ | 表示値の加工、演算 |
ディレクティブ | 文書ツリーの操作 |
- モジュール
- Angularにおけるモジュールの実態は、TypeScriptのクラス
- 定義しただけではモジュールとみなされず、@NgModuleデコレータで宣言が必要
設定ファイル
設定ファイル | 概要 |
---|---|
package.json | 利用するライブラリ情報 |
tsconfig.json | TypeScriptコンパイラーの動作を |
systemjs.config.js | モジュールローダー(SystemJS)の設定 |
インストール
>npm install -g @angular/cli
アプリケーションの生成
> ng new myapp
アプリケーションの実行
> ng serve
Angular CLI の主なコマンド
概要 | コマンド |
---|---|
appアプリを生成 | ng new app |
ひな形の生成 | ng generate ... |
ビルドして起動 | ng serve |
ビルド | ng build |
ユニットテスト | ng test |
E2Eテスト | mg e2e |
i118nメッセージを抽出 | ng xi18n |
指定されたキーワードで検索 | ng doc keyword |
TSLintによるコードチェック | ng lint |
現在の設定を取得 | ng get key |
指定されたキー/値を設定 | ng set key=value |
Angular CLIのバージョン | ng version |
オブジェクト生成 ng generate サブコマンド
要素 | コマンド |
---|---|
モジュール | ng g moduole hoge |
コンポーネント | ng g component hoge |
ディレクティブ | ng g directive hoge |
パイプ | ng g pipe hoge |
サービス | ng g service hoge |
ガード | ng g guard hoge |
クラス | ng g class hoge |
インターフェース | ng g interface hoge |
列挙 | ng g enum hoge |
ng-bootstrap
ngx-bootstrap
- https://valor-software.com/ngx-bootstrap
- BootstrapをAgularアプリケーションから利用
Angular-CLIから利用
インストール
npm install ngx-bootstrap bootstrap --save
src/app/app.module.ts の編集
import { AlertModule } from 'ngx-bootstrap'; : @NgModule({ : imports: [AlertModule.forRoot(), ... ], : })
.angular-cli.json に以下を追加
"styles": [ "../node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.css" ],
src/app/app.component.html に以下を追加
<alert type="success">hello</alert>
Angular Material
$ ng add @angular/material
カスタムテーマ
- https://www.creative-tim.com/product/material-dashboard-angular2
- Angular Material でカスタムカラーを使う
- カラーパレット生成
- カラーシステム
- Angular Materialのカスタムテーマを設定
- Angular Material _palette.scss
非同期通信
app.module.ts
import { HttpModule } from '@angular/http'; : @NgModule({ : imports: [ : HttpModule, ], : })
データバインディング
構文
データ方向 | 種類 | 記法 |
---|---|---|
コンポーネント -> ビュー | Interpolation(補間) | {{...}} |
コンポーネント -> ビュー | プロパティ/属性バインディング | [property]='value' |
ビュー -> コンポーネント | イベントバインディング | (event)='handler' |
コンポーネント <-> ビュー | 双方向バインディング | [(target)]='value' |
フォーム要素 | input/textarea/selectなど,
フォーム要素をバインドするには、ngModel を利用する |
<textarea [(ngModel)]="memo"></textarea> |
テンプレート参照変数
- テンプレート内の特定の要素を参照するための変数
- 与えられた変数経由でプロパティなどにアクセス
#変数名
- (change)="0"は、イベントトリガーで値を更新するため必要
<input #txtHoge type="text" (change)="0"/> <div>{{txtHoge.value}}</div>
双方向バインディング
- ビューの値とコンポーネントの値を双方向に同期させる
- AngularでForm要素を操作するためには、FormModuleが必要
- import と @NgModule の imports に FormModuleを追加
ルートモジュール(app.modules.ts)
import { FormsModule } from '@angular/forms'; : @NgModule({ : imports: [ BrowserModule, FormsModule, :
ビュー
- input/textarea/selectなどフォーム要素をバインドするには、ngModel を利用する
- このためには、name属性の指定が必須
- ngModelを[(ngModel)]とする
<select name="selAcion" [(ngModel)]="selectedAction"> <option *ngFor="let item of testActions" value="{{item}}" >{{item}}</option> </select>
コンポーネント
export class AccountComponent implements OnInit { testActions: string[] = [,'login','logout','check']; selectedAction: string = ;
入力値の加工
- 上記は、プロパティバインディング、イベントバインディングを組み合わせて、双方向を実現している。
- データバインディング時に値を加工する場合、[(ngModel)] を [ngModel]と(ngModelChagen) に分解
- $eventは入力値そのものを表す
<input name="hoge" type="text" [ngModel] = "hogeName" (ngModelChange) = "hogeName=$event.toUpperCase()" />
イベントバインディング
- ビューからコンポーネントに情報を引き渡す仕組み
- イベントとイベントハンドラーの紐付け
<element (event)="exp"></element>
- 主なevent:click,dblclick,mousedown,mouseup,mouseenter,mousemove,mouseleave,focus,blur,keydown,keypress,keyup,input,select,reset,submit
イベント情報を取得する $event
- イベントハンドラーを呼び出す際に$eventを明示的に渡す必要がある
<input type="button" (click)="show($event)" value="hoge" />
- イベント処理後デフォルトの動作をキャンセルするには、event.preventDefault
- イベントバブリングを止めるには、event.stopPropagation
- app.component.ts
import { Component } from '@angular/core'; import { componentFactoryName } from '@angular/compiler'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.sass'] }) export class AppComponent { title = 'Hot Dog Status'; status = ; save(e: any) { this.status = "Save"; } load(e: any) { this.status = "Load"; } }
.
- appcomponent.html
<h1 id="hotDogStatus">{{title}}:{{status}}</h1> <input type="textField" id="latestHotDogStatus" /> <button (click)="save($event)">Save</button> <button (click)="load($event)">Load</button> <router-outlet></router-outlet>
属性/クラス/スタイルバインディング
- コンポーネント -> ビューへのバインディングはプロパティバインディングが基本
- 状況によりプロパティバインディングができない場合、またコードが冗長になる場合に備え以下が準備されている
属性バインディング
- HTML属性にバインド
[attr.name] = "exp"
- name:属性名
- exp:任意の式
クラスバインディング
- スタイルクラスに特化し、スタイルの着脱をシンプルに表現できる
[class.name] = "exp"
- name:スタイルクラス名
- exp:任意の式
スタイルバインディング
- スタイル属性をバインディングできる
[style.name] = "exp"
- name:スタイルプロパティ名
- exp:任意の式
- 単位付きスタイルプロパティ
[style.name.unit] = "exp"
- unit:単位
- 例
[style.font-size.%] = "size"
Form実装例
ラジオボタン
- テンプレート
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)"> <ng-container *ngFor="let itm of radioSample; index as i"> <label> <input type="radio" name="radioSample" [(ngModel)]="selected" [value]="item.value" [checked]="selected == itm.value" (change)="onRadioChange(i)">{{ itm.label }} </label> </ng-container> <button>submit</button> </form>
- コンポーネント
selected = "3"; radioSample = [ {label:'One', value:'1'}, {label:'Twe', value:'2'}, {label:'Three', value:'3'}, {label:'Four', value:'4'}, {label:'Five', value:'5'} ]; onRadioChange(index:number) { console.log(index); } onSubmit(form: NgForm) { console.log(form.value); }
ディレクティブ
- 標準的なHTMLに対して、ngFor、ngStyleなどの独自要素、属性を追加することで機能を付与
- 大きく以下の3種類に分類
分類と主なディレクティブ
種類 | 概要 |
コンポーネント | テンプレートをともなう |
構造ディレクティブ | 要素を追加削除することで、文書ツリーを操作 |
属性ディレクティブ | 属性形式で、要素、コンポーネントの見た目や動作を変更 |
分類 | 名前 | 概要 |
構造 | ngIf | 真偽により表示切り替え |
ngSwitch | 値により表示切り替え | |
ngFor | 配列のループ処理 | |
ngTemplateOutlet | 用意されたテンプレートの内容をインポート | |
ngComponentOutlet | 用意されたコンポーネントをインポート | |
属性 | ngStyle | 要素にスタイルプロパティを付与 |
ngClass | 要素にスタイルクラスを着脱 | |
ngPlural | 数値に応じて出力切り替え |
ngIf
<element *ngIf="condition"> contents </element>
- element:任意の要素
- condition:条件式
- contents:条件式が真の時に表示されるコンテンツ
- ngIf=falseは、要素を生成しない(頻繁に表示、非表示を切り替える場合、スタイルバインディングでdisplayを利用する)
- 例
- template
<mat-toolbar *ngIf="isToolbar">...</mat-toolbar>
- component
export class AppComponent { isToolbar = true; :
テンプレートを切り替える
- templateはng-templateで宣言できる
<div *ngIf="flag; then trueContents; else elseContents>この部分は表示されない</div> <ng-template #trueContentas>trueの場合表示される</ng-template> <ng-template #elseContentas>falseの場合表示される</ng-template>
ngSwitch
- 指定された式の値に応じて、表示すべきコンテンツを切り替え
- 合致がない場合、ngSwitchDefaultを尿字
<parent [ngSwitch]="exp"> <child *ngSwitchCase="value1"> ... </child> <child *ngSwitchCase="value2"> ... </child> : <child *ngSwitchDefault> ... </child> </parent>
ngFor
- 指定された配列から順に要素を取り出し内容をループする
- 指定された要素を繰り返すが、ng-container というダミーのコンテナに適用すると、含まれる要素一式を繰り返すことができる
- 要素の追加、削除をトラッキングするには、トラッキング式(trackBy関数)を利用し、要素を識別させる
<element *ngFor="let tmp of list"> ... </element>
ngFor配下で利用できる特殊変数
- 使用する場合、式の中で、index as i のようにローカル変数に代入する必要がある
変数 | 概要 |
index | ループ回数 |
first | 最初の要素か |
last | 最後の要素か |
even | indexが偶数か |
odd | indexが奇数か |
ngStyle
- 複数スタイルをまとめて設定できる
- スタイル指定は、キャメルケースもしくは、ハイフン付き表現を引用符で囲む
<element [ngStyle]="objStyle"> ... </element>
- 例
@Compnent({ : template: '<div [ngStyle]="style"></div>' }) export class AppComponent { style = { backgroundColor: '#f00', 'font-weight': 'bold' }; }
ngClass
- スタイルクラスを着脱する
<element [ngClass]="clazz"> ... </element>
- 引数(clazz部分)に指定できる形式
変数 | 概要 |
文字列 | スタイルクラス名(空白区切りで複数指定可) |
配列 | スタイルクラス名のリスト |
オブジェクト | [スタイルクラス名:有効/無効]形式 |
ngPlural
- 式の値がnumに一致する場合、ng-template配下のメッセージを表示する
<element [ngPlural]="exp"> <ng-template ngPluralCase="num">message</ng-template> : </element>
- num : =0,=1,other などを指定できる
ngTemplateOutlet
- 予め用意されたテンプレートをコンポーネントの任意の位置に挿入する
<ng-container *ngTemplateOutlet="exp; context : ctx"></ng-container>
- exp : テンプレート
- ctx : テンプレートに反映させるオブジェクト
ngComponentOutlet
- 予め用意したコンポーネントを動的にビューにインポートする
<ng-container *ngComponentOutlet="exp"></ng-container>
フォーム
- Angularでは標準のform/input要素が拡張されている
- 利用するためには、FormsModuleをインポートする
ngForm
- Angularでは標準formが拡張されている
- 以下のような属性を宣言しておく
属性 | 概要 |
#myForm='"ngForm'" | ngFormディレクティブを変数にセット |
(ngSubmit)='"show()'" | サブミット時に呼び出す処理 |
novalidate | HTML5の検証機能を無効化 |
input
- input/rextareaなどのフォーム要素も拡張されている
<input id="main" name="mail" type="email" [(ngModel)]="user.mail" required email #mail="ngModel" />
属性 | 概要 |
ngModelディレクティブ | コンポーネントプロパティとバインド、検証機能を有効化するために必須 |
name属性 | Angularが内部的にフォーム要素を識別するためのキー、必ず指定 |
#mail='"ngModel'" | テンプレート参照変数、後からフォーム要素の状態にアクセスできる |
required | 必須 |
minlength | 文字列最小値 |
maxlenght | 文字列最大値 |
メールアドレス形式 | |
pattern | 正規表現パターンにマッチするか |
min | 数値最小値 |
max | 数値最大値 |
errorsオブジェクト
- 検証の成否を参照
入力要素名.errors?.検証型
- 例
mail.errors?.required
状態検知
内容 | 書式 |
検証項目単位でのエラー有無 | 入力要素名.error.検証型 |
フォーム/入力項目単位でのエラー有無 | 入力要素名.valid、入力要素名.invalid |
フォーム/入力項目項目が変更されていない | 入力要素名.pristine |
フォーム/入力項目項目が変更された | 入力要素名.dirty |
フォーム/入力項目項目にフォーカスが当たった | 入力要素名.touched |
フォーム/入力項目項目にフォーカスが当たっていない | 入力要素名.untouched |
- Angularでは上記状態に応じて以下のスタイルクラスが設定される
スタイルクラス | 概要 |
ng-valid | 入力値が妥当 |
ng-invalid | 入力値が不正 |
ng-pristine | 初期値から変更されていない |
ng-dirty | 初期値から変更された |
ng-touched | フォーカスが当たったことがある |
ng-untouched | フォーカスが当たったことがない |
ng-submitted | サブミットされた |
- スタイルシートを準備(初期状態でエラーとならないように、ng-dirty & ng-invalid)
input.ng-dirty.ng-invalid { background-color: #fee; }
- CSSでA,Bどちらも指定されている時に有効:.A.B
- CSSでA,Bどちらかが指定されている時に有効:.A,.B
ルーティング
適用
プロジェクト作成
> ng new app --routing
--routing オプションを付与せずにプロジェクトを作成した場合
- /src/app/app-routing.module.ts を追加
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = []; @NgModule({ imports: [ RouterModule.forRoot(routes), ], exports: [RouterModule] }) export class AppRoutingModule { }
- /src/app/app.module.ts
import { AppRoutingModule } from './app-routing.module'; : @NgModule({ imports: [ BrowserModule, AppRoutingModule, ], providers: [],
- /src/app/app.component.spec.ts
import { RouterTestingModule } from '@angular/router/testing'; : describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule ], declarations: [ AppComponent ], }).compileComponents(); }));
コンポーネント作成
> ng g component account --routing
ルーティング定義
- app-routing.module.ts
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { AccountComponent } from './account/account.component' const routes: Routes = [ { path: 'account', component: AccountComponent } ]; @NgModule({ imports: [ RouterModule.forRoot(routes), ], exports: [RouterModule] }) export class AppRoutingModule { }
利用
<a routerLink="/account">Account</a> <router-outlet></router-outlet>
クエリパラメータ
import { ActivatedRoute } from '@angular/router'; : export class AppComponent implements OnInit { isToolbar: boolean = true; ngOnInit(): void { let outer = this; this.route.queryParamMap.subscribe( paramsMap => { const fullscreen = Boolean(paramsMap.get("fullscreen")); outer.isToolbar = !fullscreen; }); }
モジュール
- ある程度の規模の場合、コードをモジュールに分割し、関連クラスをまとめておく
- アプリ構成の見通しがよくなる
- 機能の着脱が容易になる
- 起動時に呼び出されるモジュールを、ルートモジュール、またはメインモジュールという(QuickStartのapp.module.tsなど)
定義
- @NgModuleデコレーターを宣言することで、モジュールと見做される
- デコレーターの主なパラメーター
パラメーター名 | 概要 |
imports | 現在のモジュールで利用する他のモジュール |
exports | 現在のモジュールから外部へ公開するコンポーネントなど |
declarations | 現在のモジュールに属するコンポーネントなど |
bootstrap | アプリで最初に起動すべき最上位のコンんポーネント |
id | モジュールのID値 |
コンポーネント
- ページを構成するUI部品
- Angularアプリとは1つ以上のコンポーネントの集合体といえる
- 一般的には複数のコンポーネントを組み合わせてページを構成する
定義
- @Componentデコレーターで宣言する
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `<h1>Hello {{name}}&kt;/h1>`, }) export class AppComponent { name = 'Angular'; }
- templateに含まれる テンプレート:... は、Interpolation(補間)と呼ばれ、ビュー変数を埋め込むためのプレースホルダーとして機能する
- Angularでは、コンポーネントの側でデータ(ビュー変数)を用意しておき、テンプレート側でデータを埋め込む場所や表示方法を定義するのが基本となる
- ビュー変数の役割をコンポーネントのプロパティが担う(上記では、nameがプロパティでテンプレートから自由に参照できる変数となる)
パラメータ名 | 概要 |
---|---|
selector | コンポーネントを適用すべき要素を表す |
template | コンポーネントに適用するビュー |
ライフサイクル
- ライフサイクルメソッドを利用する際は対応するインターフェースを実装する
- インターフェース名は、以下の一覧から ngを除いたもの
ライフサイクル | 内容 |
---|---|
コンポーネント生成 | |
コンストラクター | |
ngOnChanges | @Input経由で入力値が設定/再設定された |
ngOnInit | ngOnChangesで入力値(@Inputp)が処理された後、コンポーネントの初期化時(最初のngOnChangesメソッドの後で一度だけ)
入力プロパティに基づく処理は、コンストラクタでなく、ここで行う必要がある 一般的にコンポーネントの初期化処理はOnInitに集約する |
ngDoCheck | 状態の変更を検出したとき |
ngAfterContentInit | 外部コンテンツを初期化した時(最初のngDoCheckメソッドの後で一度だけ) |
ngAfterContentChecked | 外部コンテンツの変更をチェックした時 |
ngAfterViewInit | 現在のコンポーネントと子コンポーネントのビューを生成した時(最初のngAfterContentCheckedメソッドの後で一度だけ) |
ngAfterViewChecked | 現在のコンポーネントと子コンポーネントのビューが変更された時 |
ngOnDestroyed | コンポーネントが破棄される時
タイマーのクリア、Observableの購読解除など |
コンポーネント破棄 |
@ViewChildrenデコレーター : 子コンポーネントを取得
- https://stackoverflow.com/questions/32693061/how-can-i-select-an-element-in-a-component-template
- 現在のビューに配置された子コンポーネントを取得する
- ViewChildrenデコレーターを適用したプロパティにはQueryListがセットされます。QueryListはいわゆるコレクションクラスとなっており、検出された複数の要素をより効率的に参照できるようなAPIを提供しています
- QueryListが提供するAPI
@ViewChildren(child) prop
- child: 取得する子コンポーネント、prop:プロパティ名
@ViewChildデコレーター : 単一の子コンポーネントを取得
- 取得すべきコンポーネントが単一であることが分かっている場合は、配列を返す@ViewChildrenではなく、@ViewChildを利用できる
- child: 取得する子コンポーネント、prop:プロパティ名
@ViewChild(child) prop
コンポーネントの連携
- 複数のコンポーネントが互いに連携して1つの機能を構成
- 一般的なアプリケーションでは、複数のコンポーネントが連携して1つの機能(ページ)を構成する
- @Input() と @Output() を使うことで、 親のコンテキストと子のディレクティブやコンポーネントとの間でデータをシェアすることができます。
- @Input() プロパティは書き込み可能である一方、@Output() プロパティは観測可能です。
@Input デコレーター : コンポーネントを入れ子に配置
- https://angular.jp/guide/inputs-outputs
- プロパティ定義に@Inputデコレーターを付与することで、コンポーネントの属性として値を受け取ることができる
- 親側から入れ子のコンポーネント属性に値を設定
@Component({ selector: 'parent', template: `<div><app-feed-item [feed]="feed"></app-feed-item></div>` }) export class Parent implements OnInit { feed: Feed; ngOnInit(): void { this.feeds = new Feeds(); } }
- 入れ子になったコンポーネントのプロパティに、@Inputを付与することで、親側でセットした値を受け取ることができる
@Component({ selector: 'app-feed-item', template: `<div>{{feed.title}}</div>` }) export class FeedItemComponent { @Input() feed: Feed; }
- セッター、ゲッターに対してデコレーターを付与することもできる
export class FeedItemComponent { private feed: Feed; @Input() set Feed(feed:Feed) { this.feed = feed; } get Feed() { return this.feed; } }
@Outputデコレーター : 子コンポーネントからイベントを受け取る
- https://angular.jp/guide/inputs-outputs
- Outputデコレーターを使用することによって、子コンポーネントのイベントを親コンポーネントへ通知できる
@Component({ selector: 'app-feed-item', template: `<div>{{feed.title}}</div>` }) export class FeedItemComponent { @Input() feed: Feed; @Output() edited = new EventEmitter<Feed>(); onSubmit() { this.edited.emit(this.feed); } }
- 親側では、以下で受けられる。
- ここで、$event は、上記Feedが渡される
<element (edited)="onEdited($event)"> ... </element>
- 別名を与えることもできる
@Output('changed') edited = new EventEmitter<Feed>();
- その場合、受け側は以下
<element (changed)="onEdited($event)"> ... </element>
コンポーネント配下のコンテンツをテンプレートに反映させる ng-content
- <ng-content> を使用することで呼び出し元で指定したコンテンツをテンプレートに埋め込むことが可能になる
コンポーネントのスタイル定義
- @Componentデコレーターの属性に指定する
- styles : スタイルシートの文字列
- styleUrls : cssファイルのパス
- コンポーネントで指定されたコンポーネントスタイルは原則としてコンポーネントの中だけで有効
- コンポーネントスタイルで使用されているセレクター式はコンポーネントローカル
- styles
@Component({ selector: 'app-nav-menu', templateUrl: './nav-menu.component.html', styles: [` a.navbar-brand { white-space: normal; text-align: center; word-break: break-all; } `,` html { font-size: 14px; } `,` @media (min-width: 768px) { html { font-size: 16px; } } `,` .box-shadow { box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); } `] }) export class NavMenuComponent {
- styleUrls
@Component({ selector: 'app-nav-menu', templateUrl: './nav-menu.component.html', styleUrls: ['./nav-menu.component.css'] }) export class NavMenuComponent {
- その他の定義方法
- テンプレートに style タグを埋め込む
- テンプレートに link タグを埋め込みcssファイルを指定
- スタイルシートから@Importディレクティブによるインポート
コンポーネントスタイルで利用できる特殊セレクター
コンポーネント本体にスタイルを適用するhost擬似セレクター
- host擬似セレクターはコンポーネント自体を表す
- テンプレートはコンポーネント配下に展開され、コンポーネントそのものはテンプレートの一部ではない
- コンポーネント要素へはhostセレクター以外ではアクセスできない
@Component({ selector: 'app-fetch-data', templateUrl: './fetch-data.component.html', styles: [ `:host { display: block; border: 3px solid red; background-color: Blue; }` ] }) export class FetchDataComponent { :
- host(スタイルクラス名): { ... } : 特定のスタイルクラス名を持つ場合だけ有効
コンポーネントの内部状態に応じてにスタイルを適用するhost-context擬似セレクター
サービス
- サービスクラスであることの条件は、@Injectable デコレータを付与することのみ。
- @Injectable デコレータは、コンポーネントに対してサービスを引き渡せることを意味する。
登録
- モジュールにサービスを登録する。
- コンポーネントにも登録できる(@Componentデコレーターのprovidersパラメータ)。この場合コンポーネントと子コンポーネントのみで利用できる。
import { HogeService } from './hoge.service'; @NgModule({ : providers: [HogeService], : })
依存性注入
使用する
- 使用するためには、コンストラクタに対応する引数を追加するだけ
- 以下の例の場合、this.hogeService.メソッド で利用可能
export class FooComponent { constructor( private hogeService: HogeService ){} }
Providerであることの条件は以下のプロパティを持つこと
プロパティ | 内容 |
---|---|
provide | サービスを注入する際に利用するDIトークン |
useXxxxx | サービスの生成方法 例 userClass: XXXX と指定するとクラス XXXX を常にnew でインスタンス化する |
multi | 同一のDIトークンに対して複数のProviderを追加するか |
useXxxx
プロパティ | 内容 |
---|---|
useClass | 指定されたクラスを注入のたびにインスタンス化 |
useValue | 指定されたオブジェクトを常に引き渡す(同じ値になる) |
useExisting | 指定されたトークンのエイリアスを生成 |
useFactory | 指定されたファクトリー関数で注入の際にオブジェクトを生成 |
useClass
- 常に新たなインスタンスを生成する
providers: [ { provide: HogeService, useClass: HogeService } ]
useValue
- 常に同じオブジェクトを注入する
- クラスのインスタンスを渡す
providers: [ { provide: HogeService, useVlaue: new HogeService() } ]
useExisting
- トークンのエイリアスを生成
providers: [ { provide: HogeService, useClass: HogeService }、 { provide: HogeAliasService, useExisting: HogeService }、 ]
<blockquote>互換性維持など、別のトークンから同一インスタンスを取得したい場合などに利用</blockquote>
useFactory
- ファクトリー関数経由でインスタンスを生成
providers: [ { provide: HogeService, useFactory: () => { let service = new HogeService(); service.foo = "bar"; return service; } } ]
パイプ
- テンプレート上に埋め込まれたデータを加工・整形する
- 与えられた式の値を加工/整形する
呼び出し構文
{{exp | pipe [:param1 [:param2 ..]]}}
- 式とパイプは、| で区切る
- パイプのパラメーターは、: で列記
- | を重ねることで複数パイプを適用
- 例
{{ price | currency : '¥' }}
標準パイプ
パイプ | 内容 |
---|---|
lowercase | 小文字変換 |
uppercase | 大文字変換 |
titlecase | 単語の先頭を大文字変換 |
slice | 文字列から部分文字列を切り出し |
date | 日付・時刻整形 |
number | 数値を桁区切り |
percent | パーセント形式 |
currency | 通貨形式 |
json | オブジェクトをJSON変換 |
i18nPlural | 数値によって表示文字列をへんか |
i18nSelect | 文字列に応じて出力を切替 |
async | Observable/Promiseによる非同期処理の結果を取得 |
作成
- 小文字に変換してブランクを指定文字に置き換える
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'globCategory' }) export class GlobCategoryPipe implements PipeTransform { transform(value: string, ...args: string[]): string { if (value != null) { const replacement = ((args != null && args[0] != null)?args[0]:'-'); value = (value.toLowerCase()).replace(/ /g, replacement); } return value; } }
- 使用
<a *ngFor="let category of feed.categories" href="https://www.typea.info/blog/index.php/category/{{category | globCategory :'_'}}" target="_blank" class="category-link"> {{category}} </a>
Modules
Http
RxJs
- RxJsライブラリ
- https://angular.jp/guide/rx-library
リアクティブプログラミングは、データストリームと変更の伝播に関する非同期プログラミングのパラダイムです。 RxJS (Reactive Extensions for JavaScript) は、非同期またはコールバックベースのコード (RxJS Docs) の作成を容易にする observables を使用したリアクティブプログラミング用のライブラリ
- 非同期処理の既存のコードを observables に変換する
- ストリーム内の値を反復処理する
- 異なる型への値のマッピング
- ストリームのフィルタリング
- 複数のストリームの作成
Observable 作成関数
ベント、タイマー、promise などから observables を作成するプロセスを簡素化
promise から observable を作成
import { from } from 'rxjs'; // promise から Observable を作成 const data = from(fetch('/api/endpoint')); // 非同期の結果の購読を開始 data.subscribe({ next(response) { console.log(response); }, error(err) { console.error('Error: ' + err); }, complete() { console.log('Completed'); } });
カウンターから observable を作成する
import { interval } from 'rxjs'; // 一定間隔でで値を発行するObservable を作成 const secondsCounter = interval(1000); // 発行される値の購読を開始 secondsCounter.subscribe(n => console.log(`It's been ${n} seconds since subscribing!`));
イベントから observable を作成する
import { fromEvent } from 'rxjs'; const el = document.getElementById('my-element'); // マウスの移動を発行する Observable の作成 const mouseMoves = fromEvent(el, 'mousemove'); // マウス移動イベントを購読開始 const subscription = mouseMoves.subscribe((evt: MouseEvent) => { console.log(`Coords: ${evt.clientX} X ${evt.clientY}`); // スクリーンの左端をマウスが越えたら購読中止 if (evt.clientX < 40 && evt.clientY < 40) { subscription.unsubscribe(); } });
AJAX リクエストから observable を作成
import { ajax } from 'rxjs/ajax'; // AJAXリクエストを生成する Observable の作成 const apiData = ajax('/api/data'); // リクエストの生成を購読 apiData.subscribe(res => console.log(res.status, res.response));
オペレーター
- オペレーターは、コレクションの高度な操作を可能にするために、observables 基盤上に構築される関数です。たとえば RxJS は map()、filter()、concat()、flatMap() のようなオペレーターを定義
- オペレーターは設定オプションをとり、ソースとなる observable を受け取る関数を返します。この返された関数を実行するとき、オペレーターは observable が出力する値を観測、変換し、変換された値の新しい observable を返します
- RxのflatMapの使い方
Map operator
import { map } from 'rxjs/operators'; const nums = of(1, 2, 3); const squareValues = map((val: number) => val * val); const squaredNums = squareValues(nums); squaredNums.subscribe(x => console.log(x)); // Logs // 1 // 4 // 9
パイプ
パイプを使用するとオペレーターをリンクすることができます。パイプを使用すると、複数の機能を1つの機能にまとめることができます。pipe() 関数は、結合する関数を引数としてとり、実行時に順次関数を実行する新しい関数を返します。
observable に適用される�オペレーターのセットは、レシピ、つまり関心のある値を生成するための一連の命令です。それだけではレシピは何もしません。レシピを通して結果を出すには subscribe() を呼び出す必要があります。
単独利用
import { filter, map } from 'rxjs/operators'; const nums = of(1, 2, 3, 4, 5); // Observable を受け入れる関数の生成 const squareOddVals = pipe( filter((n: number) => n % 2 !== 0), map(n => n * n) ); // filter と map 関数を実行する Observable を生成 const squareOdd = squareOddVals(nums); // 結合された関数の実行を購読 squareOdd.subscribe(x => console.log(x));
Observable.pip
pipe() 関数は RxJS Observable のメソッドでもあるため、短く書き換え
import { filter, map } from 'rxjs/operators'; const squareOdd = of(1, 2, 3, 4, 5) .pipe( filter(n => n % 2 !== 0), map(n => n * n) ); squareOdd.subscribe(x => console.log(x));
コンポーネントからサービスのデータを購読する
Service
import { Subject } from 'rxjs/subject'; import { User } from './user'; @Injectable() export class AccountService { private userChangeAnnouncedSource = new Subject<User>(); userChangeAnnounced$ = this.userChangeAnnouncedSource.asObservable(); : announceUserChange(user: User) { this.userChangeAnnouncedSource.next(user); } :
Component
import { Subscription } from 'rxjs/Subscription'; export class AccountComponent implements OnInit, OnDestroy { user: User; subscription: Subscription ngOnInit() { this.subscription = this.accountService.userChangeAnnounced$.subscribe((user:User) => { this.user = user; }); } ngOnDestroy() { this.subscription.unsubscribe(); } :
DI
依存関係を @Optional として @Host で検索を制限
- コンポーネントが依存関係を要求すると、Angularはそのコンポーネントのインジェクターから開始し、 最初の適切なプロバイダーが見つかるまでインジェクターツリーを検索
- @Optional プロパティデコレーターは、依存関係が見つからない場合にnullを返す
- @Host プロパティデコレーターは、上方向への検索を ホストコンポーネント で停止==
@Inject を使用してカスタムプロバイダーを設定
- カスタムプロバイダーを使用すると、組み込みのブラウザAPIなど、暗黙的な依存関係に対する具体的な実装を提供できる
コンポーネントの DOM 要素を注入
プロバイダーを定
- 値プロバイダー: useValue
- クラスプロバイダー: useClass
- エイリアスプロバイダー: useExisting
- ファクトリープロバイダー: useFactory
UI
Angular Material
レスポンシブレイアウト
テスト
ユニットテスト
Karma
- https://karma-runner.github.io/2.0/index.html
- karma.conf.js
Firebase
Firestoreの利用
Tips
機能
router-outlet で @Outputしたい=
@Outputはできないが、onActivateでコンポーネント参照が取得できる
<router-outlet (activate)="onActivate($event)"></router-outlet>
- コンポーネント参照のメソッドを呼ぶなりする
onActivate(component: any /*Component*/) { }
Chrome拡張機能
Facebook SDK
Hello.js 認証をまとめるJavaScriptライブラリ
文法
Javascriptのグローバルオブジェクトを利用
declare const hoge;
コード
コードからルーティング
import { Router } from '@angular/router'; : constructor(private router: Router) {} : this.router.navigate(['/hoge'])
CSRF
- https://angular.io/api/common/http/HttpClientXsrfModule
- https://angular.io/guide/http
- Angular + HttpClientXsrfModule + Flask で CSRF
CORS対応
参照
Firebase
© 2006 矢木浩人