#NG0953: Unexpected emit for destroyed `OutputRef`. The owning directive/component is destroyed

25 messages · Page 1 of 1 (latest)

rare flame
#

I am getting this error

Parent HTML pseudo code:

<ul>
@for (option of options, track option.title) {
  <li>
    <row>
      <column>
        <button (clicked)="onButtonClicked(option)"
      <column>
    </row>
  </li>
}
</ul

<ng-container #dynamicComponentContainer></ng-container>

Parent TS pseudo code:

@ViewChild('dynamicComponentContainer', {read: ViewContainerRef})
protected dynamicComponentContainer!: ViewContainerRef;

protected child1Ref?: ComponentRef<Child1Component>;

protected onButtonClicked(option) {
  if (option.title === 'Component1') {
    this.dynamicComponentContainer.clear();
    this.child1Ref.setInput('isOpen', true);
    this.child1Ref.instance.closed.subscribe(() => {
      if (!this.compnent1Ref) {
        return;
      } 
      
      this.component1Ref.destroy();
    });
 }
}

Child1 HTML:

<custom-dialog
  [openValue]="isOpen()"
   (closed)="closeDialog()">
</custom-dialog>

Child1 TS:

  isOpen = input.required<boolean>();
   closed = output<void>();

protected closeDialog() {
  this.closed.emit();
}

But whenever I closed the dialog, I get NG0953: Unexpected emit for destroyed OutputRef. The owning directive/component is destroyed
How can I fix this? I see there's an OutputRef that automatically cleans up the subscription if the component of the output is destroyed. Should I unsubscribe from the this.child1Ref.instance.closed (after saving to a variable) before destroying?

wise palm
#

When does (closed) emit on the custom-dialog component?
The error means that you tried to do this.closed.emit() after "Child1" is already destroyed.

rare flame
#

Hmm looks like there's an
The custom-dialog sets [noCloseButton]="false"

constructor() {
effect(() => {  
  this.open.set(this.openValue());
});
effect(() => {  
  this.open() ? this.handleOpenDialog() : this.handleCloseDialog()
});
effect(() => {  
  this.noCloseButton.set(this.noCloseButtonValue());
});
}

async handleCloseDialog() {
  await this.stopAnimations();
  await this.runClosedAnimations();
  this.closed.emit();
}

The animation methods dont look anything interesting

wise palm
#

That's your issue though, I think. handleCloseDialog is waiting for things and then emitting, but by the time the awaiting has completed, the component is already destroyed. At least that is my guess from just looking at the code snippets.

rare flame
wise palm
#

You have to fix custom-dialog

rare flame
#

That's another team but I can ask them

wise palm
#

You should first verify with the stacktrace that it is actually that closed.emit

rare flame
#

Should I unsubscribe from this.component1Ref.instance.closed.subcribe()?
Saving that into a variable then unsubscribe before destroying

For general knowledge

wise palm
#

The problem is not that you're subscribed after it is destroyed, the problem is that someone is trying to call emit on an Output after the component of that output has been destroyed.

rare flame
#

Oh sorry, that was for my own knowledge as it's good to unsubscribe if you subscribe in general
Inside the subscribe, I used setTimeout that has the destroy and a timeout of 0 and not getting an error anymore

wise palm
#

Sorry, can you show what you did exactly? It probably only works "by luck" and is not the correct solution

#

As for unsubscribing, yes you should always unsubscribe, preferably don't even subscribe manually, use something like toSignal.
If you are subscribing to an Observable that represents events not data (i.e. "this emits when the button is clicked") then subscribing manually is better though and in that case you should usually use takeUntilDestroyed to automatically unsubscribe when your component is destroyed.

#

But again, this is unrelated to this error.

rare flame
#
this.component1Ref.instance.closed.subscribe(() => {
setTimeout(() => {
If (!this component1Ref) return

This.compojent1Ref.destroy()
This.component1Ref = null;
}, 0);
});
wise palm
#

The horror

rare flame
#

Dumb luck?

wise palm
#

That is a workaround at best.

#

The error message means a broken emit call. Hence, the fix should involve that emit call.

#

Since your change does not touch an emit call, it is not a proper fix 😄

rare flame
#

Oh definitely, yeah I'll put in a request for a fix

#

Thank you!

rare flame
# wise palm That is a workaround at best.

Question, do I even need to output back to the parent? When the dialog gets all the data and closes, that data should be passed directly to a 3rd component, so I shouldnt need to go back to the parent?
Unless it should be destroyed for resource purposes

wise palm
#

That's really hard to answer without seeing the actual code structure

rare flame
# wise palm That's really hard to answer without seeing the actual code structure

Hmm actually, the parent is a landing page,
It's like

@if (data exists) {
  <app-sub1></app-sub1>
} @ else {
  <app-sub2></app-sub2>
}

if data doesnt "exist" (either being passed in from previous step in the flow or from the buttons), there a few buttons in app-sub2 to get some. One of those creates that modal Child1, gets response from a 3rd party, massages that response then should send it back to the landing page parent. Since data now exists, the parents shows a different view on the same component