Chrillewoodz
Chrillewoodz

Reputation: 28338

Emitting a value from inside a lifecycle function causes "Expression has changed after it was checked" error since angular 2.0.0-rc2

Since upgrading to angular 2.0.0-rc2 I noticed that calling this.onModelChange.emit(this.model) from inside the ngOnInit or ngDoCheck functions causes this error:

Expression has changed after it was checked

I've looked through the changelog of the new release but I can't find anything related to this.

This is a big issue if you want to set a default value when you first init the component. If I don't emit the value on init then the value isn't available in the outer scope which is no good.

How can I fix this problem?

import {Component, Input, Output, EventEmitter, ElementRef, OnInit, DoCheck, OnDestroy, Renderer} from '@angular/core';
import {GlobalVariablesService} from '../../core/global-variables.service';
import {IsTouchedDirective} from '../../core/directives/is-touched.directive';

@Component({
  selector: 'form-select',
  templateUrl: './form-select.component.html',
  styleUrls: ['./form-select.component.css'],
  inputs: [
    'options',
    'callback',
    'model',
    'modelProperty',
    'required',
    'optionsLabel',
    'disabled',
    'label',
    'placeholder'
  ],
  directives: [
    IsTouchedDirective
  ]
})

export class FormSelectComponent implements OnInit, DoCheck, OnDestroy {
  @Input() model: any;
  @Output('modelChange') onModelChange: EventEmitter<any> = new EventEmitter();
  @Output() callback: EventEmitter<any> = new EventEmitter();

  public isOpen: boolean = false;
  public previousModel: any = null;

  constructor(private _globals: GlobalVariablesService, private _elementRef: ElementRef, private _renderer: Renderer) {
    this.ns = _globals.ns;

    this.eventHandler = _renderer.listenGlobal('document', ('ontouchstart' in window ? 'touchend' : 'click'), (e) => {

      // If the clicked element is not the component element or any of its children, close the select
      if (this._elementRef.nativeElement !== e.target && !this._elementRef.nativeElement.contains(e.target)) {
        this.close();
      }
    });
  }

  ngOnInit() {

    // If no model is set and the select shouldn't be allowed empty, set the model to the first option
    if (!this.model && this.required) {
      this.model = this.options[0];
    }
    // If it should be allowed empty, set it to null which will add an empty class in the markup
    else if (!this.required) {
      this.model = null;
    }

    this.onModelChange.emit(this.model);
  }

  ngDoCheck() {

    // Check if the model changes and emit it if it does
    if (this.previousModel !== this.model) {
      this.onModelChange.emit(this.model);
    }

    this.previousModel = this.model;
  }

  ngOnDestroy() {
    this.eventHandler();
  }

  toggle() {

    if (this.disabled) {
      this.isOpen = false;

      return false;
    }

    this.isOpen = !this.isOpen;
  }

  close() {
    this.isOpen = false;
  }

  select(option) {
    this.model = option;
    this.isDirty = true;
    this.close();

    this.onModelChange.emit(this.model);
    this.callback ? this.callback.emit() : false;
  }

  isSelected(option) {

    if (this.model) {
      return option[this.modelProperty] === this.model[this.modelProperty];
    }
    else {
      return false;
    }
  }
}

Upvotes: 1

Views: 786

Answers (1)

Arpit Agarwal
Arpit Agarwal

Reputation: 4013

It seems angular 2.0.0-rc2 has fixed a bug with event emitter which is causing this issue

`var emitter = new EventEmitter()
      emitter.emit(data)`

Above line rc1 will emit asynchronously so your changes would be detected in next cycle so you won't get this error.

Same line In rc2 will emit synchronously so changes will happen in same cycle so you will get mentioned error.

EventEmitter has an optional argument to make emitting async. You can modify above code as below to works as it was working in rc1

var emitter = new EventEmitter(true) emitter.emit(data)

Upvotes: 3

Related Questions