「テキスト入力 -> 自動で検索開始」を実現するためのRx使い方

シュミレート用サンプルコード

テキスト入力の代わりに、画面クリックで一連の流れが確認できる簡易版コードです。 ブラウザのコンソールで実行させれば、動作確認できます。

http://reactivex.io/rxjs/などで実行してみてください。)

const sub = new Rx.BehaviorSubject();
const http = () => Rx.Observable.of('{ data }').delay(500);

document.addEventListener('click', (e) => {
  sub.next(e);
});

sub.debounceTime(1000)
  .distinctUntilChanged()
  .switchMap(() => {
    console.log('start http');
    const next$ = sub.skip(1);
    return http().takeUntil(next$);
  })
  .subscribe(val => console.log('get', val));

説明

  • .debounceTime
    • 入力中に送信されないようにする
  • .distinctUntilChanged
    • 前回と同じ値を送信しないようにする
  • .switchMap
    • 未完了のhttp通信があった場合、無視する
  • .takeUntil(next$)
    • debounceTimeで遅延させている間にhttp通信が完了する場合があるので、何かしらの入力変更があったらhttp通信を無視できるようにする

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などのタイミングでフォームモデルから送信用のデータモデルを作成
  • データモデルとフォームモデルの構造は一緒である必要はない(当然、似ている方が楽ではあるが)

バリデーション

(あとで追記)