我正在嘗試使用 RXJS 製作一個簡單的 TODO 應用程式。我有一個包含 TODO 任務的 JSON 伺服器模擬資料庫。
所以我最後得到了這個 TasksService:
@Injectable({ providedIn: 'root' }) export class TasksService { private _tasks : ITask[] = []; private _tasks$: BehaviorSubject<ITask[]> = new BehaviorSubject<ITask[]>([]); constructor (private _http: HttpClient) { } public getTasks() { this.getTasksObservableFromDb().pipe( tap( (tasks) => { this._tasks = tasks; this._tasks$.next(tasks); } ) ).subscribe(); return this._tasks$; } public addTask(task: ITask) { this._tasks.push(task); this._tasks$.next(this._tasks); } private getTasksObservableFromDb(): Observable<any> { return this._http.get<any>('http://127.0.0.1:3000/tasks'); }
當我新增任務時,我不想立即將它們發佈到伺服器。 因此,當我從伺服器取得任務時,我將它們儲存到 _tasks 屬性,然後將它們傳遞給我的 _tasks$:BehaviorSubject 的 next() 方法。 因為後來我想將我的任務批次發佈到伺服器,現在我只想讓它們在 Angular 中正確顯示。
在我的 AppComponent 中,我取得任務並將它們指派給我的任務屬性。
export class AppComponent implements OnInit { public tasks!:BehaviorSubject<ITask[]>; constructor (private _tasksService: TasksService) {} ngOnInit(): void { console.log('OnInit'); this.tasks = this._tasksService.getTasks(); } public addTask() { this._tasksService.addTask( { id: crypto.randomUUID(), isImportant: true, text: 'Added task' } ); } }
在我的 HTML 範本中,我使用非同步管道作為我的任務屬性並顯示我的任務:
<ng-container *ngFor="let task of tasks | async"> {{task.text}} {{task.id}} </ng-container> <button type="button" (click)="addTask()">Add Task</button>
但後來我不小心刪除了我的 TaskService 中的這一行:
this._tasks$.next(this._tasks);
所以我的方法現在看起來像這樣:
public addTask(task: ITask) { this._tasks.push(task); }
但是新增任務仍然有效!即使我沒有為我的BehaviorSubject 傳遞新任務數組,Angular 也會顯示新新增的任務。
所以我決定記錄我的任務中的值! :我的 AppComponent 類別中的BehaviorSubject<ITask[]> 屬性:
public addTask() { this._tasksService.addTask( { id: crypto.randomUUID(), isImportant: true, text: 'Added task' } ); this.tasks.pipe(tap((value) => console.log(value) )).subscribe(); }
且任務會如預期新增 - 每次取得包含一個任務的陣列時:
Array(3) [ {…}, {…}, {…} ] <- Add task button is clicked Array(4) [ {…}, {…}, {…}, {…} ] <- Add task button is clicked Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- Add task button is clicked
但是當我將這一行傳回 TaskService 中的 addTask 方法時:
this._tasks$.next(this._tasks);
我得到這些日誌:
Array(3) [ {…}, {…}, {…} ] <- Add task button is clicked -> one task is added Array(4) [ {…}, {…}, {…}, {…} ] <- Add task button is clicked -> one task is added Array(4) [ {…}, {…}, {…}, {…} ] <- I get the same array Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- Add task button is clicked -> one task is added Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- I get the same array Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- I get the same array once again
所以我有點迷失為什麼可觀察的行為是這樣的......也許我不完全理解 next() 方法?
P粉0806439752024-03-22 00:14:34
據我了解,這段程式碼有兩個問題。首先,您不知道為什麼控制台日誌有重複。其次,即使您沒有對行為主題呼叫“.next()”,您也不知道為什麼視圖會更新。
讓我們從第一個開始。
您需要了解 rxjs observables 和 BehaviourSubjects 是如何運作的。 普通的可觀察對象,當您訂閱它時,將等待發出某個值,然後每次發生時,它都會呼叫您附加到它的操作。例如:
exampleSubject = new Subject(); ngOnInit(): void { this.exampleSubject.pipe( tap(console.log) ).subscribe(); } emitValue() { this.exampleSubject.next("text"); }
現在請注意,在這段程式碼中,我們僅在 ngOnInit 中訂閱了一次。儘管如此,每次呼叫emitValue()方法(例如從按鈕)時,console.log都會被呼叫。這是因為訂閱會持續到取消訂閱為止。這意味著,每次對主題呼叫 next() 時都會呼叫該操作。
那麼當您訂閱多次時會發生什麼?讓我們試試看:
exampleSubject = new Subject(); ngOnInit(): void { subscribeToSubject(); // 1st subscribeToSubject(); // 2nd subscribeToSubject(); // 3rd this.emitValue(); } emitValue() { this.exampleSubject.next("text"); } subscribeToSubject() { this.exampleSubject.pipe( tap(console.log) ).subscribe(); }
我們訂閱了一個主題 3 次,現在每當發出值時,控制台日誌都會被呼叫 3 次。您創建的每個訂閱都會呼叫它。
現在,當我們查看您的範例時,每次點擊 addTask 按鈕時都會新增訂閱。這就是為什麼每次新增任務時,都會多一個控制台日誌。 但是,嘿,在您的第一個範例中,當您刪除 .next() 時,您有一些控制台日誌,即使您沒有發出任何值,這是為什麼呢?現在我們來到BehaviourSubject。這是一種特殊的主題,它擁有其價值,並在訂閱後立即發出它。而且每次你調用 .next() 時它也會發出,但你沒有這樣做,所以這就是為什麼它每次訂閱時都會調用 1 個控制台日誌。
你應該做的是打電話
this.tasks.pipe(tap((value) => console.log(value) )).subscribe();
僅一次,例如在 ngOnInit() 中
好了,現在我們進入第二期
這非常簡單,但需要一些關於引用如何運作的知識。
在您的服務中,您將整個任務陣列放入 BehaviourSubject 中。事實上,這個主題持有這個陣列的引用。這意味著,每當您將新值推送到陣列中時,BehaviourSubject 也會擁有它。這就是發生的情況,每當您呼叫 addTask 方法時,都會將新值推送到任務數組,並且您的 BehaviourSubject 也有它。