Angular Service 獨立管理

RxJS 的 BehaviorSubject

利用 RxJS 的 BehaviorSubject,將原本儲存在各 Component 中的狀態,集中儲存在 Service 中,以方便各階層的元件透過 DI 存取,而不需要透過元件間的互動來傳遞狀態數值。

Api Service 獨立管理

Configures Angular’s HttpClient service to be available for injection.
配置 Angular 的 HttpClient 服務以供注入。
provideHttpClient
src/app/app.config.ts

錯誤訊息:ERROR NullInjectorError: R3InjectorError(Environment Injector)[_ProductsService -> _ApiService -> _HttpClient -> _HttpClient]:
處理方式如下
引入 provideHttpClient
1
2
3
4
5
6
7
8
9
10
11
12
13
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
+ import {provideHttpClient} from '@angular/common/http';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
+ provideHttpClient(),
provideRouter(routes)
]
};
新增 api資料夾 http-provider.service.ts 與 web-api.service.ts 官網Injectable 注入
Injectable: 裝飾器將某個類別標記為可用,可以作為依賴項提供和注入。
Observable:
透過網路傳輸來取得,則是屬於「非同步任務」,理由是需要連線到伺服器,使用者的應用程式必須等待伺服器回傳資料。
參考資料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//web-api.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class WebApiService {
private readonly http = inject(HttpClient);

private readonly httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

get(url: string): Observable<any> {
return this.http.get(url, this.httpOptions)
.pipe(catchError(this.handleError));
}

post(url: string, model: any): Observable<any> {
return this.http.post(url, model, this.httpOptions)
.pipe(catchError(this.handleError));
}

put(url: string, model: any): Observable<any> {
return this.http.put(url, model, this.httpOptions)
.pipe(catchError(this.handleError));
}

delete(url: string): Observable<any> {
return this.http.delete(url, this.httpOptions)
.pipe(catchError(this.handleError));
}

private handleError(error: any) {
console.error('API 錯誤:', error);
return throwError(() => new Error(error.message || '錯誤已消失'));
}
}

http-provider.service.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { WebApiService } from './web-api.service';
import { environment } from '../../environment/environment';
import dayjs from 'dayjs'

@Injectable({ providedIn: 'root' })

export class HttpProviderService {
private readonly apiUrl = environment.apiUrl;
private readonly webApiService = inject(WebApiService);

getAllLists(): Observable<any> {
return this.webApiService.get(`${this.apiUrl}/api/toDoLists`);
}
// 編輯
deleteTodoById(_id: string): Observable<any> {
return this.webApiService.delete(`${this.apiUrl}/api/toDo/${encodeURIComponent(_id)}`);
}

getTodoById(id: string): Observable<any> {
return this.webApiService.get(`${this.apiUrl}/api/toDo/${encodeURIComponent(id)}`);
}
// 新增
addList(newTask: string): Observable<any> {
let query:any = {
title: newTask,
buildDate: Date.now(),
updataDate: Date.now(),
}
return this.webApiService.post(`${this.apiUrl}/api/toDo`,query);
}

// 編輯
updateTodo(item: any): Observable<any> {
let query:any = {
title: item.title,
buildDate: dayjs(item.buildDate).valueOf(),//改為時間搓
updataDate: Date.now(),
}
return this.webApiService.put(`${this.apiUrl}/api/toDo/${encodeURIComponent(item._id)}`, query);
}
}

頁面的引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
+ import { HttpProviderService } from '../api/http-provider.service';
import { addListsType } from '../Types/toDo';
import dayjs from 'dayjs'
@Component({
selector: 'app-home',
imports: [CommonModule,RouterModule,FormsModule,ToDoListsComponent ],
templateUrl: './home.component.html',
styleUrl: './home.component.scss'
})

+ export class HomeComponent implements OnInit {
+ private readonly httpProvider = inject(HttpProviderService);
}
todoLists = <addListsType[] >([]);
//獲取數據
async getAllList() {
this.httpProvider.getAllLists().pipe(
tap(lists => {
let arrLists:any[] = [];
lists.map(function(item: any) {
let query: any = {
_id: item._id,
title: item.title,
Editing: false, // 編輯
Status: false, //選取狀態
CanEdit: true, //可以編輯
buildDate: dayjs(item.buildDate).format('YYYY-MM-DD HH:mm'),
updataDate: dayjs(item.updataDate).format('YYYY-MM-DD HH:mm'),
};
arrLists.push(query);
// 排序
arrLists.sort((a, b) => {
return dayjs(b.buildDate).valueOf() - dayjs(a.buildDate).valueOf()
})

});
this.todoLists = arrLists;
}),
catchError((error: any) => {
console.error("獲取錯誤:", error);
return of([]);
})
).subscribe();

}

使用 BehaviorSubject 的狀態儲存器

Subject
Subject 可以接受 Observer 的訂閱。 也可以用來訂閱其他的 Observable,所以他本身既是 Observable 又是 Observer。

BehaviorSubject 繼承自 Subject具有同樣的功能,然而兩者最主要的差異,在於:

  1. BehaviorSubject 可以接受給定初值,而 Subject 不可以。
  2. Subject 只會在被訂閱之後,而所訂閱的 Observable 有發出新值時,才會做通知。而 BehaviorSubject 會在每一 Observer 訂閱時,對其發出目前已收到的最新值。
也因此 BehaviorSubject 這種類似『狀態暫存』的模式,很適合用來做狀態管理之用。
參考資料