5

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

Boann
  • 48,794
  • 16
  • 117
  • 146
eutychos tfar
  • 169
  • 2
  • 9

4 Answers4

3

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.

Kiran Mistry
  • 2,614
  • 3
  • 12
  • 28
unsignedmind
  • 182
  • 1
  • 12
  • your code works but there an issue, when i select an option from the drop down the "selection" object in the termSelectChanged() will show the previously selected value? – eutychos tfar Nov 12 '20 at 03:20
  • 2
    I think that because of this statement `(ngModelChange)='termSelectChanged(selected)' ` just change the `selected` to `$event` or something. It should work in my guess – Rishanthakumar Nov 12 '20 at 03:47
3

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.

Rishanthakumar
  • 871
  • 10
  • 20
  • 1
    You shouldn't ever have to call internal NgModel methods directly. – Ingo Bürk Nov 11 '20 at 06:42
  • @Rishanthakumar In your post that you have mentioned i'm not using "Direct model Binding". I was curious, if i would want to use "Direct Model binding", then how would i make the (ngModelChange) event fire when the component loads? Could you please post direct model binding along with the existing property binding as well? – eutychos tfar Nov 12 '20 at 02:59
  • @eutychostfar what I mentioned was about the "Two Way Binding", where we can put `[(ngModel)]="selected"`. So that you don't need to update the `this.selected` variable when the `ngModelChange` fired since it is bound both way. Any reason for you to trigger the `ngModelChange` initially, because in a simple way you can trigger that method from the same place you are updating the variable. – Rishanthakumar Nov 12 '20 at 03:20
  • @Rishanthakumar The reason i want to execute the event on component load is, i want to get the object that i'm storing in the "value" property of the selected "option" of the drop down and this value will go through some processes later on. I have tried the method that was give by the stack overflow user "unassignedmind"( see below ) his technique works but when i select an option from the drop down "termSelectChanged()" will receive the previously selected object. So i was thinking that, there should be a proper way to do this? – eutychos tfar Nov 12 '20 at 03:26
  • 1
    @eutychostfar I think that because of this statement in his solution `(ngModelChange)='termSelectChanged(selected)'` just change the `selected` to `$event` or something. It should work in my guess. – Rishanthakumar Nov 12 '20 at 03:36
  • @Rishanthakumar after making the changes that you have suggested, the answer given by stack user "unassignedmind" worked. So i have given him an upvote. But i could mark your(Rishanthakumar ) answer as a complete answer, if you could give the alternative solution using direct two-way binding as well? – eutychos tfar Nov 12 '20 at 04:28
  • 1
    @eutychostfar I think you should mark "unassignedmind" answer as correct since it is simple. May be my answer can stay here just for the knowledge. Again to make it direct two-way binding, you need to put this `[(ngModel)]="selected"` and also apart from that to make your solution work in addition to this `this.ngModel.viewToModelUpdate(this.termsColl[1]);` you need to assign the value for the variable in the subscription as well. And then you can remove the variable assignment in `ngModelChange` trigger. – Rishanthakumar Nov 12 '20 at 04:39
  • @Rishanthakumar no issues, I will mark it as you have suggested and thank you for the tip on how to extend the code to support direct two way binding : ) – eutychos tfar Nov 12 '20 at 05:13
2

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

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

Pallavi
  • 496
  • 5
  • 12