5
votes

In my Angular 8 component, I have added bi-directional binding to a dropdown control.

The view

<select  (ngModelChange)='termSelectChanged($event)' [ngModel]="selected">
  <option [ngValue]="t" *ngFor='let t of termsColl'>{{t?.code}}</option>
</select>

The component code

export class AppComponent implements OnInit{


     public termsColl : Array<DDModel>;
     public selected : DDModel;
     
      constructor( private s : DDService ){}
      
    termSelectChanged( event  ){    
            alert('HIT');
    }
    
    
      ngOnInit(){
        
        //service call #1
        this.s.getDataForComplexBind().subscribe(  data => {
          this.termsColl = data;
         
        }, error => error );    


        //service call #2
        this.s.getOtherData(  ).subscribe( data => {
          
          //model changes here
          this.selected = this.termsColl[1];
          
        }, error => {  console.error(error);  });

                    
    }

} 

When the component loads, it executes ngOnInit() and sets the model-bound property Selected with the first element of the array termsColl. termsColl has data but the line this.selected = this.termsColl[1]; does not change the selected option to the first element in the dropdown. In fact, when the component loads, I was expecting it to fire the event ngModelChange but it did NOT fire the event either. I have added an alert() in the code but it did not show when the component loads. It shows only if I select an option from the dropdown. How do I change the code so it will execute the ngModelChange event when the component loads?

Here is my stackblitz https://stackblitz.com/edit/angular-version-yeg27j?file=src%2Fapp%2Fapp.component.ts

4
@ArunkumarRamasamy i have updated my post with stakcblitz - eutychos tfar
I don't understand why do you need (ngModelChange) to fire when setting the value initially. Can you explain to me why? - Saiyaff Farouk

4 Answers

3
votes

You can use viewToModelUpdate() of ngModel to update the value and if you want to trigger the ngModelChange. You can find more about it here.

But you need to do the following changes.

In html template:

<select  (ngModelChange)='termSelectChanged($event)' [ngModel]="selected" #ngModel="ngModel">
  <option [value]="t" *ngFor='let t of termsColl'>{{t?.code}}</option>
</select>

You can see I am adding a reference to the ngModel in template, which I will use it in the component class.

In the component class:

export class AppComponent {
  name = "Angular 6";
  version = VERSION.full;

  public termsColl: Array<DDModel>;
  public selected: string;

  @ViewChild("ngModel") ngModel: NgModel;

  constructor(private s: DDService) {}

  termSelectChanged(event) {
    this.selected = event;
  }

  ngOnInit() {
    this.s.getOtherData().subscribe(
      data => {
        this.termsColl = data;
        this.ngModel.viewToModelUpdate(this.termsColl[1]);
      },
      error => {
        console.error(error);
      }
    );
  }
}

You can see I am using the ngModel reference to call the viewToModelUpdate with the value, which in return triggers the ngModelChange.

Since you are not using two way binding directly, you have to set the value to the selected variable inside the trigger function termSelectChanged.

Hope this would help you to achieve your requirement.

3
votes

I changed the ngModeChange input to DDModel and called your termSelectChanged() from inside the subscribe. I also changed [ngModel] to [(ngModel)]

<select  (ngModelChange)='termSelectChanged($event)' [(ngModel)]="selected">
  <option [ngValue]="t" *ngFor='let t of termsColl'>{{t?.code}}</option>
</select>
  termSelectChanged(selection: DDModel) {
    console.log("HIT", selection);
  }

  ngOnInit() {
    this.s.getOtherData().subscribe(
      data => {
        this.termsColl = data;
        this.selected = this.termsColl[0];
        this.termSelectChanged(this.selected);
      },
      error => {
        console.error(error);
      }
    );
  }

I can't tell you why changing this.selected from code does not trigger the ngModelChange. Maybe it's because ngModelChange is called in the template.

2
votes

You can use value instead of ngValue in you option elements. And assign t.code instead of t to the value attribute.

<select  (ngModelChange)='termSelectChanged($event)' [ngModel]="selected">
  <option [value]="t.code" *ngFor='let t of termsColl'>{{t?.code}}</option>
</select>

Reference: https://angular-version-xkjuff.stackblitz.io

2
votes

termsColl has data but the code line this.selected = this.termsColl[1]; does not change the selected option to the first element in the drop down.

Because you are using propery binding [ngModel]="selected", not two-way data binding.

[(ngModel)]="selected" is for two-way binding, and the syntax is compound from:

[ngModel]="selected" and (ngModelChange)="selected = $event"

Also, the value of selected should be the value which is available in dropdown, i.e

ngOnInit(){
 
 this.selected = this.termsColl[1].code

}

Below code would work for you:

<select  [(ngModel)]="selected" (change)="onSelection()">
  <option  *ngFor='let t of termsColl' [value]="t.code" [selected]="selected === t.code">{{t?.code}}</option>
</select>

You might also want to refer this https://stackoverflow.com/a/63192748/13596406