9月リンク集

これは何か

  • 9月に調べたことのメモ。基本形式は、「参考URLとそれに関するメモとコメント」。
  • Angular関連がほとんど。

NgRx, アプリケーションの状態管理

A Comprehensive Introduction to @ngrx/store - Companion to Egghead.io Series

// combine multiple state slices
Observable.combineLatest(
  store.select('people'),
  store.select('events'),
  (people, events) => {
    // projection here
})

Using NgRx 4 to Manage State in Angular Applications

NgRx: Patterns and Techniques – nrwl

platform/README.md at master · ngrx/platform · GitHub

Rx関連

Rxのオペレータメモ #rx · GitHub

  • 自分用のメモ

mergeMapとswitchMap

RxJS を学ぼう #2 – よく使う ( と思う ) オペレータ15選 – NET BIZ DIV. TECH BLOG

Angular関連

angular - @HostBinding and @HostListener: what do they do and what are they for? - Stack Overflow

  • @HostBindingと@HostListenerのシンプルな利用例

コンポーネントを動的に表示

Change Detectionについて

私がMVCフレームワークをもはや使わない理由

Running Protractor tests on Webdriver 2.47.1 gets - Error: Server terminated early with status 1 · Issue #2638 · angular/protractor · GitHub

  • Macを買い換えたらE2Eのテストがこけてしまった
    • 原因はJavaのバージョン
      • export JAVA_HOME="/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home"

RouteReuseStrategy

  • 実際にはこのような使い方をしていないがメモとして
/**
 * コンポーネントの再描画のルールを変更
 * https://medium.com/@juliapassynkova/angular-2-component-reuse-strategy-9f3ddfab23f5
 */
export class CustomRouteReuseStrategy implements RouteReuseStrategy {

  // copy from DefaultRouteReuseStrategy
  shouldDetach(route) { return false; }
  store(route, detachedTree) { }
  shouldAttach(route) { return false; }
  retrieve(route) { return null; }

  // override
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return this._shouldReuseRoute(future, curr) && this.isUnChanged(future, curr);
  }
  private _shouldReuseRoute(future, curr) {
    return future.routeConfig === curr.routeConfig;
  }

  /**
   * 追加ルール
   * 特定コンポーネントかつ、`key`が変更した時
   */
  private isUnChanged(future, curr) {
    const name = future.component && (<any>future.component).name;
    if (name === 'BarComponent') {
      if (future.paramMap.get('key') && curr.paramMap.get('key') && future.paramMap.get('key') !== curr.paramMap.get('key')) {
        return false;
      }
    }
    return true;
  }
}

テスト

selenium - Debugging “Element is not clickable at point” error - Stack Overflow

Angular (4)で設定したLocale IDを取得

LOCALE_IDを利用。
以下のようにAOTでコンパイルしていない場合、初期値のen-USになります。

ng serve --aot --locale ja
import { Component, OnInit, LOCALE_ID, Injector } from '@angular/core';

export class FooComponent implements OnInit {

  constructor(private injector: Injector) { }

  ngOnInit() {
    const locale = this.injector.get(LOCALE_ID);
    // -> 'ja'
  }
}

雑メモはGist (Lepton) に

f:id:ryotah:20170911192300p:plain

プログラムに関する備忘録をこのブログに書いていましたが、それくらいの文章やコードならGistにあげればいいのかと思い始めました。

Leptonというアプリがあるのでしばらくそれを使ってみようかと思います。

Quiver、SnippetsLabなど他のアプリも試してみましたが、今のところLeponが自分の用途にはあってそうです。

  • Lepton
    • GIstのクライアントアプリ
    • 無料
  • Quiver
  • SnippetsLab

以下ページも参考になりそうです。

AngularのTemplate Syntax

Angular - Template Syntaxを読んだメモです。

(v4.3.6対応)

Binding一覧

<!-- Property -->https://angular.io/guide/template-syntax
<img [src]="heroImageUrl">
<hero-detail [hero]="currentHero"></hero-detail>
<greeting message="hello"></greeting><!-- プロパティが文字列かつテンプレートに直接埋め込む場合 -->

<!-- Attribute -->
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>

<!-- Class -->
<div class="bad curly special" [class]="badCurly">Bad curly</div><!-- `badCurly`で上書きされる -->
<div [class.special]="isSpecial">The class binding is special</div><!-- 特定のクラスのon/off -->

<!-- Style -->
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>

<!-- Event -->
<button (click)="onSave()">Save</button>

参考

Operator一覧

<!-- Pipe -->
<div>Title through uppercase pipe: {{title | uppercase}}</div>

<!-- Safe navigation operator (`?.`) -->
The null hero's name is {{nullHero?.name}}

<!-- Non-null assertion operator (`!`)-->
<div *ngIf="hero">
  The hero's name is {{hero!.name}}
</div>

参考

Built-in attribute directives

ngClass

ngStyle

ngModel

Two-way

export class SizerComponent {
  @Input() size: number | string;
  @Output() sizeChange = new EventEmitter<number>();
    ...
    resize(delta: number) {
    this.sizeChange.emit(this.size);
  }
}
<!-- 以下コードは同じ結果になる -->
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
<my-sizer [(size)]="fontSizePx"></my-sizer>

*ngForのtrackByを使って描画対象かどうかを判断する

Template reference variables ( #var )

その他

DOM propertyとは

Template expressions/statementsで利用できないJS構文

Template expressionsのガイドライン

AngularのForm(Reactive Forms)メモ

ryotah.hatenablog.com 別記事でこんなのも書きました。


(Angular v.4.3.6を利用)

Reactive Formとは

Template-driven Formsとの比較

  • Template-driven Forms
    • ngModelのようなディレクティブを利用して、フォーム要素とデータモデルを紐づける。(FormControlは自動で生成されている。)入力内容が変更されるとミュータブルなデータモデルが更新される。
  • Reactive Form
    • 最初にFormControlのツリーを生成する。フォームとの紐付けはformControlNameプロパティなどでおこなう。

Template-driven Forms

<form #heroForm="ngForm">
  <input type="text" [(ngModel)]="model.name" name="name">
</form>

Reactive Forms

<form [formGroup]="heroForm">
  <input formControlName="name">
</form>
import { FormControl, FormGroup } from '@angular/forms';
...
heroForm = new FormGroup({
  name: new FormControl()
});

主要4クラス

  • AbstractControl
    • 基本クラス。他3クラスはこの抽象クラスをextendsしている。
  • FormControl
    • 個々のフォーム要素(<input>など)の値と有効性の状態を追跡
  • FormGroup
    • AbstractControlのグループの値と有効性の状態を追跡
  • FormArray
    • AbstractControlの配列の値と有効性の状態を追跡

FormGroup, FormArrayを利用した例

f:id:ryotah:20170831210750p:plain

<form [formGroup]="heroForm">
  <div>
    <label>Name:
      <input formControlName="name">
    </label>
  </div>

  <!-- アドレスの数は可変 -->
  <div formArrayName="addresses">
    <div *ngFor="let address of addresses.controls; let i=index" [formGroupName]="i">
      <div><b>Address {{i  + 1}}</b></div>
      <label>City:
        <input formControlName="city">
      </label>
    </div>
  </div>
  <button (click)="addAddress()">add</button>
</form>
<pre>Form value: {{ heroForm.value | json }}</pre>
<pre>Form status: {{ heroForm.status | json }}</pre>
heroForm: FormGroup;
constructor(private fb: FormBuilder) {

  // `FormBuilder`を利用した`FormGroup`の生成
  // https://angular.io/guide/reactive-forms#introduction-to-formbuilder
  this.heroForm = this.fb.group({
    name: '',
    addresses: this.fb.array([]),
  });
}

addAddress() {
  this.addresses.push(this.fb.group({ city: '' }));
}

get addresses(): FormArray {

  // 個々の`FormControl`, `FormGroup`, `FormArray`は `get`で取得する
  // https://angular.io/guide/reactive-forms#inspect-formcontrol-properties
  return this.heroForm.get('addresses') as FormArray;
}

フォームの値を変更する

フォームの変更を監視する

const nameControl = this.heroForm.get('name');
nameControl.valueChanges.forEach(
  (value: string) => console.log(value)
);

データモデルとフォームモデル

  • データモデルを使いフォームモデル(FormControl)を作成
  • ユーザー操作の結果、更新されるのはフォームモデル
  • Submitなどのタイミングでフォームモデルから送信用のデータモデルを作成
  • データモデルとフォームモデルの構造は一緒である必要はない(当然、似ている方が楽ではあるが)

バリデーション

(あとで追記)

AngularのForm(Template-driven Forms)メモ

(Angular v.4.3.6を利用)

<select>を利用する

  • *ngForを利用する
    • AngularJSのng-optionsのようなものはない
<select [(ngModel)]="model.power" name="power">
  <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
</select>

NgFormとは

<form #heroForm="ngForm">

ngModelとname属性

  • ngModelを利用すると、自動的にFormControlインスタンスが生成される
  • FormControlインスタンスはname属性に割り当てられた名前でNgFormに登録される

NgModelインスタンスを取得

  • template reference variableを利用する
<input type="text" [(ngModel)]="model.name" name="name" #name="ngModel">

参考

Directive({
  selector: '[ngModel]:not([formControlName]):not([formControl])',
  providers: [formControlBinding],
  exportAs: 'ngModel'
})

バリデーション

<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を利用)

参考

リンクを設定したい

通常

<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内で遷移の制御をしたい

通常

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' });

クエリを保存して遷移したい

参考

コンポーネント内でパラメータを取得したい

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

特定の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.

        ルートパラメータの例では、ルートに固有のパラメータのみを扱いますが、オプションのパラメータをすべてのルートで使用できるようにしたい場合はどうなりますか?ここでクエリパラメータが有効になります。

前回の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);
    });
  }
}

参考

Routerのナビゲーションイベントを確認したい

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;
}

カスタマイズしたい

参考

RouterとActivatedRouteについて知りたい

その他

複数の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 }}]);

「遷移前に認証チェックをしたい」「遷移前に未保存のデータがあるか確認をしたい」など

遷移前にデータを読み込みたい

遅延読み込み

基本設定

読み込み開始前にガードしたい

プリロードさせたい

RouterModule.forRoot(
  appRoutes,
  {
    // PreloadAllModules
    //   https://angular.io/api/router/PreloadAllModules
    //   guardされていないrouteが全てプリロードされる
    preloadingStrategy: PreloadAllModules
  }
)
@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);
    }
  }
}