Angular:从Observable数组取随机元素时滚动导致元素频繁变化
Angular模板中从Observable数组获取固定随机元素的解决方案
问题根源
滚动时图片持续变化的核心原因:
- Angular每次执行变更检测(滚动、页面交互等操作都会触发)时,都会重新调用模板中的
getRandomTile$()方法,每次调用都会创建一个全新的Observable。 - 新Observable订阅后,
map操作符里会重新生成随机索引,导致每次都拿到新的随机元素,图片自然一直变化。 - 加
take(1)没用是因为每次调用方法都会生成新的Observable,每次都执行take(1),但变更检测会不断触发新的调用,还是会重复生成随机值。
解决方案
方案1:提前生成带随机元素的组合数组(推荐)
将othertiles和tiles$结合,一次性生成包含随机元素的数组,只有当原始数据变化时才重新计算,避免变更检测重复触发。
组件代码:
import { combineLatest, of } from 'rxjs'; import { map, shareReplay } from 'rxjs/operators'; @Select(SomeState.tiles) tiles$: Observable<Tile[]>; othertiles: any[]; // 你的原始数组 tilesWithRandom$: Observable<Array<{item: any, randomTile?: Tile}>>; ngOnInit() { this.tilesWithRandom$ = combineLatest([this.tiles$, of(this.othertiles)]).pipe( map(([tiles, items]) => { if (!tiles.length) return items.map(item => ({item})); // 为每个符合条件的项生成随机Tile return items.map((item, index) => { if (index % 8 === 0) { const randomTile = tiles[Math.floor(Math.random() * tiles.length)]; return { item, randomTile }; } return { item }; }); }), shareReplay(1) // 缓存结果,避免重复计算 ); }
模板代码:
<ng-container *ngFor="let entry of tilesWithRandom$ | async"> <div *ngIf="entry.randomTile"> <img [src]="entry.randomTile.imgUrl" /> </div> </ng-container>
方案2:静态缓存随机元素(适合othertiles固定不变的场景)
如果othertiles不会动态变化,可以在组件初始化时,为符合条件的索引生成一次随机元素并缓存,后续直接使用缓存值。
组件代码:
@Select(SomeState.tiles) tiles$: Observable<Tile[]>; othertiles: any[]; randomTilesCache = new Map<number, Tile>(); ngOnInit() { this.tiles$.pipe( filter(tiles => tiles.length > 0), take(1) // 如果tiles后续会更新,去掉take(1) ).subscribe(tiles => { this.othertiles.forEach((_, index) => { if (index % 8 === 0) { const randomTile = tiles[Math.floor(Math.random() * tiles.length)]; this.randomTilesCache.set(index, randomTile); } }); }); }
模板代码:
<ng-container *ngFor="let tile of othertiles; index as i"> <div *ngIf="i % 8 == 0 && randomTilesCache.has(i)"> <img [src]="randomTilesCache.get(i)?.imgUrl" /> </div> </ng-container>
方案3:缓存每个索引对应的Observable
为每个符合条件的索引创建唯一的Observable并缓存,避免每次变更检测生成新流。
组件代码:
import { filter, map, take, shareReplay } from 'rxjs/operators'; @Select(SomeState.tiles) tiles$: Observable<Tile[]>; othertiles: any[]; randomTileObsCache = new Map<number, Observable<Tile>>(); getRandomTileForIndex(index: number): Observable<Tile> { if (!this.randomTileObsCache.has(index)) { const obs = this.tiles$.pipe( filter(tiles => tiles.length > 0), map(tiles => tiles[Math.floor(Math.random() * tiles.length)]), take(1), // 若需tiles更新后同步随机元素,去掉take(1) shareReplay(1) ); this.randomTileObsCache.set(index, obs); } return this.randomTileObsCache.get(index)!; } // 优化*ngFor的变更检测 trackByIndex(index: number): number { return index; }
模板代码:
<ng-container *ngFor="let tile of othertiles; index as i; trackBy: trackByIndex"> <div *ngIf="i % 8 == 0"> <ng-container *ngIf="getRandomTileForIndex(i) | async; let randomTile"> <img [src]="randomTile.imgUrl" /> </ng-container> </div> </ng-container>
内容的提问来源于stack exchange,提问作者Applecow
相关产品推荐
相关产品推荐

