Angular *ngFor存在隐式依赖 移除schedule$遍历后events不加载
问题根因
出现这个隐式依赖的核心原因是组件启用了OnPush变更检测策略,且手动订阅赋值的操作没有主动触发视图更新,具体逻辑如下:
- Angular的
OnPush变更检测策略不会主动追踪组件内部普通属性的赋值操作,只有三类场景会触发组件视图刷新:组件输入属性引用变更、组件模板内DOM事件触发、模板内async管道收到新的数据流。 - 你的
getEvents()方法里通过手动.subscribe()给this.events赋值的操作,不会自动把组件标记为待检测状态,哪怕数据已经请求成功,视图也不会重新渲染。 - 当模板里保留
schedule$ | async代码时,getCalendar()的请求返回后,async管道会自动调用markForCheck()方法标记组件需要刷新,这时候Angular执行变更检测流程,才会读取到已经赋值完成的events数组,把列表渲染出来。 - 一旦移除
schedule$相关的async管道,就没有触发器通知Angular刷新视图,哪怕events数据已经请求成功,视图也不会更新,就会出现列表消失的现象。
代码里还有几个不规范点,虽不是直接诱因,但容易引发其他问题:
ngOnInit里用逗号分隔两个方法调用属于不规范写法,应该用分号结尾- 模板里
*ngFor写在了<ul>标签上,会导致每一条数据渲染一个完整的<ul>,正确写法是把*ngFor写在内部的<li>标签上 getCalendar()和getEvent()请求的是完全相同的接口地址,属于重复冗余代码
解决方案
按照以下步骤修改即可完全移除schedule$相关代码,同时保证events列表正常渲染:
- 清理所有冗余的
schedule$相关代码- 删除组件类中
schedule$的属性声明 - 删除
ngOnInit里的this.getCalendar()调用,修正写法为只保留this.getEvents(); - 删除组件内的
getCalendar()方法 - 删除模板中第二个展示schedule数据的
<div class="boxed">整块代码 - 如果
ApiService里的getCalendar()没有其他组件调用,一并删除即可
- 删除组件类中
- 修复变更检测触发问题,二选一即可:
- 方案一(推荐,符合Angular最佳实践):把
events改为Observable形态,交给async管道管理,避免手动订阅的内存泄漏和变更检测问题,修改示例:
组件类代码:
模板代码(同时修正*ngFor位置错误):import { map, catchError, of, tap } from 'rxjs'; events$!: Observable<CalendarEvent[]>; ngOnInit(): void { this.getEvents(); } public getEvents(): void { this.events$ = this.apiService.getEvent().pipe( map((data: any) => { return data.map((event: any) => ({ ...event, start: new Date(event.start), end: new Date(event.end) })) }), catchError(err => { console.error(err); return of([]); }), tap(() => console.log('done loading events')) ); }<div class="boxed"> CalendarEvent Div from getEvents() <ul> <li *ngFor="let evnt of events$ | async"> Title: {{evnt.title}}<br> Start: {{evnt.start}}<br> End: {{evnt.end}}<br> Creator: {{evnt.created_by}} </li> </ul> </div> - 方案二(改动最小):保留现有手动订阅的写法,注入
ChangeDetectorRef主动触发变更检测,修改示例:
组件类构造函数注入:
修改constructor( private cdr: ChangeDetectorRef, private apiService: ApiService, private auth: AuthService ) {}getEvents()的subscribe回调,赋值完成后触发检测:
同时修正模板里的ngFor位置错误,把ngFor从public getEvents(): void { this.apiService.getEvent() .subscribe({ next: (data: any) => { this.events = data; for (let event of this.events) { event.start = new Date(event.start); event.end = new Date(event.end); } this.cdr.markForCheck(); // 手动标记视图需要更新 }, error: (err:any) => console.error(err), complete: () => console.log('done loading events') }); }<ul>移到内部的<li>标签上即可。
- 方案一(推荐,符合Angular最佳实践):把
内容的提问来源于stack exchange,提问作者Eric Kumar
相关产品推荐
相关产品推荐

