`ɵzoneWrap` ignores a handler passed as an observer object, and holds a wrapped promise forever once its injector is destroyed

Đang mở
#3,828 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 3 ngày

@armando-navarro đang làm issue này rồi.

Từ ngày 30/10/2026.

  • #3829 của @armando-navarro — đang mở

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
25/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Đình trệ
Công nghệ
angular, firebase, typescript
Lĩnh vực
frontend

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

comp: app-check comp: core comp: firestore comp: remote-config comp: zones type: bug version: current (v17+)
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 runOutsideAngular and 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 .then callback runs as a later microtask after the injection context is torn down, and zone.js runs it in the zone where .then was attached, not where resolve was called.
  • The one thing the layers do is throw: runInInjectionContext checks assertNotDestroyed, so when the Firebase promise settles after ApplicationRef.destroy() or a TestBed reset, the callback throws NG0205 before resolve runs 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 }) and onSnapshotsInSync(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 onSnapshot with 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.

Ngôn ngữ chính
TypeScript
Star
7.8k
Fork
2.2k
Merge trung bình
2 ngày 12 giờ
Pull request đã merge (30 ngày)
24

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của angular/angularfire

Tất cả issue của angular/angularfire

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.