Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ngModel binding is null in OnInit in Angular 2

I ran into a issue using Angular 2 ngModel binding. plnkr

If I use ngModel to bind a value to a child component, the value is not populated on the OnInit function of the child component. So If I bind to a property call "boundName" and I try to access that in the OnInit, it will be null. However if I bind the to the same value in the parent control not using ngModel but a input parameter, the value is accessible in the OnInit function.

So.. if my parent component creates a child component like

 <my-boundcomp [(ngModel)]="name" [(inputName)] ="name" ></my-boundcomp>

And my child components onInit Function is

public ngOnInit() {
   console.log("Input Name :" + this.inputName);
   console.log("Bound Name :" + this.boundName);
   this._boundNameOnInit = this.boundName; // <--- Going to be null
   this._inputNameOnInit = this.inputName; // <--- Going to be not null

}

I found this behavior to be odd, and unexpected. I'm not sure if this is a bug or I am not using the FormsModule ngModel correctly, but interesting enough that I thought I should share.

Here is the full plnkr https://plnkr.co/edit/Im5oz7q1HhG5MgGTTZ1R?p=preview

like image 324
Jon Z Avatar asked Oct 03 '16 16:10

Jon Z


2 Answers

This code

[(ngModel)]="name"

sets the value of NgModel in OnInit not of BoundValueComponent. When BoundValueComponent.writeValue is called from NgModel, then boundName is set.

I'm pretty sure this is as designed.

like image 93
Günter Zöchbauer Avatar answered Sep 27 '22 22:09

Günter Zöchbauer


I believe this is a "problem" if you are dependent on the value that is set via writeValue when using the ControlValueAccessor. Since boundName is set and ngOnInit is one of the first things fired, the writeValue has not had a chance to run yet.

I tried adding this on some other lifecycle hooks (AfterViewInit, AfterContentInit) and it is still too early. In fact if you default your boundName to '' or something you will notice that in AfterViewInit or AfterContentInit, it is actually nulled out before the writeValue is called.

For this reason, I would suggest setting your value in the writeValue method. If you need it to be set only once, you can use a flag. See your code below...

export class BoundValueComponent  implements OnInit, ControlValueAccessor {

@Input() inputName: string;
boundName: string;

private _boundNameOnInit : string;
private _inputNameOnInit : string;

private initialized: boolean = false;

private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;

constructor() {
}

public ngOnInit() {
   console.log("Input Name :" + this.inputName);
   console.log("Bound Name :" + this.boundName);
  // this._boundNameOnInit = this.boundName; // <--- Going to be null
   this._inputNameOnInit = this.inputName; // <--- Going to be not null
}

get value(): any {
  return this.boundName;
};

/**
* Set accessor including call the onchange callback.
*/
set value(v: any) {
  if (v !== this.boundName) {
     this.boundName = v;
     this.onChangeCallback(v);
  }
}

/**
* From ControlValueAccessor interface.
*/
writeValue(value: any) {
 this.boundName = value;

 if (!this.initialized) {
   this._boundNameOnInit = this.boundName;
   this.initialized = true;
 }
}
.......
like image 42
Chewy Avatar answered Sep 27 '22 20:09

Chewy