509

I am using Angular 2 (TypeScript).

I want to do something with the new selection, but what I get in onChange() is always the last selection. How can I get the new selection?

<select [(ngModel)]="selectedDevice" (change)="onChange($event)">
   <option *ngFor="#i of devices">{{i}}</option>
</select>
onChange($event) {
    console.log(this.selectedDevice);
    // I want to do something here with the new selectedDevice, but what I
    // get here is always the last selection, not the one I just selected.
}
HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
Hongbo Miao
  • 45,290
  • 60
  • 174
  • 267

14 Answers14

948

If you don't need two-way data-binding:

<select (change)="onChange($event.target.value)">
    <option *ngFor="let i of devices">{{i}}</option>
</select>

onChange(deviceValue) {
    console.log(deviceValue);
}

For two-way data-binding, separate the event and property bindings:

<select [ngModel]="selectedDevice" (ngModelChange)="onChange($event)" name="sel2">
    <option [value]="i" *ngFor="let i of devices">{{i}}</option>
</select>
export class AppComponent {
  devices = 'one two three'.split(' ');
  selectedDevice = 'two';
  onChange(newValue) {
    console.log(newValue);
    this.selectedDevice = newValue;
    // ... do other stuff here ...
}

If devices is array of objects, bind to ngValue instead of value:

<select [ngModel]="selectedDeviceObj" (ngModelChange)="onChangeObj($event)" name="sel3">
  <option [ngValue]="i" *ngFor="let i of deviceObjects">{{i.name}}</option>
</select>
{{selectedDeviceObj | json}}
export class AppComponent {
  deviceObjects = [{name: 1}, {name: 2}, {name: 3}];
  selectedDeviceObj = this.deviceObjects[1];
  onChangeObj(newObj) {
    console.log(newObj);
    this.selectedDeviceObj = newObj;
    // ... do other stuff here ...
  }
}

Plunker - does not use <form>
Plunker - uses <form> and uses the new forms API

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 1
    anwere is right but whats the role of ngModel here m still confused will you exaplain it ? – Pardeep Jain Dec 22 '15 at 05:48
  • 1
    @PardeepJain, using two-way data binding with NgModel causes Angular to 1) automatically update `selectedDevice` when the user selects a different item, and 2) if we set the value of `selectedDevice`, NgModel will automatically update the view. Since we also want to be notified of a change I added the `(change)` event binding. We shouldn't have to do it this way... we should be able to break up the two-way data binding into `[ngModel]="selectedDevice" and (ngModelChange)="onChange($event)"`, but as I found out, `onChange()` gets called twice for each select list change that way. – Mark Rajcok Dec 22 '15 at 16:34
  • okay thanx one thing more please. i want to get the value of: `` `course.course_name` and `course.id` both how can i send these two via (change) function ? – Pardeep Jain Dec 24 '15 at 08:55
  • @PardeepJain, use `value=[course]` then the `change()` function will get the entire `course` object, from which you can then get the individual values. – Mark Rajcok Dec 24 '15 at 16:30
  • i want to do same thing with [materialize](http://materializecss.com/getting-started.html).. Tried what you have answered [here](https://plnkr.co/edit/q0WlWjOONTJk9e1tSsZL?p=preview) .. Not Working – Dimple Apr 05 '16 at 12:11
  • @Dimple, yeah, well, I looked at the HTML in your plunker, and I see that materialize inserts an `` into the DOM and uses that in place of the original ` – Mark Rajcok Apr 05 '16 at 14:36
  • @MarkRajcok I have a question. For second way, why the `$event` in `(ngModelChange)="onChange($event)"` just becomes to `newValue` automatically in the function below? Thanks – Hongbo Miao Apr 20 '16 at 21:13
  • 1
    @HongboMiao, the special `$event` variable/symbol is part of the "statement context" that Angular template statements have. If instead I wrote `(ngModelChange)="onChange('abc')"` then `newValue` would get the string `abc`. It's just normal JavaScript function calling. – Mark Rajcok Apr 20 '16 at 21:21
  • Your plunker does not work in firefox nor IE, are you aware of that? – dcp Apr 29 '16 at 00:43
  • @MarkRajcok I saw you updated changing from [value] to [ngValue], both work now for arrary. Any difference? thanks – Hongbo Miao May 19 '16 at 16:17
  • 2
    @HongboMiao, use [ngValue] if you have an array of objects. Use [value] if you have an array of primitive types (string, boolean, integer). – Mark Rajcok May 19 '16 at 17:21
  • on rc4 and nothing seems working, pathetic! I am getting undefined in my function argument. – CodeYogi Jul 07 '16 at 11:43
  • @CodeYogi, I updated the plunker to RC4... it works, take a look. – Mark Rajcok Jul 07 '16 at 15:10
  • @MarkRajcok ah! I see you are not using form. I am using select inside the form, can you please check that? – CodeYogi Jul 07 '16 at 18:45
  • @CodeYogi, it seems to work inside `
    ` tags: http://plnkr.co/edit/pv5j4b1NFyTGFkxHUSga?p=preview. Tested on Chrome only.
    – Mark Rajcok Jul 07 '16 at 21:25
  • @MarkRajcok can you please test it on new form API as name attribute is mandatory on control elements. I will try myself to create a plunker. – CodeYogi Jul 08 '16 at 03:35
  • @CodeYogi, I updated the plunker I referenced in the comment above to use the new form API. I added `name="..."` to the `select` elements that use `ngModel`. – Mark Rajcok Jul 08 '16 at 03:43
  • [ngModel]="selectedDeviceObj" ngModelChange)="onChangeObj($event)" there's a missing parenthesis, in the third block of code in your answer, thanks btw great answer – Sfp Sep 09 '16 at 12:12
  • Update for Material version >= 6 Use the (selectionChange) event on mat-select. – user3444999 May 30 '19 at 17:08
  • It still does not work properly in case of 2-way binding for NUMBER values. – Alexander Jun 24 '20 at 22:42
  • 4
    $event.target.value Property 'value' does not exist on type 'EventTarget'?? can you advice for typescript 4 – user964829 Jun 12 '21 at 12:22
  • If you're dealing with `integer` values, it might be worth noting that `$event.target.value` gives a `string`, so you [might want to](https://stackoverflow.com/questions/66957660/event-target-value-as-a-number) use `$event.target.valueAsNumber` in the template or cast it to a number later in your TypeScript file. – codesnerd Jun 07 '22 at 09:52
45

You can pass the value back into the component by creating a reference variable on the select tag #device and passing it into the change handler onChange($event, device.value) should have the new value

<select [(ng-model)]="selectedDevice" #device (change)="onChange($event, device.value)">
    <option *ng-for="#i of devices">{{i}}</option>
</select>

onChange($event, deviceValue) {
    console.log(deviceValue);
}
Brocco
  • 62,737
  • 12
  • 70
  • 76
30

Just use [ngValue] instead of [value]!!

export class Organisation {
  description: string;
  id: string;
  name: string;
}
export class ScheduleComponent implements OnInit {
  selectedOrg: Organisation;
  orgs: Organisation[] = [];

  constructor(private organisationService: OrganisationService) {}

  get selectedOrgMod() {
    return this.selectedOrg;
  }

  set selectedOrgMod(value) {
    this.selectedOrg = value;
  }
}


<div class="form-group">
      <label for="organisation">Organisation
      <select id="organisation" class="form-control" [(ngModel)]="selectedOrgMod" required>
        <option *ngFor="let org of orgs" [ngValue]="org">{{org.name}}</option>
      </select>
      </label>
</div>
Rusty Rob
  • 16,489
  • 8
  • 100
  • 116
  • 1
    This helped me. Not sure if my case is slightly different, but for me `(change)="selectOrg(selectedOrg)"` calls the function selectOrg with the current/previous selection, not the newly selected option. I figured out, I don't even need the `(change)` bit. – Nick May 19 '16 at 19:12
  • Good spotting. yes that's odd behaviour with (change) passing the old model. I thought it would be like ng-change maybe that's a bug in angular2. I've updated it to use get and set methods. That way in the set selectedOrgMod(value) method I can know that the organisation has changed and update my list of organisational teams. – Rusty Rob May 20 '16 at 05:36
  • works for me in Ang. 15 – Greg Trevellick Jan 27 '23 at 16:51
16

I ran into this problem while doing the Angular 2 forms tutorial (TypeScript version) at https://angular.io/docs/ts/latest/guide/forms.html

The select/option block wasn't allowing the value of the selection to be changed by selecting one of the options.

Doing what Mark Rajcok suggested worked, although I'm wondering if there's something I missed in the original tutorial or if there was an update. In any case, adding

onChange(newVal) {
    this.model.power = newVal;
}

to hero-form.component.ts in the HeroFormComponent class

and

(change)="onChange($event.target.value)"

to hero-form.component.html in the <select> element made it work

jarisky
  • 191
  • 2
  • 9
12

use selectionChange in angular 6 and above. example (selectionChange)= onChange($event.value)

valentine
  • 457
  • 8
  • 17
12

I was has same problem and i solved using the below code :

(change)="onChange($event.target.value)"
Abd Abughazaleh
  • 4,615
  • 3
  • 44
  • 53
7

In Angular 8 you can simply use "selectionChange" like this:

 <mat-select  [(value)]="selectedData" (selectionChange)="onChange()" >
  <mat-option *ngFor="let i of data" [value]="i.ItemID">
  {{i.ItemName}}
  </mat-option>
 </mat-select>
B.Habibzadeh
  • 490
  • 6
  • 10
4

Angular 7/8

As of angular 6,the use of ngModel input property with reactive forms directive have been deprecated and removed altogether in angular 7+. Read official doc here.

Using reactive form approach you can get/set selected data as;

      //in your template
 <select formControlName="person" (change)="onChange($event)"class="form-control">
    <option [value]="null" disabled>Choose person</option>
      <option *ngFor="let person of persons" [value]="person"> 
        {{person.name}}
    </option>
 </select> 


 //in your ts
 onChange($event) {
    let person = this.peopleForm.get("person").value
    console.log("selected person--->", person);
    // this.peopleForm.get("person").setValue(person.id);
  }
7guyo
  • 3,047
  • 1
  • 30
  • 31
4

If you don't need two-way data-binding:

<select (change)="updateSorting($event)">
  <option disabled selected>Sorting</option>
  <option value="pointDes">pointDes</option>
  <option value="timeDes">timeDes</option>
  <option value="timeAsc">timeAsc</option>
  <option value="pointAsc">pointAsc</option>
</select>
updateSorting(e: any) {
  // console.log((e.target as HTMLSelectElement)?.value); // also work
  console.log(e.target.value);
}
tinystone
  • 369
  • 3
  • 6
3

Another option is to store the object in value as a string:

<select [ngModel]="selectedDevice | json" (ngModelChange)="onChange($event)">
    <option [value]="i | json" *ngFor="let i of devices">{{i}}</option>
</select>

component:

onChange(val) {
    this.selectedDevice = JSON.parse(val);
}

This was the only way I could get two way binding working to set the select value on page load. This was because my list that populates the select box was not the exact same object as my select was bound to and it needs to be the same object, not just same property values.

kravits88
  • 12,431
  • 1
  • 51
  • 53
2
<mat-form-field>
<mat-select placeholder="Vacancies" [(ngModel)]="vacanciesSpinnerSelectedItem.code" (ngModelChange)="spinnerClick1($event)"
    [ngModelOptions]="{standalone: true}" required>
    <mat-option *ngFor="let spinnerValue of vacanciesSpinnerValues" [value]="spinnerValue?.code">{{spinnerValue.description}}</mat-option>
</mat-select>

I used this for angular Material dropdown. works fine

Jérémie Bertrand
  • 3,025
  • 3
  • 44
  • 53
Apurv
  • 35
  • 2
1

I tried all the suggestions and nothing works for me.

Imagine the situation: you need a 2-way binding and you have a lookup with NUMBER values and you want to fill your SELECT with the values from this lookup and highlight the chosen option.

Using [value] or (ngModelChange) is a no-go, because you won't be able to select the chosen option after user initiated the change: [value] considers everything a string, as to (ngModelChange) - it obviously should not be used when user initiates the change, so it ruins the proper selection. Using [ngModel] guarantees the fixed format of received VALUE as INDEX: VALUE and it's easy to parse it correspondingly, HOWEVER once again - it ruins the selected option.

So we go with [ngValue] (which will take care of proper types), (change) and... [value], which guarantees the handler receives VALUE, not a DISPLAYED VALUE or INDEX: VALUE :) Below is my working clumsy solution:

  <select
    class="browser-default custom-select"
    (change)="onEdit($event.target.value)"
  >
    <option [value]="">{{
      '::Licences:SelectLicence' | abpLocalization
    }}</option>
    <ng-container *ngIf="licencesLookupData$ | async">
      <option
        *ngFor="let l of licencesLookupData$ | async"
        [ngValue]="l.id"
        [value]="l.id"
        [selected]="l.id == selected.id"
      >
        {{ l.id }} &nbsp;&nbsp; {{ l.displayName | defaultValue }}
      </option>
    </ng-container>
  </select>

  onEdit(idString: string) {
    const id = Number(idString);
    if (isNaN(id)) {
      this.onAdd();
      return;
    }
    this.licencesLoading = true;
    this.licencesService
      .getById(id)
      .pipe(finalize(() => (this.licencesLoading = false)), takeUntil(this.destroy))
      .subscribe((state: Licences.LicenceWithFlatProperties) => {
        this.selected = state;
        this.buildForm();
        this.get();
      });
  }
Alexander
  • 1,152
  • 1
  • 16
  • 18
0

latest ionic 3.2.0 have modified (change) to (ionChange)

eg: HTML

<ion-select (ionChange)="function($event)"> <ion-option>1<ion-option>
</ion-select>

TS

function($event){
// this gives the selected element
 console.log($event);

}
skid
  • 958
  • 4
  • 13
  • 29
0

In Angular 5 I did with the following way. get the object $event.value instead of $event.target.value

<mat-form-field color="warn">
   <mat-select (ngModelChange)="onChangeTown($event)" class="form-width" formControlName="branch" [(ngModel)]="branch" placeholder="Enter branch">
     <mat-option *ngFor="let branch of branchs" [value]="branch.value">
                  {{ branch.name }}
     </mat-option>
   </mat-select>
</mat-form-field>

onChangeTown(event): void {
  const selectedTown = event;
  console.log('selectedTown: ', selectedTown);
}
valentine
  • 457
  • 8
  • 17
Kumaresan Perumal
  • 1,926
  • 2
  • 29
  • 35