寝て起きて寝て

プログラミングが出来ない情報系のブログ

Angularでアプリケーションを作るときのメモ

Angularの色々な機能を使う時どんなのだっけなってなるので事前にメモっておく 機能が多いが故に色々なライブラリがあるので基本的には持っているものを使いたい

双方向バインディング

Vueのv-modelみたいなのはそのままでは使えない

app.module.tsFormsModule を importしてから使う

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; //これ

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

んで、コンポーネントのtsファイルに変数を用意してから html側で下記のようにして使う nameは入力要素をコンポーネント側から識別できるようにするためのものなので必須

<textarea
  [(ngModel)]="comment"
  name="name"
></textarea>

クリックイベント

html側で下記のように書く sendMessageは送信用の関数的なので気にしない

<button (click)="sendMessage(comment)" >送信</button>

DatePipe

AngularではテンプレートでDateフォーマット用のAPIが使える またangular/commonにformatDateという関数があるのでts側で使うならそれを使う

: の後ろに設定されているものはオプション

<small>{{date | date:'yyyy年MM月dd日 HH:mm'}}</small>
const date = Date.now()

表示

2021年10月10日 16:21

フォーマットに関してはドキュメントを参照

複数箇所で使う場合いちいち設定するのは面倒なのでフォーマット部分を .ts ファイルに記載して 定数化するかカスタムパイプを使って定義する。 カスタムパイプは別途記載

カスタムパイプ

ターミナルでng generateをするファイル名は適当

$ ng g pipe pipes/comment-date

実行するとpipesディレクトリの中に comment-date.pipe.ts というファイルが作られる 内容は下記の通り

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'commentDate'
})
export class CommentDatePipe implements PipeTransform {

  transform(value: unknown, ...args: unknown[]): unknown {
    return null;
  }

}

name:テンプレートから呼び出されるときの文字列 transformメソッド:テンプレートから渡ってくるデータを処理するパイプのメインの処理になる value:テンプレート側渡ってくるデータ args:パイプのオプションが渡ってくる。可変長引数になっているがこれはコロンをいくつも繋げてオプションを複数渡せるから

上のDatePipeでやったフォーマットに合わせて変えると下のような感じになる(transfrm部分だけ記載) formatDateはangular/commonのライブラリ

  transform(value: number, ...args: string[]): string {
    const format = args[0] || 'yyyy年MM月dd日 HH:mm'
    return formatDate(value, format, 'en-US');
  }

template側ではこんな感じ

<small>{{date | commentDate}}</small>

独自のフォーマットを使いたい場合は下記のような指定になる

<small>{{date | commentDate:'MM月dd日'}}</small>

item$ | async as item

基本的に下記のようにitemが取得できた時に表示させるような作りにするが、これだと item$item$.nameのようにドット形式でデータを参照することができない

<div *ngIf="item$ | async">
</div>

この時使うのが as キーワードを使う これを使うと解決されたデータをこのテンプレート内に保存できる。

あくまで div 内でのみ使える変数であることに注意 外からだとアクセスできない

<div *ngIf="item$ | async as item">
</div>

pipe内で使うやつ

Observerでpipeは使えるが、pipe内で使うやつは使えないのでimportする必要がある mapだったら

import { map } from 'rxjs/operators';

テンプレートでオブジェクト変数の値を見たい時

普通にテンプレート内でオブジェクトの変数を参照すると [object Object] と表示されるので

デバッグする場合は

<div>{{ user | json }}</div>

とすると表示されるようになる (vueだったらpreで囲めば見れるのに・・・・)

ng-container

ダミーのタグとして扱えるangularの組み込みディレクティブ こっちはvueのtemplateと同じ感じで使って良さそう

ng-template

htmlをデータとして保持するための組み込みディレクティブ データとして保持されるので画面上には出力されない vueのtemplateと同じ感じで使うというよりも ngIf の elseとかで使うようにする

NgModule

angularのデコレーターでこれを利用することでモジュールを定義できる

import export を利用してモジュール間で機能を共有できる機能 一番わかりやすいのは app.module.ts のimportsで設定するFormsModuleで 各モジュールで使えるようにする。

呼び出し先のモジュールでNgModuleをimportsに記述後 呼び出し元でも使えるようにするために呼び出し先のexportsに記載することで使えるようにできる

app.module.ts以外は作成すると CommonModule がimportされているが これがないとmoduleとして機能しないので消さないようにする(app-routing.moduleは不要なので消した方が良い)

NgModuleのベストプラクティス

モジュールのスタイルガイドでは下記構成がベストプラクティスとされている app.module以外の作成は ng g module shared みたいな感じで行うので app/shared/ といった感じで作成されているかも 使うときは忘れずにapp.module.tsにimportする

また、コンポーネントを作成した際使用したいモジュールを設定したい場合は

ng g module header  --module=app
や
ng g module header  --module=share

とように設定すると指定することができる (デフォルトは作成したところから一番近いモジュールが選ばれる)

RootModule:アプリ全体のモジュール(app.module.ts) SharedModule:共通モジュール →全てのモジュールで利用する共通化されたコンポーネントやパイプ、カスタムディレクティブを含むモジュール 各モジュールからimportされて使われる app.moduleに記載

FeatureModule:機能モジュール(画面単位) →括弧の通り画面単位で使うものをまとめたモジュール このモジュールに関してはapp.moduleではなくルーティングとセットで行う

CoreModule:一度だけ読み込むモジュール(バージョン7以降でスタイルガイドから削除) →serviceやcomponentやmockデータなど一度だけ読み込みたい時に使う 上のものは基本的にこの中にまとめておくとわかりやすい

router-outlet

これを、 <router-outlet></router-outlet> のように書くと routingしたページが表示される

ルーター上に設定されていないページの指定

app-router.module.tsで

const routes: Routes = [
  {path: '**', component:NotFoundComponent}
]

のように '**' で記載すると当てはまらない全ての画面で表示できる

htmlに書いてある#って何?

あれはフォーム変数で個別に名前を付けることができるで テンプレート変数という ngForm の値を送る時に使ったり ngIf のelseに飛ばすときの名前とかで使える

ルーティングの遅延読み込み

機能別にルーティングを分けたいときなどに使う 遅延読み込みの利点はダイナミックインポートを利用することで遷移してから 読み込まれるので表示が早くなる

実装方法

まずルーティングモジュールを作成 --routing で users-routing.module.tsの作成を行ってくれる

ng g module users --routing

次に画面コンポーネント作成 先ほどできたusersディレクトリに入れる

ng g component users/new-user

次に users-routing.mosule.ts の設定を行う

routes に先ほど作成した NewUserComponentを指定

const routes: Routes = [
  { path: 'new', component:NewUserComponent}
];

最後に app-routing.module.ts に今回作成したルーティングモジュールをダイナミックインポートする

routes 定数に下記を記載

const routes: Routes = [
  {path: '', component: ChatComponent},
  {path: 'users', loadChildren: ()=> import('./users/users.module').then(m=>m.UsersModule)}//遅延読み込み
]

アクセス制限をつける(Guard)

Guardはルーターモジュールのルート設定配列に canActivate などを設定することで実現できる

設定方法

ターミナルで設定したいguardの名前を入力

ng g guard core/guards/auth

下のように聞かれるので今回はCanActivateにチェックを入れエンター スペースで複数選択、iで選択項目の反転ができる

? Which interfaces would you like to implement? (Press <space> to select, <a> to to
ggle all, <i> to invert selection)
❯◉ CanActivate
 ◯ CanActivateChild
 ◯ CanDeactivate
 ◯ CanLoad

すると下記のようなファイル(auth.guard.ts)ができる canActivateのreturnでtrueだとアクセス可、falseだと不可になる boolean以外にの色々返せる

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
  }
  
}

ファイル自体はServiceとほとんど変わらないが、 implements されるのが CanActivate になっている

次に app-routing.module.ts で制限をかけたいファイルに先ほど作成したクラスを指定する 今回はsignup,loginに指定。 配列なので複数クラスを指定できるのに注意

const routes: Routes = [
  { path: '', component: ChatComponent },
  { path: 'users', loadChildren: () => import('./users/users.module').then(m => m.UsersModule) },
  { path: 'signup', component: SignUpComponent,canActivate:[AuthGuard] },
  { path: 'login', component: LoginComponent,canActivate:[AuthGuard] },
  { path: '**', component: NotFoundComponent }
];

次に auth.guard.ts に移り canActivate を下記のように設定

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return this.afAuth.authState.pipe(
      map((user:firebase.User)=>{
        if(!user){
          return true
        }
        else{
          this.router.navigateByUrl('/')
          return false
        }
      })
    )
  }

この場合returnにはObservableが帰ってくるがmap内の真偽値を見て遷移させるかとどまらせるかを設定している

UrlTree

上の書き方はUrlTreeを使うともっと簡単に書くことができる

・UrlTreeとは Angularのルーターが扱うpath情報が保存されているオブジェクト UrlTreeを作成して戻り値に指定するとシンプルなコードで実装ができる

UrlTreeオブジェクトはAngularの parseUrl() メソッドを使うと生成できる

変更するのはmap内のelse部分

        else{
          // this.router.navigateByUrl('/')
          // return false
          return this.router.parseUrl("/")
        }

Guardの種類

・CanActivate  対象パスへのページ遷移を許可するか ・CanActivateChild  対象パスの小ルートへの遷移を許可するか ・CanDeactivate  他のパスへの遷移を制限するか ・CanLoad  loadChildrenでモジュールを読み込めるか ・Resolve  対象パスへの遷移中に実行する中間処理を指定