Attempt to use a destroyed view: detectChanges

馋奶兔 提交于 2019-11-27 18:16:57

Only solution that worked for me was:

if (!this.changeDetectionRef['destroyed']) {
    this.changeDetectionRef.detectChanges();
}

I solved the same issue of yours, but with much smaller code, I'll tell you the point that could help you to solve the issue.

The issue clearly comes from detectChanges() because the changes were done and the method called during the destroy phase of the component.

So you need to make your component to implements OnDestroy, then you need to cancel the changes that make this.ref.detectChanges() to be called. so your TopNavbarComponent must be similar to:

export class TopNavbarComponent implements OnDestroy {
  // ... your code

  ngOnDestroy() {
    this.cdRef.detach(); // do this

    // for me I was detect changes inside "subscribe" so was enough for me to just unsubscribe;
    // this.authObserver.unsubscribe();
  }
}

P.S: Don't forget to unsubscribe() all observers that you have in your component! Anyways you have to do that, subscriptions without unsubscribing could be the main reason for hundreds of issues including this, refer to Angular/RxJs When should I unsubscribe from `Subscription`

Edit: I know other solution around in the web trying to just solve the issue by handling the error itself, while the best practice is to know the root of the issue, check if the view destroyed or not is a good solution but the original cause could be a problem behind memory leak, so the root of the issue is that running service need to be killed NOT just try to kill the error itself without the root, for instance, running subscriptions (specially your custom one) must be closed.

SO housekeeping stuff is necessary for better performance, it is not always an easier and faster solution is the better one, if you do hide the dirt under carpets does not mean that you cleaned your room :)

You can use

this.cdref.markForCheck();

instead of this.cdref.detectChanges(); in many cases. But the best approach is follow @Al-Mothafar tips

You have to get the value of subscriber in a variable and unsubscribe by the same var. Please refer following same code

import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { Cartservice } from './../cartservice.service';
import { ISubscription } from 'rxjs/Subscription';



export class CartComponent implements OnInit, OnDestroy {

private subscription: ISubscription;
ngOnInit() {
    this.subscription = this.cartservice.productsObservable.subscribe(cart => {
      this.cartProducts = cart.products;
      this.cartTotal = cart.cartTotal;
      this.changeDetectorRef.detectChanges();
    });
  }

 ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Notice that I have unsubscribe changes in method ngOnDestroy().

My solution was to unsubscribe all observers.

Subscription:

ngOnInit() {
     this._currentUserSubscription = this._auth.currentUser.subscribe(currentUser => {});
}

Unsubscription with changeDetector.detach():

ngOnDestroy() {
    this._currentUserSubscription.unsubscribe();
    this._cdRef.detach();
}

That was necesery on my code, i also have must use ChangeDetectorRef functionality, only those two things ishhued my code without errors.

I have it solved with:

if (!(<ViewRef>this.cd).destroyed) {
   this.cd.detectChanges();
}

Simply detach ChangeDetectorRef on OnDestroy lifecycle hook and check whether the ChangeDetectorRef destroyed before executing the detectChanges method

    constructor(private cd: ChangeDetectorRef){}

    someFunction(){
      if(!this.cd['destroyed']){
        this.cd.detectChanges();
      }
    }

    ngOnDestroy(){
      this.cd.detach();
    }

In my case, it was a matter mismanaging async test setup of component configuration and compilation.

  1. The failing code had a singular async beforeEach() using TypeScript Async/Await
  2. To get it working, I'm using two beforeEach()s, where the first handles Async using Angular's async(), and the second beforeEach() is sync.

Code causing errors..

beforeEach(async () => {
    await TestBed.configureTestingModule({
        imports: [
            BrowserAnimationsModule
        ],
        providers: [
            { provide: ComponentFixtureAutoDetect, useValue: true },
            { provide: OptionsService, useValue: optionServiceMock },
        ],
        declarations: [EventLogFilterComponent],
        schemas: [NO_ERRORS_SCHEMA]
    }).compileComponents();
    fixture = TestBed.createComponent(EventLogFilterComponent);
    component = fixture.componentInstance;
    optionsService = TestBed.get(OptionsService);
    component.filterElem = jasmine.createSpyObj('filterElem', ['close']);
    fixture.detectChanges();
});

Was fixed with...

beforeEach(async(() => {
    TestBed.configureTestingModule({
        imports: [
            BrowserAnimationsModule
        ],
        providers: [
            { provide: ComponentFixtureAutoDetect, useValue: true },
            { provide: OptionsService, useValue: optionServiceMock },
        ],
        declarations: [EventLogFilterComponent],
        schemas: [NO_ERRORS_SCHEMA]
    }).compileComponents();
}));

beforeEach(() => {
    fixture = TestBed.createComponent(EventLogFilterComponent);
    component = fixture.componentInstance;
    optionsService = TestBed.get(OptionsService);
    component.filterElem = jasmine.createSpyObj('filterElem', ['close']);
    fixture.detectChanges();
});

Not much related to specific question, but I landed here by googling the same error so I'll share my workaround. The problem was that I was calling fixture.detectChanges() inside fixture.whenStable().then(() => {}) without wrapping the test within an async function.

Before:

it('should...', () => {
  fixture.whenStable().then(() => {
    fixture.detectChanges();
  });
});

After:

it('should...', async(() => {
  fixture.whenStable().then(() => {
    fixture.detectChanges();
  });
}));

Simple:

import { OnDestroy } from '@angular/core';
import { Subject } from 'rxjs/Subject';

export class Component implements OnDestroy {
    componentDestroyed: Subject<boolean> = new Subject();

constructor() { }

function() {
   this.service.serviceFunction
     .takeUntil(this.componentDestroyed)
     .subscribe((incomingObservable: Type) => {
       this.variable = incomingObservable;
     });
  }

ngOnDestroy() {
   this._cdRef.detach(); //If you use change dectector
   this.componentDestroyed.next(true);
   this.componentDestroyed.complete();
}

My cause happened when I was trying to open a modal dialog with NgbActiveModal. Apparently calling .dismiss() from its own ngOnInit() causes this.

export class MyModal {
    constructor(private modalInstance: NgbActiveModal) {
    }

    ngOnInit() {
       if (foo) this.modalInstance.dismiss();
    }

Solution was to wait a tick before dismissal.

if (foo) setTimeout(() => this.modalInstance.dismiss());

Well this answers doesn't helped me. I found other solution.

Child component has output that fires when close button is clicked

<child-component
    *ngIf="childComponentIsShown"
    (formCloseEmitter)="hideChildComponent()"
></child-component>

And "hideChildComponent()" method in parent component detects changes.

hideChildComponent() {
  this.childComponentIsShown = false;
  this.cdr.detectChanges();
}

Hope this will help someone.

For me, the only solution that worked is the following:

ngOnInit() {
    if (this.destroyedComponent) this.changeDetector.reattach();

this.destroyedComponent = false;
this.subscription = this.reactive.channel$.subscribe(msg => {
  switch (msg) {
    case "config:new_data":
      if (!this.destroyedComponent) {
        this.table.initTable();
        this.changeDetector.detectChanges();
      }
  }
})
}

ngOnDestroy() {
   this.subscription = null;
   this.destroyedComponent = true;
   this.changeDetector.detach();
}

Explanation:

  1. If the component has been destroyed previosuly, reattach the detector.
  2. Set that previous flag to a falsy value.
  3. Save the RxJs subscription, and set your desired logic inside.
  4. Wrap that logic in a conditional that checks whether the component has been set as destroyed or not by the flag previously declared.
  5. Perform your desired detection of changes inside that block.
  6. Set ngOnDestroy() method and nullify the subscription, set the destroyedComponent flag to a truthy value, and detach the changeDetector.

To avoid this error, instead of calling detectChanges() try wrapping the model changing code by:

this.ngZone.run(() => {
      ...
});
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!