iT邦幫忙

2023 iThome 鐵人賽

DAY 19
0
Modern Web

從0開始的的Angular站台架設-Stnadalone 系列 第 19

D18 Rxjs的Effect與Proxy API串接與如何透過Service管理(下)

  • 分享至 

  • xImage
  •  
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { AppSettings } from 'src/app/core/settings/app.settings';

@Injectable({
  providedIn: 'root',
})
export class HttpService {
  private http = inject(HttpClient);
  private get<T>(path: string, headers?: HttpHeaders, params?: any): Observable<T> {
    return this.http.get<T>(path, {
      params: params,
      headers: headers,
    });
  }
  
  getFormDummy<T>(path: string, params?: any): Observable<T> {
    const headers = new HttpHeaders({ 'app-id': AppSettings.DUMMY_API_KEY });
    return this.get(path, params, headers);
  }
}

我們在昨天已經設置好了Dummy.io的專屬HTTPS,並設置了Header與代理出去的proxy參數,接下來我們將要用Service來進行我們整個相關資訊的管理

我們首先需要在我們的shared > service > user-info.service.ts中進行我們所需功能的宣告

在Service之中,我個人習慣有三種不同類型的Service Method,分別如下"

  • private action"XXXX"(){} Action Method 觸發Action
  • public getAPI"XXXX"(){} API Method 進行API的取用,僅回傳客製化後API
  • public "XXXX"(){} Business Method 業務邏輯的取用,理論上由此進行Action的擊發

當然,隨著我們的Methods的增長,也可以進行切分為user-info.action.service.tsuser-info.API.service.tsuser-info.service.ts

但現在的專案還算是中型的,設計上還不用太過切分,接下來我們看看user-info.service.ts的內容

import { Injectable, inject } from '@angular/core';
import { HttpService } from './http.service';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { GET_USER_LIST } from 'src/app/core/store/actions/user.actions';

@Injectable({
  providedIn: 'root',
})
export class UserInfoService {
  private store = inject(Store);
  private httpService = inject(HttpService);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  private getAPIUserList(): Observable<any> {
    return this.httpService.getFormDummy('/dummyApi/user', null);
  }
  actionGetUserList(): void {
    this.store.dispatch(GET_USER_LIST());
  }
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  getUserList(): Observable<any> {
    return this.getAPIUserList();
  }
}

然後轉頭在store > actions > user.action.ts建置我們所需的Actions

首先是我們發出的第一發子彈GET_USER_LIST,以及後續我們的狀態GET_USER_LIST_SUCCESSGET_USER_LIST_FAIL,當然我們未來也可以根據不同的狀態以及後續的連鎖不斷的用effect來串接不同的action在收束回reducer之中

import { createAction, props } from '@ngrx/store';

/**
 * 取得使用者清單
 */
export const GET_USER_LIST = createAction('[DETAIL] GET_USER_LIST');

export const GET_USER_LIST_SUCCESS = createAction(
  '[DETAIL] GET_USER_LIST_SUCCESS',
  props<{ userList: Array<unknown> }>()
);
export const GET_USER_LIST_FAIL = createAction(
  '[DETAIL] GET_USER_LIST_FAIL',
  props<{ userList: Array<null> }>()
);

在接下來建立user-info.effect.ts

import { Injectable, inject } from '@angular/core';
import { catchError, concatMap, map, of } from 'rxjs';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { UserInfoService } from 'src/app/shared/service/user-info.service';
import * as UserActions from '../actions/user.actions';
@Injectable()
export class UserEffects {
  private actions$ = inject(Actions);
  private userInfoService = inject(UserInfoService);

  /**
   * 取得UserList
   */
  userLogin$ = createEffect(() => {
    return this.actions$.pipe(
      ofType(UserActions.GET_USER_LIST),
      concatMap(() =>
        this.userInfoService.getUserList().pipe(
          map((result) => {
            if (result.data) {
              return UserActions.GET_USER_LIST_SUCCESS({
                userList: result.data,
              });
            } else {
              return UserActions.GET_USER_LIST_FAIL({
                userList: [],
              });
            }
          }),
          catchError(() =>
            of(
              UserActions.GET_USER_LIST_FAIL({
                userList: [],
              })
            )
          )
        )
      )
    );
  });
}

首先,我們必須透過pipe來讓我們在資料流中對Action做一些事情與調整

ofType就像是filter,幫我們挑出特定Type的Action讓我們針對他做後續的行為

接下來透過Rx.jsconcatMap()將我們串接API後的後續回傳Observable接在原先的資料流後面,相當於移花接木,又或是我把自強號後3節列車移除掉,換了N節莒光號的車廂接上去

接下來最為重要的就是,我的return值為新的Action,這個就像是鐵路的軌道切換器,在這邊我們就是要根據情境接到另一個Action,幸好軌道上沒有1個朋友與100個陌生人讓你做選擇

接下來我們將要把effect進行註冊,這同樣也是在app.config.ts之中,假如是原本的AppModule的話也是要注入Effect.Root()

app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './core/routes/app.routes';
import { provideStore } from '@ngrx/store';
import { reducer } from './core/store/reducers';
import { layoutReducerKey } from './core/store/reducers/layout.reducers';
import { provideHttpClient } from '@angular/common/http';
import { userReducerKey } from './core/store/reducers/user.reducers';
import { effects } from './core/store/effects';
import { provideEffects } from '@ngrx/effects';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideStore({
      [layoutReducerKey]: reducer[layoutReducerKey],
      [userReducerKey]: reducer[userReducerKey],
    }),
    provideHttpClient(),
    provideEffects(effects),
  ],
};

同樣的,我們也必須在reducer進行on的監聽
user.reducers.ts

import { createReducer, on } from '@ngrx/store';
import { GET_USER_LIST_FAIL, GET_USER_LIST_SUCCESS } from '../actions/user.actions';

export const userReducerKey = 'user';
export const initialState: userState = {
  userList: [],
};
export interface userState {
  userList: Array<unknown>;
}
export const reducer = createReducer(
  initialState,
  on(GET_USER_LIST_SUCCESS, (state, action) => {
    return {
      ...state,
      userList: action.userList,
    };
  }),
  on(GET_USER_LIST_FAIL, (state, action) => {
    return {
      ...state,
      userList: action.userList,
    };
  })
);

至此我們對於ActionEffectReducerSelector以及API的取用就有一定的了解了

關於Rxjs其實還有許多東西可以講得,例如forkjoin做多重API呼叫之類的,但有鑑於我們的目的是Angular-Standalone的站台架設,所以這邊真的非常推薦想要精進Rxjs的朋友看《全端人員開發天梯》、《打通RXjs任督二脈》,淺顯易懂的同時,深度的講解也十分精彩

明天我們將進行Router的講解與函式庫的安裝


上一篇
D17 Rxjs的Effect與Proxy API串接與如何透過Service管理(上)
下一篇
D19 Angular Router(上) Route基礎、/:id動態路由
系列文
從0開始的的Angular站台架設-Stnadalone 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言