余白

https://blog.lacolaco.net/ に移転しました

Angular 頻出実装パターン その1

僕がAngularアプリケーションを書くときに頻出する実装パターンを紹介する記事です。続くかどうかは未定です。

onDestroy$

ngOnDestroyメソッドが呼び出されたタイミングでemitされるEventEmitterを作っておき、RxJSのtakeUntilパイプなどで使う実装パターン。 ngOnDestroyメソッド内でunsubscribeメソッドを呼び出すよりも宣言的で意味が取りやすいし、忘れにくい。

実装例はこんな感じ。ReactiveFormsModuleを使うときにvalueChangesに引っ掛けることが多い。

import { Component, OnDestroy, OnInit, EventEmitter, Output } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-form',
  template: `
  <form [formGroup]="form">
    <input formControlName="name">
  </form>
  `
})
export class FormComponent implements OnDestroy {
  @Output() valueChange = new EventEmitter<any>();

  readonly form = new FormGroup({
    name: new FormControl(),
  });

  private readonly onDestroy$ = new EventEmitter();

  constructor() {
    this.form.valueChanges
      .pipe(
      takeUntil(this.onDestroy$),
    )
      .subscribe(value => {
        this.valueChange.emit(value);
      });
  }

  ngOnInit() {
    this.form.patchValue({
      name: 'Angular 5'
    });
  }

  ngOnDestroy() {
    this.onDestroy$.emit();
  }
}

https://stackblitz.com/edit/angular-vdnrbp

MaterialModule

Angular Materialのモジュールを束ねるための中間モジュールを作るパターン。 使っているモジュールが一箇所でわかるのと、TestBedに依存モジュールとして渡すのが楽になるので有用。 DI経由でグローバルに適用するAngular Materialの設定もここにまとめられるのでわかりやすい。

import { NgModule } from '@angular/core';
import {
  MatButtonModule,
  MatCardModule,
} from '@angular/material';
import { PortalModule } from '@angular/cdk/portal';
import { OverlayModule } from '@angular/cdk/overlay';

export const modules = [
  MatButtonModule,
  MatCardModule,
  OverlayModule,
  PortalModule,
];

@NgModule({
  imports: [...modules],
  exports: [...modules],
  providers: [
    {
      provide: MAT_LABEL_GLOBAL_OPTIONS,
      useValue: {
        float: 'always',
      },
    },
  ],
})
export class MaterialModule {}

safeHtmlパイプ

これはみんな書くでしょ、って気がする。与えられた文字列を安全なHTMLですよ、とマーキングしてAngularに渡すお仕事。

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Pipe({
  name: 'safeHtml',
})
export class SafeHtmlPipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}

  transform(value: string): any {
    return this.sanitizer.bypassSecurityTrustHtml(value);
  }
}

provideXXX関数

サービスのプロバイダを関数としてエクスポートして、モジュール側で関数を呼び出すパターン。 何をprovideしているかがわかりやすい。 複数のサービスを一気にプロバイドする場合、特に順序が重要になるHTTP_INTERCEPTORSのようなmultiなプロバイダにおいて、モジュール側にそれを意識させずに済むのが気に入ってる。

export function provideHttpInterceptor() {
  return [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthorizationHeaderInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: CredentialInterceptor,
      multi: true,
    },
  ];
}
import { provideHttpInterceptor } from './config/http';

@NgModule({
  ...
  providers: [
    provideHttpInterceptor(),
  ],
})
export class CoreModule {}

書いてみるとそれほど種類がないなという気がしてきたので、次回は未定です。