Skip to content

Erratic behavior with DestroyRef, not all callbacks are always being called upon component destruction. #50221

@wartab

Description

@wartab

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

Yes

Description

We have migrated our code base to use the new takeUntilDestroyed pipe along with DestroyRef.

Unfortunately, we have noticed very erratic behavior that with these, going so far as to not actually unsubscribe from some Observables at all after components are destroyed (I am still working on trying to provide a minimal reproduction for some cases).

I have been able to replicate some bugs consistently with the reproduction below.
https://stackblitz.com/edit/at-ng-14-router-aoffwi?file=src%2Fapp%2Fhello.component.ts

Reproduction

This small App contains
A Service with a BehaviorSubject (bhSubject) that increments an integer and a Subject (subject) that "increments" strings.
Those two actions are performed from AppComponent, which is always visible with the buttons.
A third button "Toggle Child" will show and hide the HelloComponent, which contains the actual bug.

HelloComponent registers a onDestroy callback in each of the following cases:

  1. In the callback given to subscribe() on bhSubject.
  2. In a method called from the same callback given to subscribe on bhSubject.
  3. In the callback given to subscribe() on subject.
  4. In a setTimeout of 0.
  5. In ngZone.runOutsideAngular()
  6. Directly in ngOnit()

Note that a lot of the stuff is going on in the console.

Case 1:

  • Toggle Child
  • Toggle Child again

Expected onDestroy callbacks to have been called:

  • 1, 2, 4, 5?, 6

Actual callbacks called

  • 2, 4, 6

Case 2:

  • Toggle Child
  • Int++
  • Toggle Child

Expected onDestroy callbacks to have been called:

  • 1, 1, 2, 2, 4, 5?, 6

Actual callbacks called

  • 1, 2, 2, 4, 6

Case 3:

  • Toggle Child
  • Letter++
  • Toggle Child

Expected onDestroy callbacks to have been called:

  • 1, 2, 3, 4, 5?, 6

Actual callbacks called

  • 2, 3, 4, 6

Following this, it seems like the first callback from a BehaviorSubject doesn't allow to use DestroyRef.

Now you might be wondering why I added runOutsideAngular() in this, it's normal it's not working there. Well, if we comment the subscription to the normal subject (line 40 to 50 in hello.component.ts) and do case 1 again, it somehow does work with runOutsideAngular(). https://stackblitz.com/edit/at-ng-14-router-d9iur8?file=src%2Fapp%2Fhello.component.ts

    /*this.someService.subject
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe((value) => {
        console.log(value);

        this.destroyRef.onDestroy(() => {
          console.log('Subject ' + value + ' destroyRef');
        });

        this.letterValue = value;
      });*/

Case 4 (Same as Case 1, but with a subscription that is commented out):

  • Toggle Child
  • Toggle Child again

Expected onDestroy callbacks to have been called:

  • 1, 2, 4, 5?, 6

Actual callbacks called

  • 2, 4, 5, 6

This all seems very inconsistent. As stated before, we ran into bugs where takeUntilDestroyed() didn't work at all when used on observables within ngOnit directly (with destroyRef supplied, of course) and it wouldn't unsubscribe from all observables. Sadly I've not been able to reproduce this in a simple example yet, but this makes this new feature quite unusable. I'll keep you updated if I manage to craft a minimal reproduction for that bug as well.

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/at-ng-14-router-aoffwi?file=src%2Fapp%2Fhello.component.ts

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 16.0.0
Node: 16.18.0
Package Manager: npm 9.6.4
OS: win32 x64

Angular: 16.0.0
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, localize, material, platform-browser
... platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1600.0
@angular-devkit/build-angular   16.0.0
@angular-devkit/core            16.0.0
@angular-devkit/schematics      16.0.0
@schematics/angular             16.0.0
rxjs                            7.8.1
typescript                      5.0.4

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions