Attempt to use a destroyed view: detectChanges
Asked Answered
F

15

89

I am creating a simple UI using Angular Meteor 2.

1) I have a top navbar component which has a 'logout' button.
2) On clicking 'logout' button it redirects to 'login'.
3) Then I see this error in console : EXCEPTION: Attempt to use a destroyed view: detectChanges

Exception:

EXCEPTION: Attempt to use a destroyed view: detectChanges
browser_adapter.js:77 EXCEPTION: Attempt to use a destroyed view: detectChangesBrowserDomAdapter.logError @ browser_adapter.js:77BrowserDomAdapter.logGroup @ browser_adapter.js:87ExceptionHandler.call @ exception_handler.js:57(anonymous function) @ application_ref.js:265schedulerFn @ async.js:123SafeSubscriber.__tryOrUnsub @ Subscriber.js:225SafeSubscriber.next @ Subscriber.js:174Subscriber._next @ Subscriber.js:124Subscriber.next @ Subscriber.js:88Subject._finalNext @ Subject.js:128Subject._next @ Subject.js:120Subject.next @ Subject.js:77EventEmitter.emit @ async.js:112onError @ ng_zone.js:120onHandleError @ ng_zone_impl.js:66ZoneDelegate.handleError @ angular2-polyfills.js:394Zone.runTask @ angular2-polyfills.js:323ZoneTask.invoke @ angular2-polyfills.js:490
browser_adapter.js:77 STACKTRACE:BrowserDomAdapter.logError @ browser_adapter.js:77ExceptionHandler.call @ exception_handler.js:59(anonymous function) @ application_ref.js:265schedulerFn @ async.js:123SafeSubscriber.__tryOrUnsub @ Subscriber.js:225SafeSubscriber.next @ Subscriber.js:174Subscriber._next @ Subscriber.js:124Subscriber.next @ Subscriber.js:88Subject._finalNext @ Subject.js:128Subject._next @ Subject.js:120Subject.next @ Subject.js:77EventEmitter.emit @ async.js:112onError @ ng_zone.js:120onHandleError @ ng_zone_impl.js:66ZoneDelegate.handleError @ angular2-polyfills.js:394Zone.runTask @ angular2-polyfills.js:323ZoneTask.invoke @ angular2-polyfills.js:490
browser_adapter.js:77 Error: Attempt to use a destroyed view: detectChanges
    at ViewDestroyedException.BaseException [as constructor] (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:3349:23)
    at new ViewDestroyedException (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:10626:16)
    at DebugAppView.AppView.throwDestroyedError (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:11277:72)
    at DebugAppView.AppView.detectChanges (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:11230:18)
    at DebugAppView.detectChanges (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:11321:44)
    at ViewRef_.detectChanges (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:11011:65)
    at http://localhost:3000/app/app.js?hash=323b1216814e80ed467d95bcda255eb217d7c468:2224:23
    at ZoneDelegate.invokeTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4721:174)
    at Object.onInvokeTask (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:9393:41)
    at ZoneDelegate.invokeTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4721:72)
  -------------   Elapsed: 80 ms; At: Wed Jun 15 2016 20:22:09 GMT-0700 (PDT)   -------------  
    at Object.onScheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5734:30)
    at ZoneDelegate.scheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4704:57)
    at Zone.scheduleMacroTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4652:47)
    at http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4467:37
    at setTimeout (eval at createNamedFn (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5346:24), <anonymous>:3:37)
    at new TopNavbarComponent (http://localhost:3000/app/app.js?hash=323b1216814e80ed467d95bcda255eb217d7c468:2221:9)
    at DebugAppView._View_HomeComponent0.createInternal (HomeComponent.template.js:48:34)
    at DebugAppView.AppView.create (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:11098:21)
  -------------   Elapsed: 2 ms; At: Wed Jun 15 2016 20:22:09 GMT-0700 (PDT)   -------------  
    at Object.onScheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5734:30)
    at ZoneDelegate.scheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4704:57)
    at Zone.scheduleMicroTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4649:47)
    at scheduleResolveOrReject (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4928:22)
    at resolvePromise (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4893:29)
    at http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4930:25
    at ZoneDelegate.invokeTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4721:174)
    at Object.onInvokeTask (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:9393:41)
  -------------   Elapsed: 0 ms; At: Wed Jun 15 2016 20:22:09 GMT-0700 (PDT)   -------------  
    at Object.onScheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5734:30)
    at ZoneDelegate.scheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4704:57)
    at Zone.scheduleMicroTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4649:47)
    at scheduleResolveOrReject (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4928:22)
    at ZoneAwarePromise.then (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5011:25)
    at RuntimeCompiler.resolveComponent (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:40230:14)
    at DynamicComponentLoader_.loadNextToLocation (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:10788:31)
    at RouterOutlet.activate (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:26844:26)
  -------------   Elapsed: 0 ms; At: Wed Jun 15 2016 20:22:09 GMT-0700 (PDT)   -------------  
    at Object.onScheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5734:30)
    at ZoneDelegate.scheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4704:57)
    at Zone.scheduleMicroTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4649:47)
    at scheduleResolveOrReject (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4928:22)
    at resolvePromise (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4893:29)
    at http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4877:21
    at ZoneDelegate.invoke (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4696:161)
    at Object.onInvoke (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:9402:41)
  -------------   Elapsed: 0 ms; At: Wed Jun 15 2016 20:22:09 GMT-0700 (PDT)   -------------  
    at Object.onScheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5734:30)
    at ZoneDelegate.scheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4704:57)
    at Zone.scheduleMicroTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4649:47)
    at scheduleResolveOrReject (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4928:22)
    at resolvePromise (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4893:29)
    at http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4877:21
    at ZoneDelegate.invoke (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4696:161)
    at Object.onInvoke (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:9402:41)
  -------------   Elapsed: 0 ms; At: Wed Jun 15 2016 20:22:09 GMT-0700 (PDT)   -------------  
    at Object.onScheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5734:30)
    at ZoneDelegate.scheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4704:57)
    at Zone.scheduleMicroTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4649:47)
    at scheduleResolveOrReject (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4928:22)
    at resolvePromise (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4893:29)
    at http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4930:25
    at ZoneDelegate.invokeTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4721:174)
    at Object.onInvokeTask (http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:9393:41)
  -------------   Elapsed: 1 ms; At: Wed Jun 15 2016 20:22:09 GMT-0700 (PDT)   -------------  
    at Object.onScheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5734:30)
    at ZoneDelegate.scheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4704:57)
    at Zone.scheduleMicroTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4649:47)
    at scheduleResolveOrReject (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4928:22)
    at ZoneAwarePromise.then (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5011:25)
    at http://localhost:3000/packages/modules.js?hash=560db94ec01c0b3e8f499491ffcce7a2ec6c3c5e:26895:53
    at http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:1105:22
    at ZoneDelegate.invoke (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4696:161)
  -------------   Elapsed: 0 ms; At: Wed Jun 15 2016 20:22:09 GMT-0700 (PDT)   -------------  
    at Object.onScheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:5734:30)
    at ZoneDelegate.scheduleTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4704:57)
    at Zone.scheduleMicroTask (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4649:47)
    at scheduleResolveOrReject (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4928:22)
    at resolvePromise (http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4893:29)
    at http://localhost:3000/packages/barbatus_angular2-runtime.js?hash=fda9b73362c52e988ad030102a9f58e4d584cda3:4877:21
    at http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:1105:22BrowserDomAdapter.logError @ browser_adapter.js:77ExceptionHandler.call @ exception_handler.js:60(anonymous function) @ application_ref.js:265schedulerFn @ async.js:123SafeSubscriber.__tryOrUnsub @ Subscriber.js:225SafeSubscriber.next @ Subscriber.js:174Subscriber._next @ Subscriber.js:124Subscriber.next @ Subscriber.js:88Subject._finalNext @ Subject.js:128Subject._next @ Subject.js:120Subject.next @ Subject.js:77EventEmitter.emit @ async.js:112onError @ ng_zone.js:120onHandleError @ ng_zone_impl.js:66ZoneDelegate.handleError @ angular2-polyfills.js:394Zone.runTask @ angular2-polyfills.js:323ZoneTask.invoke @ angular2-polyfills.js:490
Subscriber.js:229 Uncaught Attempt to use a destroyed view: detectChanges

top-navbar.component.ts

"use strict";
import {Logger} from "../services/logger.service";
import {Component, ChangeDetectionStrategy, ChangeDetectorRef} from '@angular/core';
import {User} from "../models/user";
import {Router} from '@angular/router-deprecated';
import {UserService} from "../services/user.service";
import {CORE_DIRECTIVES} from '@angular/common';
import {DROPDOWN_DIRECTIVES} from '../../node_modules/ng2-bootstrap';

@Component({
    selector: 'top-navbar',
    templateUrl: 'client/top-navbar/top-navbar.html',
    bindings: [UserService, Logger],
    directives: [CORE_DIRECTIVES, DROPDOWN_DIRECTIVES]
})

export class TopNavbarComponent {

    public user:User;

    public statusDropdown = {
        isOpen: false
    };

    constructor(private userService:UserService, private router:Router, private logger:Logger, private ref:ChangeDetectorRef) {
        setTimeout(() => {
            this.ref.markForCheck();
            this.user = this.userService.getLoggedInUser();
            this.ref.detectChanges();
        }, 0)
    }

    logout() {
        this.logger.warn('[Top Navbar] Logging out the user.');
        localStorage.clear();
        this.router.navigateByUrl('/login');
    }
}

and this is my login.component.ts

"use strict";
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { FormBuilder, ControlGroup, Validators } from '@angular/common';
import { MeteorComponent } from 'angular2-meteor';
import { Router } from '@angular/router-deprecated';
import { Logger } from "../services/logger.service";

@Component({
    selector: 'login',
    templateUrl: 'client/login/login.html',
    changeDetection: ChangeDetectionStrategy.OnPush,
    bindings: [Logger]
})

export class LoginComponent extends MeteorComponent {

    loginForm:ControlGroup;
    loginFailed = false;

    constructor(private _logger:Logger, private _router:Router, private ref:ChangeDetectorRef) {
        super();
        let fb = new FormBuilder();
        this.loginForm = fb.group({
            username: ["", Validators.required],
            password: ["", Validators.required]
        });
    }

    login() {


        this.call('authenticateUser', this.loginForm.value.username, this.loginForm.value.password, (err, data) => {

            if (err) {
                this._logger.error(err);

            } else {
                this._logger.info('[Authentication API] ', data);

                if (data.status != 'LOGIN_SUCCESS') {
                    this.loginFailed = true;

                } else {
                    this.loginFailed = false    ;
                    var user = {
                        id: data.id,
                        name: data.name,
                        role: data.role
                    }
                    localStorage.setItem('user', JSON.stringify(user));
                    this._router.navigate(['Home'])
                }
                //This is required for letting Angular know that something has changed.
                //Because this part of code runs out of Angular zone.
                this.ref.markForCheck();  // Mark this component and its children for change detection in next detecting cycle.
                this.ref.detectChanges(); // Trigger change detection.

            }

        });
    }
}
Froward answered 16/6, 2016 at 3:33 Comment(2)
comment the call for detectChanges(); function and check where its generate some other errorHistrionics
I have the same problem with the same scenario.Neomaneomah
T
108

Only solution that worked for me was:

if (!this.changeDetectionRef['destroyed']) {
    this.changeDetectionRef.detectChanges();
}
Theodore answered 6/10, 2017 at 12:38 Comment(6)
Same here, thank you! Although I opted to add a private field isDestroyed in each component where it was needed (i.e. where unsubscribing things was not enough). I set the field to true in ngOnDestroy, and used it instead of trying to access the internal field of the change detector ref.Yestreen
While this answer prevents the error from blowing up your app, it's covering the root cause. Al-Mothafar has the right solution here which is to detach the cdr from the component that is no longer rendered.Korean
This actually works! On the other hands this seems VERY hacky because destroyed is not part of the public API of the changeDetectionRef object. Is there a way to apply this without acceding it through ['destroyed']?Appellant
Agreed with @lealceldeiro. With the newer versions of TypeScript, we aren't able to do this anymore.Serrato
You can use !(this.changeDetectionRef as ViewRef).destroyed to make it a little less hacky but it's still pretty hacky these daysNudity
Although this answer technically solves the problem, it is, however, a bad experience as it does not fix the real bugs. Al-Mothafar's answer is the correct approach.Hylan
N
91

I solved the same issue as yours, but with a 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 detecting changes using `detectChanges()` inside a subscription with `subscribe()` to observable without limitation, so was enough for me to just `unsubscribe()` like the following line;
    // 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, checking if the view destroyed or not is a good solution IMHO as the original cause could be a problem behind memory leak, who knows! so the root of the issue is that running service needs to be killed NOT just try to kill the error itself without knowing the root of the issue, for instance, running subscriptions (especially 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 even if it indeed looks nice :)

Neomaneomah answered 15/2, 2017 at 12:12 Comment(10)
When I did that, I got this error at ngOnDestory function: console.js:26 ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'detach' of undefined TypeError: Cannot read property 'detach' of undefinedQuincey
this means your ChangeDetectorRef service name is incorrect maybe? if you have private cdRef: ChangeDetectorRef in the constructor it should be working.Neomaneomah
yea, I have that in the constructor of my component.Quincey
Why would the method(detectChanges) be called during the destroy phase?Notwithstanding
@DongBinKim If you turned On detectChanges and changes happened during destroying phase, this function will start to apply changes on the dead component before the services die as well, so the issue will happen, it is something related to View lifecycle for angular, anyway you can see this link angular.io/api/core/ChangeDetectorRef#detectChanges and angular.io/guide/lifecycle-hooks as you can see DOM stays and service in memory will try to apply changes while the component itself got killed.Neomaneomah
@Neomaneomah It sounds like, "You tell me(changedetector) to detect changes so I'm going to do what I'm told to do even if the component I belong is in destroy phase".Notwithstanding
@DongBinKim something like that, or maybe like detectChanges was going to apply changes as service lives in memory and it got surprised with the component that he was trying to use is expired "destroyed", detectChanges in this case it didn't know that component got destroyed already it is tried that.Neomaneomah
@Neomaneomah Then, I guess we should always write some sort of protection code such as this.cdRef.detach() inside of ngOnDestroy lifecycle when we use detectChanges method because we wouldn't know how/when the above error occurs.Notwithstanding
@DongBinKim exactlyNeomaneomah
This solution didn't work for me. In fact the code comment for the detectChanges method seems to suggest that detectChanges will reattach the change detector. For me the only working solution was tomaszbak's method, or to always unsubscribe/clearTimeout/etc.Yestreen
B
17

You can use

this.cdref.markForCheck();

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

Brookner answered 9/10, 2017 at 20:34 Comment(0)
T
9

I have it solved with:

if (!(<ViewRef>this.cd).destroyed) {
   this.cd.detectChanges();
}
Twilley answered 17/5, 2018 at 14:8 Comment(1)
Nearly the same as the accepted answer but provides the now necessary type conversionNudity
N
7

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();
    }
Niedersachsen answered 28/9, 2018 at 7:58 Comment(3)
is this.cd.detach() on destroy necessary?Landgrave
When the component is no longer rendered ChangeDetectorRef can be detached. It is not necessary, but doing this we make sure there is no any change detection happening in the destroyed view. On the other hand, if you are really confident that there is no change detection needed or need to disable change detection in component, you can detach change detector at any point.Niedersachsen
Thanks Thilina. I think this.cd will be automatically detached on destroy. And if not so, since !this.cd['destroyed'] is tested before this.cd.detectChanges() in someFunction, you'll be confident to call someFunction on any circumstance.Landgrave
C
5

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().

Coulson answered 4/10, 2017 at 11:57 Comment(0)
S
4

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.

Sensor answered 4/3, 2018 at 16:45 Comment(0)
C
4

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());

Coxswain answered 17/5, 2018 at 22:5 Comment(0)
F
3

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.
Fid answered 18/9, 2018 at 12:35 Comment(1)
Same here; changeDetector.detach() and changeDetector.markForCheck() weren't working in my code so I ended up setting a flag in ngOnDestroy().Chilon
L
2

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();
});
Lindo answered 12/1, 2018 at 0:4 Comment(0)
R
2

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();
  });
}));
Retral answered 29/1, 2018 at 15:34 Comment(0)
J
2

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.

Jeffery answered 19/2, 2018 at 7:58 Comment(0)
M
2

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

this.ngZone.run(() => {
      ...
});
Mendenhall answered 29/6, 2019 at 11:57 Comment(0)
S
1

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();
}
Simply answered 5/4, 2018 at 21:26 Comment(0)
H
1

Try using setTimeout without delay parameter. For me it worked well, because setTimout without delay is a tricky way to detect changes after model updat is over

setTimeout(() => {
   if (!this.ref['destroyed']) {
      this.ref.detectChanges();
   }   
});
Halothane answered 18/8, 2022 at 14:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.