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.ts
、user-info.API.service.ts
、user-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_SUCCESS
與GET_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,
};
})
);
至此我們對於Action
、Effect
、Reducer
、Selector
以及API的取用就有一定的了解了
關於Rxjs其實還有許多東西可以講得,例如forkjoin
做多重API呼叫之類的,但有鑑於我們的目的是Angular-Standalone的站台架設,所以這邊真的非常推薦想要精進Rxjs的朋友看《全端人員開發天梯》、《打通RXjs任督二脈》,淺顯易懂的同時,深度的講解也十分精彩
明天我們將進行Router的講解與函式庫的安裝