Skip to content

Order of ngModel & onModelChange #11234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ObaidUrRehman opened this issue Sep 1, 2016 · 25 comments
Open

Order of ngModel & onModelChange #11234

ObaidUrRehman opened this issue Sep 1, 2016 · 25 comments
Labels
area: core Issues related to the framework runtime core: inputs / outputs freq1: low P4 A relatively minor issue that is not relevant to core functions type: confusing workaround1: obvious
Milestone

Comments

@ObaidUrRehman
Copy link

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
Currently the order of (ngModelChange) and [(ngModel)] on an input element matters. In the following case the ngModelChange callback is called before ngModel updates the value of model:

<select (ngModelChange)="onModelChange()" [(ngModel)]="hero.name" >
   <option>Dexter</option>
   <option>Voltron</option>
</select>

The following however works fine:

<select  [(ngModel)]="hero.name" (ngModelChange)="onModelChange()">
   <option>Dexter</option>
   <option>Voltron</option>
</select>

Expected/desired behavior

Not sure what the expected behavior should be or if this the intended behavior. But I think the order of attributes in an element should not matter.

Reproduction of the problem
Link to plunker: http://plnkr.co/edit/wrVrHYx3pPdLmCKz17RL?p=preview

Open up the console and select Hero name from the drop-down. Notice how the values are printed in console. Swap the order of ngModel and onModelChange and observe the difference.

  • Angular version: 2.0.0-rc.5
  • Browser: Chrome Version 52.0.2743.116 m
  • Language: TypeScript
@pkozlowski-opensource
Copy link
Member

@ObaidUrRehman so this is an interesting one! To fully understand what is going on here we need to observe that [(ngModel)]="hero.name" is just a short-cut that can be de-sugared to: [ngModel]="hero.name" (ngModelChange)="hero.name = $event".

So if we de-sugar your code we would end up with: <select (ngModelChange)="onModelChange()" [ngModel]="hero.name" (ngModelChange)="hero.name = $event"> or <[ngModel]="hero.name" (ngModelChange)="hero.name = $event" select (ngModelChange)="onModelChange()">.

If you inspect the above code you will notice that we end up with 2 ngModelChange events and those need to be executed in some order.

We could potentially sort event handlers in the way that sugared one is executed first so I will mark it as a "bug" but don't expect it to be fixed in priority.

Anyway, I think that in your case you are after having the latest (changed) model value in your ngModelChange and you can easily achieve this by doing (ngModelChange)="onModelChange($event)"

@AzatNitka
Copy link

I found the same. But there is no any description or note about order ngModel and ngModelChange in the documentation! Or! One year it's not fixed yet. Strange, very strange

@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018
@lvanderbijl
Copy link

In addition, ngModelChange fires before the form is marked as dirty. This means any subsequent check for a dirty form done in the same thread, will NOT regard the form as dirty.

@trotyl
Copy link
Contributor

trotyl commented Feb 16, 2018

Supersede by #21513. closed by #21514.

@CWSpear
Copy link

CWSpear commented Aug 30, 2018

At least some form of this bug still lives on, #21514 did not change the fact that the order of [(ngModel)] and (ngModelChange) in your HTML will affect how they behave.

Tested in latest Angular 6.1.6.

@alinmateut
Copy link

The bug is still here in version 8.2.14

@KirillNikalaichuk
Copy link

The bug is still here in version 9.0.0-rc.8

@pbrissaud
Copy link

This bug is still here in version 9.1.1
My code worked fine in 8.2 and when I updated to Angular 9, I had to change the position.

@jgutix
Copy link

jgutix commented Apr 26, 2020

In the original plunker is not that noticeable the issue, here is a reproduction on Angular 9, that affects filtering: https://angular-u3dg2n.stackblitz.io

@cskata
Copy link

cskata commented May 6, 2020

This bug caused me a massive headache today. Please fix it, 2020 is bad enough already.

@kara
Copy link
Contributor

kara commented May 27, 2020

As @pkozlowski-opensource points out, outputs have to be executed in some order. Template order seems reasonable to me as a default ordering scheme, but I would be curious to hear more thoughts. Are you expecting something like alphabetical order?

It's worth noting that the best practice for ngModelChange is to pass through the $event instead of relying on ngModel order (which is brittle). That way, you will always have the correct value.

@jgutix
Copy link

jgutix commented May 27, 2020

@kara I think the issue is not about the expected order, probably most of us were not aware of the de-sugaring, however adding to the confusion some IDEs(PHPStorm) as part of the code cleaning would move ngModelChange first.

The use case here is not to set ngModel with the right value, but to do other stuff after ngModel is set.

Ideally the de-sugaring would make sure that there's no duplicated ngModelChange, producing something like: <select (ngModelChange)="hero.name = $event; onModelChange()" [ngModel]="hero.name"> following the @pkozlowski-opensource example.

@kara
Copy link
Contributor

kara commented May 27, 2020

however adding to the confusion some IDEs(PHPStorm) as part of the code cleaning would move ngModelChange first

Ouch, I didn't realize that was happening. I can see how that would cause problems.

Ideally the de-sugaring would make sure that there's no duplicated ngModelChange, producing something like: <select (ngModelChange)="hero.name = $event; onModelChange()" [ngModel]="hero.name"> following the @pkozlowski-opensource example.

That's an interesting idea. We probably wouldn't want to special case the compiler for ngModel specifically, but it's seems like it could be common sense for most two-way bindings... something to think about.

@joshuakeel
Copy link

Just experienced this issue, and wasn't aware of the ordering being important. I understand the rationale behind the current behavior, and how it might be challenging to fix cleanly.

I think better documentation could help some people out. We essentially need a note (probably in the NgModel API docs) that reminds people about the de-sugaring, and that the order matters.

I also think it would be best if order did not matter at all. Not sure what the best way to achieve that would be, but it would clear up a lot of confusion and unexplained behavior.

@SherifNeamatalla
Copy link

Just had the same issue, changing the order did fix it ! This is one of the interesting bugs in Angular !

@mlc-mlapis
Copy link
Contributor

mlc-mlapis commented Jul 31, 2020

@SherifNeamatalla There is a long story about that topic. Some versions ago, the order was exactly the opposite, but there were other good reasons why the change was done. So it's not a bug, It was controlled change.

@Credo-Zhao
Copy link

2016~~2020?!Why not resolved?

@aikithoughts aikithoughts added the P4 A relatively minor issue that is not relevant to core functions label Apr 27, 2021
@DavideDelbianco
Copy link

If this behaviour will change in the future, please mark it as a breaking change because i think i am not the only one relying on the order of execution to oldValue (ngModel) vs newValue ($event) to mock the old $scope.$watch in angularJS

@rainbow-six3
Copy link

Same issue here. Any progress?

@SherifNeamatalla
Copy link

hey,

I haven't written angular for 1 year so I can't really remember the exact order, but I remember that one had to come before the other or else it breaks

@AmirSavand
Copy link

AmirSavand commented Jun 15, 2022

Guys, there's FormControl why would you ever use [ngModel]?

class FormControl<TValue = any> extends AbstractControl<TValue> {

  new (): FormControl<any>
  defaultValue: TValue
  setValue(value: TValue, options?: { onlySelf?: boolean; emitEvent?: boolean; emitModelToViewChange?: boolean; emitViewToModelChange?: boolean; }): void
  patchValue(value: TValue, options?: { onlySelf?: boolean; emitEvent?: boolean; emitModelToViewChange?: boolean; emitViewToModelChange?: boolean; }): void
  reset(formState?: TValue | FormControlState<TValue>, options?: { onlySelf?: boolean; emitEvent?: boolean; }): void
  getRawValue(): TValue
  registerOnChange(fn: Function): void
  registerOnDisabledChange(fn: (isDisabled: boolean) => void): void

  // inherited from forms/AbstractControl
  constructor(validators: ValidatorFn | ValidatorFn[], asyncValidators: AsyncValidatorFn | AsyncValidatorFn[])
  value: TValue
  validator: ValidatorFn | null
  asyncValidator: AsyncValidatorFn | null
  parent: FormGroup | FormArray | null
  status: FormControlStatus
  valid: boolean
  invalid: boolean
  pending: boolean
  disabled: boolean
  enabled: boolean
  errors: ValidationErrors | null
  pristine: boolean
  dirty: boolean
  touched: boolean
  untouched: boolean
  valueChanges: Observable<TValue>
  statusChanges: Observable<FormControlStatus>
  updateOn: FormHooks
  root: AbstractControl
  setValidators(validators: ValidatorFn | ValidatorFn[]): void
  setAsyncValidators(validators: AsyncValidatorFn | AsyncValidatorFn[]): void
  addValidators(validators: ValidatorFn | ValidatorFn[]): void
  addAsyncValidators(validators: AsyncValidatorFn | AsyncValidatorFn[]): void
  removeValidators(validators: ValidatorFn | ValidatorFn[]): void
  removeAsyncValidators(validators: AsyncValidatorFn | AsyncValidatorFn[]): void
  hasValidator(validator: ValidatorFn): boolean
  hasAsyncValidator(validator: AsyncValidatorFn): boolean
  clearValidators(): void
  clearAsyncValidators(): void
  markAsTouched(opts: { onlySelf?: boolean; } = {}): void
  markAllAsTouched(): void
  markAsUntouched(opts: { onlySelf?: boolean; } = {}): void
  markAsDirty(opts: { onlySelf?: boolean; } = {}): void
  markAsPristine(opts: { onlySelf?: boolean; } = {}): void
  markAsPending(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
  disable(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
  enable(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
  setParent(parent: FormGroup<any> | FormArray<any>): void
  abstract setValue(value: TRawValue, options?: Object): void
  abstract patchValue(value: TValue, options?: Object): void
  abstract reset(value?: TValue, options?: Object): void
  getRawValue(): any
  updateValueAndValidity(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
  setErrors(errors: ValidationErrors, opts: { emitEvent?: boolean; } = {}): void
  get<P extends string | ((string | number)[])>(path: P): AbstractControl<ɵGetProperty<TRawValue, P>> | null
  getError(errorCode: string, path?: string | (string | number)[]): any
  hasError(errorCode: string, path?: string | (string | number)[]): boolean
}

I kid 😋! Try to use this instead as much as possible.

@Vikingama
Copy link

https://github.com/NiklasPor/prettier-plugin-organize-attributes

@Vikingama
Copy link

@imaksp
Copy link

imaksp commented Feb 7, 2024

Faced this issue today not sure why this is not fixed yet, now I think will just use value from event instead of depending on order.

@alexnoise79
Copy link

alexnoise79 commented Feb 27, 2024

Is weird that order affects the behavior, but there is another problem... when you autoreformat code in IDE like intelliJ ngModel is moved after ngModelChange.

i hope Jetbrains Will fix this

https://youtrack.jetbrains.com/issue/IDEA-347524/Angular-Plugin-reformat-ngModel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: core Issues related to the framework runtime core: inputs / outputs freq1: low P4 A relatively minor issue that is not relevant to core functions type: confusing workaround1: obvious
Projects
None yet
Development

No branches or pull requests