`ɵzoneWrap` ignores a handler passed as an observer object, and holds a wrapped promise forever once its injector is destroyed
维护者通常 3 天内回复
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 25/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 停滞
- 技术栈
- angular, firebase, typescript
- 领域
- frontend
调研方向
Start by reading src/zones.ts, where ɵzoneWrap handles callbacks and promises. Reproduce the observer-object and destroyed-injector cases described in the issue, then check that object handlers receive the same task, injection-context and zone behavior as function handlers, and that wrapped promises settle after injector destruction. A linked pull request is already open.
由索引模型根据 Issue 内容生成。
描述
Version info
Angular: 22.2
Firebase: 12.19.0
AngularFire: 22.0.0-rc.0, and main
Description
Two defects in ɵzoneWrap (src/zones.ts), the function the generated @angular/fire/* wrappers go through.
1. A handler passed as an object is not wrapped. Firebase accepts a subscription handler either as a function, onSnapshot(ref, snapshot => ...), or as an observer object, onSnapshot(ref, { next: snapshot => ... }). The wrapper rewrites an argument only when typeof is 'function', so the object form reaches the SDK untouched:
- No pending task is registered, so server rendering does not wait for the first value.
inject()throws inside the handler, because nothing re-enters the injection context.- In a zone.js app the handler runs outside the Angular zone, because the wrapper calls Firebase inside
runOutsideAngularand then never brings the object's members back in. The unwrapped Firebase call made from in-zone code keeps its handler in the zone, so the wrapper makes the object form worse than not wrapping at all.
Eleven wrapped functions accept the object form: onAuthStateChanged, onIdTokenChanged, onTokenChanged, onSnapshot, onSnapshotResume, onSnapshotsInSync, onMessage, onRegistered, onUnregistered, onConfigUpdate (which accepts only the object form) and data-connect's subscribe (whose object uses onNext, onErr and onComplete). The three messaging functions are generated with blockUntilFirst: false, so the pending task point does not apply to them.
2. A wrapped promise never settles after its injector is destroyed.
- When a wrapped call returns a Promise, the wrapper resolves its own promise through
runInInjectionContext(injector, () => run(() => resolve(value))). - Neither layer reaches the caller: a
.thencallback runs as a later microtask after the injection context is torn down, and zone.js runs it in the zone where.thenwas attached, not whereresolvewas called. - The one thing the layers do is throw:
runInInjectionContextchecksassertNotDestroyed, so when the Firebase promise settles afterApplicationRef.destroy()or aTestBedreset, the callback throwsNG0205beforeresolveruns and the caller's promise never settles.
How to reproduce these conditions
On a zone.js Angular 22.2 app and a zoneless one, against the Firestore emulator, call the real functions through ɵzoneWrap the way a constructor does, inside the Angular zone and an injection context, and record inside the handler whether a pending task was registered, whether inject() works and whether NgZone.isInAngularZone() is true:
onSnapshot(ref, { next, error })andonSnapshotsInSync(firestore, { next, error }): no task in either app,inject()throws in both, and in the zone.js app the handler runs outside the Angular zone. A plain property written from the handler does not render there, in a component with default change detection.onSnapshot(ref, next), the function form: one task, released when the handler ran,inject()works, and in the zone.js app the handler runs inside the zone.- The unwrapped
onSnapshotwith either form of handler, called from the same place:inject()throws, but in the zone.js app the handler stays inside the zone.
For the promise defect, a spec: wrap a function that resolves after 20 ms with ɵzoneWrap(fn, true), call it inside TestBed.runInInjectionContext, call TestBed.resetTestingModule(), then await the returned promise. It never settles and the spec times out after 5 seconds.
Expected behavior
A handler passed as an observer object gets what a handler passed as a function gets: the pending task, the Angular zone and a working inject(). A wrapped promise settles whenever the Firebase promise settles.
Actual behavior
The object form gets none of the three, and a wrapped promise that settles after its injector is destroyed never settles.
- 主要语言
- TypeScript
- 星标
- 7.8k
- 派生
- 2.2k
- 平均合并
- 2 天 12 小时
- 30 天内合并 PR
- 24
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
angular/angularfire 的其他 Issue
-
Document the 22.0.0-rc.1 changes可能已有人在做 @armando-navarro 于 2 天前认领。 未关闭comp: docs type: chore
难度 3/5 半天 新手友好度 18/100
angular/angularfire#3834 ·
维护者通常 3 天内回复
-
comp: compat comp: ssr comp: zones type: feature version: current (v17+)
难度 3/5 1-2 天 新手友好度 74/100
angular/angularfire#3832 ·
维护者通常 3 天内回复
-
Accept firebase 13 in @angular/fire 22可能已有人在做 @armando-navarro 于 3 天前认领。 未关闭comp: core comp: schematics type: feature
难度 4/5 3-5 天 新手友好度 25/100
angular/angularfire#3830 ·
维护者通常 3 天内回复
-
Eleven wrapped callbacks and remote-config `isSupported` hold the app unstable until something that may never happen可能已有人在做 @armando-navarro 于 4 天前认领。 未关闭comp: app-check comp: core comp: database comp: remote-config comp: ssr comp: zones type: bug version: current (v17+)
难度 4/5 3-5 天 新手友好度 48/100
angular/angularfire#3826 ·
维护者通常 3 天内回复
-
comp: compat comp: firestore comp: storage type: chore
难度 4/5 3-5 天 新手友好度 48/100
angular/angularfire#3822 ·
维护者通常 3 天内回复
查看 angular/angularfire 的全部 Issue
相似的 Issue
-
needs-area
难度 2/5 1-3 小时 新手友好度 68/100
Azure/typespec-azure#5936 ·
维护者通常 1 天内回复
-
难度 2/5 半天 新手友好度 72/100
vercel-labs/scriptc#2910 ·
维护者通常 1 天内回复
-
automation missing-model model-sync provider:fireworks-ai
难度 2/5 1-3 小时 新手友好度 72/100
anomalyco/models.dev#12458 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 62/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复