Covalent: Bug: change detection error in td-layout components

Created on 10 Jun 2017  路  13Comments  路  Source: Teradata/covalent

Bug Report

Currently there is a bug related to the way that properties in td-layout components are set using the media service. Take as example:

<td-layout-nav-list #manageList [opened]="media.registerQuery('gt-sm') | async" [mode]="(media.registerQuery('gt-sm') | async) ? 'side' : 'over'"
  [sidenavWidth]="(media.registerQuery('gt-xs') | async) ? '250px' : '65%'" logo="assets:warehouse" navigationRoute="/">

It will throw the following error:

LayoutPageComponent.html:1 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '[object Object]'. Current value: 'side'.
    at viewDebugError (core.es5.js:8507)
    at expressionChangedAfterItHasBeenCheckedError (core.es5.js:8485)
    at checkBindingNoChanges (core.es5.js:8649)
    at checkNoChangesNodeInline (core.es5.js:12504)
    at checkNoChangesNode (core.es5.js:12476)
    at debugCheckNoChangesNode (core.es5.js:13251)
    at debugCheckDirectivesFn (core.es5.js:13153)
    at Object.eval [as updateDirectives] (LayoutPageComponent.html:1)
    at Object.debugUpdateDirectives [as updateDirectives] (core.es5.js:13135)
    at checkNoChangesView (core.es5.js:12296)
    at callViewAction (core.es5.js:12660)

Another example:

<td-layout-manage-list #manageList [opened]="false" [mode]="'over'" [sidenavWidth]="(media.registerQuery('gt-xs') | async) ? '280px' : '70%'">

WIll cause the following:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '[object Object]'. Current value: '280px'.
    at viewDebugError (core.es5.js:8507)
    at expressionChangedAfterItHasBeenCheckedError (core.es5.js:8485)
    at checkBindingNoChanges (core.es5.js:8649)
    at checkNoChangesNodeInline (core.es5.js:12506)
    at checkNoChangesNode (core.es5.js:12476)
    at debugCheckNoChangesNode (core.es5.js:13251)
    at debugCheckDirectivesFn (core.es5.js:13153)
    at Object.View_LayoutPageComponent_0._co [as updateDirectives] (LayoutPageComponent.html:1)
    at Object.debugUpdateDirectives [as updateDirectives] (core.es5.js:13135)
    at checkNoChangesView (core.es5.js:12296)
    at callViewAction (core.es5.js:12660)

What is the expected behavior?

No error should be triggered.

Which version of Angular and Material, and which browser and OS does this issue affect?

material: beta.6
covalent/core: beta.5
angular: 4.2.0 (all)
angular-cli: 1.1.1

Other information

A possible fix was pointed already by @emoralesb05

Promise.resolve(undefined).then(() => {
      this.media.broadcast();
      this._changeDetectorRef.markForCheck();
    });

The way this is implemented in the current version of the covalent documentation page is:

//constructor of a component
constructor(private _changeDetectorRef: ChangeDetectorRef,
              public media: TdMediaService) {}

  ngAfterViewInit(): void {
    // broadcast to all listener observables when loading the page
    this.media.broadcast();
    this._changeDetectorRef.detectChanges();
  }
faqs

All 13 comments

Yeah, this is a limitation in 4.2.0.. where you need to explicitly call a change detection cycle if you change any inputs in ngAfterViewInit.. since by then the change detections have finished.

There is nothing we can do internally in the TdLayout since those inputs never get in, they are checked before that and throw the error.

The correct fix is doing:

  //constructor of a component
  constructor(private _changeDetectorRef: ChangeDetectorRef,
              public media: TdMediaService) {}

  ngAfterViewInit(): void {
    // broadcast to all listener observables when loading the page
    this.media.broadcast();
    this._changeDetectorRef.detectChanges();
  }

The new angular version became more strict with change detection in component life hook cycles

Gonna add the faq label for this.

Created a PR to update docs with this

https://github.com/Teradata/covalent/pull/685

I would suggest to leave the issue open for a while so that ppl can see it. Furthermore it might be a good idea to update the changelog with this info 馃槃

Yup, will keep it open for now and create a patch release monday-tuesday to reference this and another bug found in loading haha

Thanks as always man!

@emoralesb05 : Will you be fixing the bug in loading? Or would your suggestion of using a delay through Promise.resolve() be the way to go?

I am facing some race conditions between the register and resolve calls sometimes.

@asaph26 well, i already fixed a bug i found in loading and its merged. https://github.com/Teradata/covalent/pull/682

Or are you referring to a diff one?

Since if you are calling the register in ngAfterViewInit for a loading directive, you would have to do the same thing and call this._changeDetectorRef.detectChanges()

The bug i fixed was for the full screen register in ngOnInit

@emoralesb05 : It was the full screen loading. Thanks for the quick fix. Will try out with the latest nightly.

have u solved with TdLoadingService?

I insert this.loading.register(); on "onRequest" in request.interceptor.ts and cause:

TdLoadingComponent.html:1
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'true'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ?

Yup, fixed in #682

Already published the documentation updates and changelog here to view more info https://github.com/Teradata/covalent/blob/develop/docs/CHANGELOG.md#100-beta5-1-2017-06-12

So closing this now

I just got this again with 2.0.0-beta.2.

constructor(private loading: TdLoadingService, private route: ActivatedRoute, private changes: ChangeDetectorRef) {
}
ngOnInit() {
  this.loading.register();
  this.route.queryParams.subscribe(qs => {
    if (qs['error']) {
      this.loading.resolve(); // causes ExpressionChangedAfterItHasBeenCheckedError
      // Expression has changed after it was checked. Previous value: 'mode: indeterminate'. Current value: 'mode: determinate'.
      this.changes.detectChanges(); // attempts to fix, but doesn't
    }
  });
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

fxstein picture fxstein  路  5Comments

emoralesb05 picture emoralesb05  路  4Comments

ineselmufti picture ineselmufti  路  4Comments

akrepon picture akrepon  路  3Comments

d3viant0ne picture d3viant0ne  路  3Comments