在重構我的 Angular 應用程式時,我基本上想擺脫所有訂閱,以便僅使用asyncAngular 提供的管道(只是一種宣告性方法而不是命令性方法)。
當多個來源可能導致流中的更改時,我在實施宣告性方法時遇到了問題。如果我們只有一個來源,那么當然,我可以使用scan運算子來構建我的發射值。
設想
假設我只想擁有一個簡單的組件,在路由期間決議字串陣列。在組件中,我想顯示串列并希望能夠使用按鈕添加或洗掉專案。
限制
- 我不想使用
subscribe,因為我希望 angular 使用 (asyncpipe)來處理取消訂閱 - 我不想使用 BehaviorSubject.value,因為它(從我的角度來看)是一種命令式方法而不是宣告式方法
- 實際上我根本不想使用任何型別的主題(除了用于按鈕
click事件傳播的主題),因為我認為沒有必要。我應該已經擁有所有需要的可觀察物件,只需將它們“粘合在一起”即可。
到目前為止的當前流程 到目前為止, 我的旅程走了幾個步驟。請注意,所有方法都運行良好,但每種方法都有各自的缺點):
- 使用
BehaviorSubjectand.value創建新陣列 --> 不是宣告式的 - 嘗試
scan運算子并創建一個Action界面,其中每個按鈕都會發出一個型別的動作XY。此操作將在傳遞給的函式內部讀取scan,然后使用開關來確定要執行的操作。這感覺有點像 Redux,但是在一個管道中混合不同的值型別是一種奇怪的感覺(首先是初始陣列,然后是動作)。 - 到目前為止,我最喜歡的方法如下:我基本上通過使用來模仿 BehaviorSubject,
shareReplay并在我的按鈕中使用這個立即發出的值,通過切換到一個新的 observable usingconcatMap,我只取 1 個值以防止創建回圈。下面提到的示例實作:
串列-view.component.html:
<ul>
<li *ngFor="let item of items$ | async; let i = index">
{{ item }} <button (click)="remove$.next(i)">remove</button>
</li>
</ul>
<button (click)="add$.next('test2')">add</button>
串列視圖.component.ts
// simple subject for propagating clicks to add button, string passed is the new entry in the array
add$ = new Subject<string>();
// simple subject for propagating clicks to remove button, number passed represents the index to be removed
remove$ = new Subject<number>();
// actual list to display
items$: Observable<string[]>;
constructor(private readonly _route: ActivatedRoute) {
// define observable emitting resolver data (initial data on component load)
// merging initial data, data on add and data on remove together and subscribe in order to bring data to Subject
this.items$ = merge(
this._route.data.pipe(map((items) => items[ITEMS_KEY])),
// define observable for adding items to the array
this.add$.pipe(
concatMap((added) =>
this.items$.pipe(
map((list) => [...list, added]),
take(1)
)
)
),
// define observable for removing items to the array
this.remove$.pipe(
concatMap((index) =>
this.items$.pipe(
map((list) => [...list.slice(0, index), ...list.slice(index 1)]),
take(1)
)
)
)
).pipe(shareReplay(1));
}
盡管如此,我覺得這應該是最簡單的例子,而且我的實作對于這類問題似乎很復雜。如果有人可以幫助找到解決這個問題的方法,那就太好了,這應該是一個簡單的問題。
您可以在此處找到我的實作的 StackBlitz 示例:https ://stackblitz.com/edit/angular-ivy-yj1efm?file=src/app/list-view/list-view.component.ts
uj5u.com熱心網友回復:
您可以創建一個modifications$流,從您的“修改主題”中獲取每個發射,并將它們映射到一個將相應地修改狀態的函式:
export class AppComponent {
add$ = new Subject<string>();
remove$ = new Subject<number>();
private modifications$ = merge(
this.add$.pipe(map(item => state => state.concat(item))),
this.remove$.pipe(map(index => state => state.filter((_, i) => i !== index))),
);
private routeData$ = this.route.data.pipe(map(items => items[ITEMS_KEY]));
items$ = this.routeData$.pipe(
switchMap(items => this.modifications$.pipe(
scan((state, fn) => fn(state), items),
startWith(items)
))
);
constructor(private route: ActivatedRoute) { }
}
在這里,我們定義items$從路由資料開始,然后切換到將傳入的 reducer 函式應用于狀態的流。items我們使用路由資料中的初始值作為我們內部的種子值scan。我們還使用startWith初始發出初始items.
這是一個小小的StackBlitz示例。
uj5u.com熱心網友回復:
首先,您不需要主題來傳播 HTML 事件。
在底層,Angular 使用一個EventEmitter,它基本上是一個主題,在整個應用程式中傳播更改。
所以這
<button (click)="remove$.next(i)">remove</button>
應該變成這個
<button (click)="removeItem(item, i)">remove</button>
接下來,對于路線資料,您可以使用簡單的運算子為其創建主題
routeData$ = this._route.data.pipe(pluck('ITEMS_KEY'), shareReplay(1)),
現在,這為您的組件提供了更簡潔的代碼:
routeData$ = this._route.data.pipe(pluck('ITEMS_KEY'), shareReplay(1)),
constructor(private readonly _route: ActivatedRoute) {}
addItem(item: any) {
// ...
}
removeItem(item: any) {
// ...
}
最后,您必須決定這將如何影響您的資料。做什么addItem和removeItem應該做什么?你有幾個選擇,例如:
- 對您的 API 進行 http 呼叫
- 更新您的應用程式狀態
- 重定向到相同的路由,但更新資料/路由引數等
有了這個,您應該能夠擺脫所有訂閱并讓 Angular 為您完成作業。
更好的是,您現在可以切換到OnPush檢測策略并大大提高您的應用程式性能!
uj5u.com熱心網友回復:
這是我的看法: https ://stackblitz.com/edit/angular-ivy-nsxabg?file=src/app/list-view/list-view.component.ts
我認為使用 Subjects 來模擬事件是一種矯枉過正的做法,如果你只是為 items$ 在兩個函式中可觀察到的新狀態創建了新的狀態,那么你就有了一個簡潔的解決方案。
uj5u.com熱心網友回復:
雖然我認為@BizzyBob 的答案是最適用的(即回答問題,基于rxjs,簡單易懂)我想添加我自己的版本和狀態管理庫 effector.dev
原因:當您需要涉及一些外部資源時,它會再次變得復雜。不幸的是,這就是我不再喜歡基于 RxJS 的狀態管理器的原因。
我對效應器的選擇:演示
@Injectable()
export class ListViewEffectorService {
public items$ = createStore<string[]>([]);
public addItem = createEvent<string>();
public removeItem = createEvent<number>();
constructor(private readonly _route: ActivatedRoute, private ngZone: NgZone) {
this.ngZone.run(() => {
sample({
clock: fromObservable<string[]>(
this._route.data.pipe(map((items) => items[ITEMS_KEY] as string[]))
), // 1. when happened
source: this.items$, // 2. take from here
fn: (currentItems, newItems) => [...currentItems, ...newItems], // 3. convert
target: this.items$, // 4. and save
});
sample({
clock: this.addItem,
source: this.items$,
fn: (currentItems, newItem) => [...currentItems, newItem],
target: this.items$,
});
sample({
clock: this.removeItem,
source: this.items$,
fn: (currentItems, toRemove) =>
currentItems.filter((_, idx) => idx !== toRemove),
target: this.items$,
});
});
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/529092.html
