Angular2 ExpressionChangedAfterItHasBeenCheckedErr

2019-06-01 06:10发布

I've read the documentation on this error, and I think I understand why the issue arises. I'm having a problem trying to figure out an alternative architecture that would suit me.

The root component of my site (app.component) has a "loading" component on it. It's a third party component (ngx-loading) that basically throws up a loading indicator to show the user that something is happening in the app that will take a little time. The loading component takes a parameter that tells it whether to show up or not:

<ngx-loading [show]="loading" [config]="{ backdropBorderRadius: '14px' }"></ngx-loading>

I have a site service that holds the data for this, and my application gets the service data OnInit:

this.siteService.getLoading().takeUntil(this.ngUnsubscribe).subscribe(res => {this.loading = res})

The great thing about all this is that I can change the site service value from just about anywhere in my app and control whether this loading indicator pops up. The bad thing is that now I'm seeing this ExpressionChangedAfterItHasBeenCheckedError error.

Again, I think I understand why the error happens, but the solutions I've seen so far won't allow me to keep using this one simple component to handle all my "loading" calls. Or am I missing something?

SiteService:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject }    from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class SiteService {

  private currentRoute = new Subject<string>();
  private currentAction = new Subject<string>();

  private loading = new BehaviorSubject<boolean>(false);

  constructor() {
  }

  public menuState:string = 'in';
  toggleMenuPicker() {
    this.menuState = this.menuState === 'out' ? 'in' : 'out';
  }

  getCurrentRoute(): Observable<string> {
    return this.currentRoute.asObservable();
  }
  setCurrentRoute(route: string) {
    this.currentRoute.next(route);    
  }

  getCurrentAction(): Observable<string> {
    return this.currentAction.asObservable();
  }
  setCurrentAction(action: string) {
    this.currentAction.next(action);
  }

  getLoading(): Observable<boolean> {
    return this.loading.asObservable();
  }

  setLoading(show: boolean) {
    this.loading.next(show);
  }



}

app.component

import { Component, OnDestroy, AfterContentInit } from '@angular/core';
import { trigger, state, style, transition, animate} from '@angular/animations';
import { SiteService } from './site/site.service';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  animations: [
    trigger('menuState', [
      state('in', style({
        transform: 'translate3d(-100%, 0, 0)'
      })),
      state('out', style({
        transform: 'translate3d(0, 0, 0)'
      })),
      transition('in => out', animate('200ms ease-in-out')),
      transition('out => in', animate('200ms ease-in-out'))
    ])
  ]
})
export class AppComponent implements OnDestroy, AfterContentInit {
  private ngUnsubscribe: Subject<void> = new Subject<void>();

 public loading;

  constructor(public siteService: SiteService) {
  }

  ngAfterContentInit() {
    this.siteService.getLoading().takeUntil(this.ngUnsubscribe).subscribe(res => {this.loading = res})
  }

  ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }



}

2条回答
放我归山
2楼-- · 2019-06-01 06:23

You can use ChangeDetectionStrategy.OnPush or ChangeDetectorRef.detach() to keep your single component and at the same time avoid the error.

Here is one possible solution:

@Component({
  selector: 'loader',
  template: `<ngx-loading [show]="loading"></ngx-loading>`
})
export class LoaderComponent {
 public loading;

 constructor(public siteService: SiteService, private changeDetectorRef: ChangeDetectorRef) {
   this.changeDetectorRef.detach();
   this.siteService.getLoading()
                   .takeUntil(this.ngUnsubscribe)
                   .subscribe(res => {
                                this.loading = res;
                                this.changeDetectorRef.detectChanges();
                    });       
  }
}

You can create this component an use it in the AppComponent template.

查看更多
Animai°情兽
3楼-- · 2019-06-01 06:32

Try to force change detection.

this.siteService.getLoading()
                .takeUntil(this.ngUnsubscribe)
                .subscribe(res => {
                  this.loading = res;
                  this.ref.detectChanges(); // <-- force change detection
                });

see my similar answer for more details

查看更多
登录 后发表回答