AngularのForm(Template-driven Forms)メモ
(Angular v.4.3.6を利用)
<select>
を利用する
*ngFor
を利用する- AngularJSの
ng-options
のようなものはない
- AngularJSの
<select [(ngModel)]="model.power" name="power"> <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option> </select>
NgFormとは
- https://angular.io/guide/forms#the-ngform-directive
Angular automatically creates and attaches an
NgForm
directive to the<form>
tag.
- https://angular.io/api/forms/NgForm
Creates a top-level FormGroup instance and binds it to a form to track aggregate form value and validation status.
<form #heroForm="ngForm">
ngModelとname属性
NgModel
インスタンスを取得
- template reference variableを利用する
<input type="text" [(ngModel)]="model.name" name="name" #name="ngModel">
参考
- https://angular.io/api/forms/NgModel
NgModel
はNgControl
をextendsしたもの- exportAsプロパティが'ngModel'
Directive({ selector: '[ngModel]:not([formControlName]):not([formControl])', providers: [formControlBinding], exportAs: 'ngModel' })
バリデーション
- https://angular.io/guide/form-validation#template-driven-validation
you add the same validation attributes as you would with native HTML form validation.
You can then inspect the control's state by exporting
ngModel
to a local template variable.
<input>
関連その他
参考
<input>
操作時のイベントを取得したい
<input (keyup)="onKey($event)">
$event
オブジェクトのプロパティはDOMイベントのタイプによって変わる
値の確認
onKey(event: KeyboardEvent) { console.log((<HTMLInputElement>event.target).value); }
$event
を利用せずに値を確認
(DOMイベント全体を渡さずに、値だけを渡したい。)
<!-- template reference variables を利用 --> <input #box (keyup)="onKey(box.value)">
キーイベントをフィルタリングしたい
<!-- Enterキーに反応 --> <input #box (keyup.enter)="onEnter(box.value)">
Angular Routerメモ
(Angular v.4.3.6を利用)
- 参考
- リンクを設定したい
- ts内で遷移の制御をしたい
- コンポーネント内でパラメータを取得したい
- 特定のRouteに紐付けないパラメータを利用したい
- 前回のURLを取得したい
- アプリ初期時に処理を追加したい
- Routerのナビゲーションイベントを確認したい
- コンポーネントの再利用について知りたい
- RouterとActivatedRouteについて知りたい
- その他
参考
リンクを設定したい
routerLink
を利用
通常
<a routerLink="/path">link</a> <!-- relative routerLink --> <a routerLink="./path">link</a>
パラメータがついたリンク
<!-- route param (/path/1) --> <a [routerLink]="['/path', 1 ]">link</a> <!-- matrix param (/path;matrixParam=value) --> <a [routerLink]="['/path', { matrixParam: 'value' } ]">link</a> <!-- query param (/path?page=1) --> <a [routerLink]="['/path']" [queryParams]="{ page: 1 }">link</a>
アンカー(#)がついたリンク
<!-- fragment (/path#anchor) --> <a [routerLink]="['/path']" fragment="anchor">link</a>
アクティブなリンクにクラスを設定したい
routerLinkActive
を利用
<a routerLink="/path" routerLinkActive="active">link</a> <a routerLink="/path" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">link</a> <a routerLink="/path" [routerLinkActive]="['active', 'border']">link</a>
ts内で遷移の制御をしたい
- コンポーネント、サービス内で制御したい時
Router#navigate
を利用
通常
this.router.navigate(['/path']); // relative link this.router.navigate(['path'], { relativeTo: this.route });
パラメータをつける
// route param (/path/1) this.router.navigate(['path', '1'], { relativeTo: this.route }); // matrix param (/path;matrixParam=value) this.router.navigate(['/path', { matrixParam: 'value' }]); // query param (/path?page=1) this.router.navigate(['/path'], { queryParams: { page: 1 } });
アンカー(#)をつける
// fragment (/path#anchor) this.router.navigate(['/path'], { fragment: 'anchor' });
クエリを保存して遷移したい
NavigationExtras#queryParamsHandling
を利用- https://angular.io/api/router/NavigationExtras#queryParamsHandling
preserve
,merge
が利用できる
- その他にも
NavigationExtras
を利用して、「ヒストリーにpushしない」「ヒストリーを置き換える」などの設定が可能
参考
コンポーネント内でパラメータを取得したい
Observableで取得
constructor(private route: ActivatedRoute) {} ngOnInit() { /** * Routeの設定が { path: 'view/:id' } * URLが /view/10;matrixParam=value?page=1 の場合 */ this.route.paramMap .subscribe((params: ParamMap) => { // params.keys.forEach(key => { const routeParam = params.get('id'); // -> 10 const matrixParam = params.get('matrixParam'); // -> value // }); }); this.route.queryParamMap .subscribe((params: ParamMap) => { const queryParam = this.route.snapshot.queryParamMap.get('page'); // -> 1 }); }
route.snapshot
から取得
constructor(private route: ActivatedRoute) {} // ... /** * Routeの設定が { path: 'view/:id' } * URLが /view/10;matrixParam=value?page=1 の場合 */ const routeParam = this.route.snapshot.paramMap.get('id'); // -> 10 const matrixParam = this.route.snapshot.paramMap.get('matrixParam'); // -> value const queryParam = this.route.snapshot.queryParamMap.get('page'); // -> 1
this.route.snapshot
はActivatedRouteSnapshot
- 初期データしか利用しない場合に利用
特定のRouteに紐付けないパラメータを利用したい
- クエリパラメータを利用
- https://angular.io/guide/router#query-parameters-and-fragments
In the route parameters example, you only dealt with parameters specific to the route, but what if you wanted optional parameters available to all routes? This is where query parameters come into play.
ルートパラメータの例では、ルートに固有のパラメータのみを扱いますが、オプションのパラメータをすべてのルートで使用できるようにしたい場合はどうなりますか?ここでクエリパラメータが有効になります。
- https://angular.io/guide/router#query-parameters-and-fragments
前回のURLを取得したい
アプリ初期時に処理を追加したい
- (Routerとは直接関係ないけど)
APP_INITIALIZER
を利用- 設定ファイルのロードや権限確認などに利用できそう
@NgModule({ providers: [ { provide: APP_INITIALIZER, useFactory: (sites:SitesService) => () => sites.load(), deps: [SitesService], multi: true, } ] }) export class AppModule { }
@Injectable() export class SitesService { constructor() { } load(): Promise<any> { return new Promise((resolve, reject) => { setTimeout(() => { // doing something // ... resolve(); }, 3000); }); } }
参考
- https://angular.io/api/core/APP_INITIALIZER
A function that will be executed when an application is initialized.
- What is best way to load json settings from the server? · Issue #9047 · angular/angular
- Hook into Angular Initialization Process – Hacker Noon
Routerのナビゲーションイベントを確認したい
Router.events
を利用- イベント一覧
constructor(private router: Router) { // 遷移開始時のイベントを確認 router.events.subscribe(event => { if (event instanceof NavigationStart) { console.log(event); // リダイレクトさせることも(一応)可能 // if (event.url !== '/view-a') { // router.navigate(['view-b']); // } } }); }
その他
- イベントのログをコンソールで確認(デバッグ用)
RouterModule.forRoot(appRoutes, { enableTracing: true })
コンポーネントの再利用について知りたい
routeConfig
が同じならコンポーネントは再利用される- パラメータが変わっても再描画はされない
// https://github.com/angular/angular/blob/54e02449549448ebab6f255f2da0b4396665c6f0/packages/router/src/route_reuse_strategy.ts#L66 shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { return future.routeConfig === curr.routeConfig; }
カスタマイズしたい
RouteReuseStrategy
を利用して挙動を変更できる
参考
- Force reload/refresh current route with RouteReuseStrategy · Issue #13831 · angular/angular
- Angular 2 Component Reuse Strategy – Julia Passynkova – Medium
RouterとActivatedRouteについて知りたい
- Angular Router: Understanding Router State – Angularを読むといいと思います
Any component instantiated by the router can inject its ActivatedRoute.
ActivatedRoute provides access to the url, params, data, queryParams, and fragment observables.
その他
複数のRouteを表示したい
<router-outlet></router-outlet> <!--popupという名前のoutletを設定 --> <router-outlet name="popup"></router-outlet> <!-- `path: 'compose'`に紐づけられているコンポーネントを表示 --> <a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
// `path: 'compose'`に紐づけられているコンポーネントを表示 this.router.navigate([{ outlets: { popup: 'compose' } }]); // null でクリアできる this.router.navigate([{ outlets: { popup: null }}]);
「遷移前に認証チェックをしたい」「遷移前に未保存のデータがあるか確認をしたい」など
CanActivate
,CanActivateChild
,CanDeactivate
を利用
遷移前にデータを読み込みたい
Resolve
を利用Observable
,Promise
(あるいは同期データ)を返すresolve
を定義する- https://angular.io/guide/router#fetch-data-before-navigating
- 読み込んだデータは
ActivatedRoute#data
やActivatedRoute#snapshot.data
から取得する- https://angular.io/api/router/ActivatedRoute#data
*(Routeで設定した
data
と同じように扱えるようになる)
- https://angular.io/api/router/ActivatedRoute#data
*(Routeで設定した
遅延読み込み
基本設定
- https://angular.io/guide/router#lazy-loading-route-configuration
loadChildren: 'app/admin/admin.module#AdminModule'
読み込み開始前にガードしたい
canLoad
を利用
プリロードさせたい
preloadingStrategy
を利用
RouterModule.forRoot( appRoutes, { // PreloadAllModules // https://angular.io/api/router/PreloadAllModules // guardされていないrouteが全てプリロードされる preloadingStrategy: PreloadAllModules } )
- PreloadingStrategyをカスタムすることも可能
@Injectable() export class CustomPreloadingStrategy implements PreloadingStrategy { preload(route: Route, load: () => Observable<any>): Observable<any> { // routeの設定情報に `data: { preload: true }` があればプリロードする if (route.data && route.data['preload']) { return load(); } else { return Observable.of(null); } } }
Angular CLIでビルドされたファイルの容量を把握する
把握
--stats-json
オプションを有効にしてstats.json
を出力ng build --prod --stats-json
- https://github.com/angular/angular-cli/wiki/build
webpack-bundle-analyzer
を利用
–build-optimizer
容量の把握の話とは関係ないですが、Angular CLI 1.3から--build-optimizer
が導入されています。
ng build --prod --build-optimizer
RxJSの各種オペレーターなどをimportする場合
全部をimportしない
// 禁止 import 'rxjs/Rx';
ライブラリ全体が読み込まれてしまいます。
TSLintでエラーが出るようにしておくと安全です。
"import-blacklist": [ true, "rxjs", "rxjs/Rx", ]
import用のファイルを用意する
rxjs-add.ts
import 'rxjs/add/observable/empty'; import 'rxjs/add/observable/forkJoin'; import 'rxjs/add/observable/fromPromise'; import 'rxjs/add/observable/of'; import 'rxjs/add/observable/throw'; // ... import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/concatMap'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/merge'; import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/startWith'; // ...
app.module.ts
import './rxjs-add';
angular-cliのユニットテスト対応
テスト起動ファイル(test.ts
)にimport './app/rxjs-add';
など記述するのを忘れないように
Angular CLIでメインアプリとは別にページ(html)を用意する
stories asset configuration · angular/angular-cli Wiki · GitHub
assets
に、対象のhtmlかそれを含むディレクトリを設定する。
"assets": [ "assets", "favicon.ico", "static/test.html" ]
これでhttp://localhost:4200/static/test.html
が利用できる。
単純なhtmlページではなく複数のAngularアプリケーションが必要な場合は以下の方法で。
AngularのDatePipeのlocaleを変更したい場合(タイムゾーンを変更したい場合、も)
(Angular v.4.3.6を利用)
起動時にlocaleを設定 (JIT)
LOCALE_ID
を設定
@NgModule({ imports: [ BrowserModule, ], declarations: [ AppComponent ], providers: [ { provide: LOCALE_ID, useValue: navigator.language // useValue: 'ja' } ], bootstrap: [AppComponent] }) export class AppModule { }
動的にlocaleを変更
@Pipe({ name: 'date' }) export class DatePipeProxy implements PipeTransform { constructor(private localizationService: LocalizationService) { } public transform(value: any, pattern: string = 'mediumDate'): any { const ngPipe = new DatePipe(this.localizationService.locale); return ngPipe.transform(value, pattern); } }
タイムゾーンを変更
- 直接
Intl.DateTimeFormat
を利用するPipeをつくるDateTimeFormatOptions
のtimeZone
を設定- DatePipeの内部でも
DateTimeFormat
を利用しているが、現状だとtimeZone
を変更するAPIはない
参考Url
カレンダー生成
生成の流れ
- 対象月の日数を調べる
- カレンダー上で、1日の前に何日(何マス)あるか
- カレンダー上で、最終日の後ろに何日(何マス)あるか
- カレンダーに表示される日数を調べる
- 前月、翌月も含んだ数
- 日数分の配列を生成
- chunkする
- 最終的に
[Array(7), Array(7), Array(7), Array(7), Array(7)]
のような配列になる
- 最終的に
サンプル
週の開始を変更したい場合
import chunk from 'lodash/fp/chunk'; import compose from 'lodash/fp/compose'; import map from 'lodash/fp/map'; import range from 'lodash/fp/range'; import * as moment from 'moment'; const DAYS_PER_WEEK = 7; export function createCalendar( m = moment(), // 週の最初の曜日 // default(0)は日曜 startDayOfWeek = 0) { // 指定した月の1日 const startOfMonth: moment.Moment = m.clone().startOf('month'); // 月の日数 const daysInMonth: number = m.clone().endOf('month').date(); // カレンダー上で、1日の前に何日(何マス)あるか const daysBefore: number = getDaysBefore(startOfMonth, startDayOfWeek); // カレンダー上で、最終日の後ろに何日(何マス)あるか const daysAfter: number = getDaysAfter(daysBefore, daysInMonth); // カレンダーに表示する日数分の配列を生成 const diffs: number[] = getDiffs(daysBefore, daysInMonth, daysAfter); return compose( chunk(DAYS_PER_WEEK), map((diff) => startOfMonth.clone().add(diff, 'd')) )(diffs); } function getDaysBefore(startOfMonth: moment.Moment, startDayOfWeek: number): number { const dayOfWeek = startOfMonth.day(); return (dayOfWeek + DAYS_PER_WEEK - startDayOfWeek) % DAYS_PER_WEEK; // if (dayOfWeek > startDayOfWeek) { // return dayOfWeek - startDayOfWeek; // } else { // return dayOfWeek + DAYS_PER_WEEK - startDayOfWeek; // } } function getDaysAfter(daysBefore: number, daysInMonth: number): number { return DAYS_PER_WEEK - (daysBefore + daysInMonth) % DAYS_PER_WEEK; } function getDiffs(daysBefore, daysInMonth, daysAfter): number[] { return range((daysBefore * -1), daysInMonth + daysAfter) }