Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Different behavior for required and ng-required for initial values #9063

Closed
adharris opened this issue Sep 13, 2014 · 14 comments · Fixed by #10048
Closed

Different behavior for required and ng-required for initial values #9063

adharris opened this issue Sep 13, 2014 · 14 comments · Fixed by #10048

Comments

@adharris
Copy link

I'm noticing different behavior when using required vs ng-required when combined with ng-change. When the initial model value is any empty value besides undefined (i.e. null or ""), ng-changed is fired when using ng-required="true", but not when using the basic required.

It also seems that this behavior has changed from 1.3.0 rc0 and rc1. In rc0, the change handler is never called.

The behavior for ng-required in rc1 makes sense to me; validation is run for the initial value, null and "" do not pass validation so the model value is set to undefined, and sense undefined does not equal the initial value ng-change is evaluated. But shouldn't the two variants behave the same?

Here is a plunker demonstrating the differences: http://plnkr.co/edit/e1J1VnUSpR8xVhdKIXVY?p=preview

I'm not sure, but this might be related to #8949 and/or #9001

@Narretz
Copy link
Contributor

Narretz commented Sep 15, 2014

This is probably because ngRequired observes the required attr and fires the validation a second time.
Actually, I don't think the behavior of ngRequired in rc.1 is correct. The validation calls the parsers, which detect an undefined viewValue and therefore changes the model value to undefined (without calling any parsers), which triggers ngChange. But as @caitp pointed out in #9044 (comment) angular should not actually parse empty input values.

@caitp
Copy link
Contributor

caitp commented Sep 15, 2014

to expand on that, I don't think we should parse any values unless setViewValue was called, which should not happen when calling $validate() in isolation (imo)

@Narretz
Copy link
Contributor

Narretz commented Sep 15, 2014

I agree. At a minimum, running parsers should be extracted from $validate. But I haven't checked how tight the coupling overall still is.

@shahata
Copy link
Contributor

shahata commented Sep 15, 2014

For the record, re-parsing on $validate was introduced in tbosch@1ec8bde
The goal was to get rid of $$invalidModelValue. If we do not want to re-parse, it means we need to add $parsedModelValue or something similar as discussed in #9016 (comment)

@Narretz
Copy link
Contributor

Narretz commented Sep 28, 2014

This "fails" even with 92f05e5 (don't parse if viewValue is undefined)
This is because there's an unnecessary validation after the model is set to the view for the first time:

When the ngModelController is created, it checks the model via $watch, formats it, sets its viewValue, and calls $$runValidators. This is okay.
The problem is that all directives / validators that use an observer call $validate very often, too often I'd say.
-> before the ngModelCtrl is initialized: these are ignored because the $modelValue isn't set yet => Okay
-> after the modelWatch is called: these go through, and since the modelValue is invalid, they set the model to undefined. Since this observer comes directly after the first model -> value, it is unnecessary, since the viewValue / modelValue cannot have changed yet.

One solution is to not call $validate inside observers when the attr value hasn't changed. It's actually possible to that $validate is called far more often that it needs to be. The problem here is that this is needed for all observers, so everyone who writes a validator needs to be aware of this boilerplate.

@btford btford self-assigned this Sep 29, 2014
@IgorMinar
Copy link
Contributor

@tbosch can we have a quick chat about this some time today?

@caitp
Copy link
Contributor

caitp commented Sep 30, 2014

@Narretz yes, I've thought about this, unfortunately this is hard.

I think we do want to run the validators on demand not sure we can defer validation the same way we defer adding/removing classes) --- however, we don't want to incorrectly report errors that aren't really errors. I'm not sure how (if?) we're still seeing incorrect parse errors (you seem to be saying we are), it would be helpful if you can provide a repro so that it's easier to investigate

@Narretz
Copy link
Contributor

Narretz commented Sep 30, 2014

@caitp This specific issue is actually not about parse errors, I think your fix got those (at least those I know).
This is about inputs that a have an invalid initial value & use a validator that is called by observe. In this plunker, I have put some console.log inside the angular.js file:

http://plnkr.co/edit/NItvaqntVXGPlgYvFbNI?p=preview

  1. observe inside the directive is called, but no parse / validation is called because the modelCtrl isn't ready yet.
  2. the modelCtrl detects a change in the watch and after setting the view calls $$runValidators (which let's us keep initially invalid models)
  3. the observer is fired again, this time it calls parseAndValidate. Because the value is invalid, the model is changed and therefore the viewChangeListeners are called.

@jeffbcross jeffbcross modified the milestones: 1.3.0-rc.4, 1.3.0-rc.5 Oct 2, 2014
@btford
Copy link
Contributor

btford commented Oct 8, 2014

@IgorMinar @tbosch what was the result of this conversation?

@tbosch
Copy link
Contributor

tbosch commented Oct 8, 2014

@btford All our points are already mentioned in the previous commnents.

@btford btford modified the milestones: 1.3.0-rc.5, 1.3.0-rc.6 Oct 8, 2014
@IgorMinar IgorMinar removed this from the 1.3.0-rc.6 milestone Oct 13, 2014
@IgorMinar IgorMinar modified the milestones: 1.3.0, 1.3.0-rc.6 Oct 13, 2014
@tbosch tbosch modified the milestones: 1.3.x, 1.3.1, Backlog Oct 15, 2014
@Narretz
Copy link
Contributor

Narretz commented Oct 21, 2014

What I don't understand (and previously didn't pay any attention to) is that required only fires the observer once, but ngRequired twice. I can for the love of it not understand why that is.

e: it's not the cause of the issue, but it's interesting nevertheless.
The actual cause lies somewhere here, 9ad7d74, probably something about the removal of $$invalidModelValue

@onehourlate
Copy link

Just to add my two cents : this change even breaks the most basic angular-only behavior with checkbox input types.

If an input checkbox has an initial model of undefined or null, and it has both ng-required and ng-change, the ng-change callback will fire on page load, which is definitely not supposed to happen.

cf http://plnkr.co/edit/5gqfJvtxwHpvSHoPDBfA?p=preview (the $scope.counter equals to 1 after page load, when it should be 0)

@Narretz
Copy link
Contributor

Narretz commented Nov 12, 2014

@btford I'm taking this over, this is part of a bigger problem with ngModel

@Narretz Narretz assigned Narretz and unassigned btford Nov 12, 2014
Narretz added a commit to Narretz/angular.js that referenced this issue Nov 13, 2014
Previously, $validate would execute the parsers to obtain a
modelValue for validation. This was necessary, because a validator
that is called outside of model / view update (e.g. from an observer)
otherwise might only an undefined modelValue, because a previous
view update has found a validation $error and set the model
to undefined (as is tradition in angular)

This is problematic as validators that are run immediately after
the ngModelController initializes would parse the modelValue
and replace the model, even though there had been no user input.

The solution is to go back to an older design: the ngModelController
will now internally record the $$rawModelValue. This means a model
or view update will store the set / parsed modelValue regardless
of validity, that is, it will never set it to undefined because of
validation errors.

When $validate is called, the $$rawModelValue will passed to the
validators. If the validity has changed, the usual behavior is kept:
if it became invalid, set the model to undefined, if valid,
restore the last available modelValue - the $$rawModelValue.

Additionally, $validate will only update the model when the validity
changed. This is to prevent setting initially invalid models other
than undefined to undefined (see angular#9063)

Fixes: angular#9063
Fixes: angular#9959
Fixes: angular#9996
Fixes: angular#10025

Closes: angular#9890
Closes: angular#9913
Closes: angular#9997
Narretz added a commit to Narretz/angular.js that referenced this issue Nov 13, 2014
Previously, $validate would execute the parsers to obtain a
modelValue for validation. This was necessary, because a validator
that is called outside of model / view update (e.g. from an observer)
otherwise might only an undefined modelValue, because a previous
view update has found a validation $error and set the model
to undefined (as is tradition in angular)

This is problematic as validators that are run immediately after
the ngModelController initializes would parse the modelValue
and replace the model, even though there had been no user input.

The solution is to go back to an older design: the ngModelController
will now internally record the $$rawModelValue. This means a model
or view update will store the set / parsed modelValue regardless
of validity, that is, it will never set it to undefined because of
validation errors.

When $validate is called, the $$rawModelValue will passed to the
validators. If the validity has changed, the usual behavior is kept:
if it became invalid, set the model to undefined, if valid,
restore the last available modelValue - the $$rawModelValue.

Additionally, $validate will only update the model when the validity
changed. This is to prevent setting initially invalid models other
than undefined to undefined (see angular#9063)

Fixes: angular#9063
Fixes: angular#9959
Fixes: angular#9996
Fixes: angular#10025

Closes: angular#9890
Closes: angular#9913
Closes: angular#9997
Narretz added a commit to Narretz/angular.js that referenced this issue Nov 13, 2014
Previously, $validate would execute the parsers to obtain a
modelValue for validation. This was necessary, because a validator
that is called outside of model / view update (e.g. from an observer)
otherwise might only an undefined modelValue, because a previous
view update has found a validation $error and set the model
to undefined (as is tradition in angular)

This is problematic as validators that are run immediately after
the ngModelController initializes would parse the modelValue
and replace the model, even though there had been no user input.

The solution is to go back to an older design: the ngModelController
will now internally record the $$rawModelValue. This means a model
or view update will store the set / parsed modelValue regardless
of validity, that is, it will never set it to undefined because of
validation errors.

When $validate is called, the $$rawModelValue will passed to the
validators. If the validity has changed, the usual behavior is kept:
if it became invalid, set the model to undefined, if valid,
restore the last available modelValue - the $$rawModelValue.

Additionally, $validate will only update the model when the validity
changed. This is to prevent setting initially invalid models other
than undefined to undefined (see angular#9063)

Fixes: angular#9063
Fixes: angular#9959
Fixes: angular#9996
Fixes: angular#10025

Closes: angular#9890
Closes: angular#9913
Closes: angular#9997
Narretz added a commit to Narretz/angular.js that referenced this issue Nov 15, 2014
Previously, $validate would execute the parsers to obtain a
modelValue for validation. This was necessary, because a validator
that is called outside of model / view update (e.g. from an observer)
otherwise might only an undefined modelValue, because a previous
view update has found a validation $error and set the model
to undefined (as is tradition in angular)

This is problematic as validators that are run immediately after
the ngModelController initializes would parse the modelValue
and replace the model, even though there had been no user input.

The solution is to go back to an older design: the ngModelController
will now internally record the $$rawModelValue. This means a model
or view update will store the set / parsed modelValue regardless
of validity, that is, it will never set it to undefined because of
validation errors.

When $validate is called, the $$rawModelValue will passed to the
validators. If the validity has changed, the usual behavior is kept:
if it became invalid, set the model to undefined, if valid,
restore the last available modelValue - the $$rawModelValue.

Additionally, $validate will only update the model when the validity
changed. This is to prevent setting initially invalid models other
than undefined to undefined (see angular#9063)

Fixes: angular#9063
Fixes: angular#9959
Fixes: angular#9996
Fixes: angular#10025

Closes: angular#9890
Closes: angular#9913
Closes: angular#9997
Narretz added a commit to Narretz/angular.js that referenced this issue Nov 17, 2014
Previously, $validate would execute the parsers to obtain a
modelValue for validation. This was necessary, because a validator
that is called outside of model / view update (e.g. from an observer)
otherwise might only an undefined modelValue, because a previous
view update has found a validation $error and set the model
to undefined (as is tradition in angular)

This is problematic as validators that are run immediately after
the ngModelController initializes would parse the modelValue
and replace the model, even though there had been no user input.

The solution is to go back to an older design: the ngModelController
will now internally record the $$rawModelValue. This means a model
or view update will store the set / parsed modelValue regardless
of validity, that is, it will never set it to undefined because of
validation errors.

When $validate is called, the $$rawModelValue will passed to the
validators. If the validity has changed, the usual behavior is kept:
if it became invalid, set the model to undefined, if valid,
restore the last available modelValue - the $$rawModelValue.

Additionally, $validate will only update the model when the validity
changed. This is to prevent setting initially invalid models other
than undefined to undefined (see angular#9063)

Fixes: angular#9063
Fixes: angular#9959
Fixes: angular#9996
Fixes: angular#10025

Closes: angular#9890
Closes: angular#9913
Closes: angular#9997
Closes: angular#10048
Narretz added a commit to Narretz/angular.js that referenced this issue Nov 17, 2014
Previously, $validate would execute the parsers to obtain a
modelValue for validation. This was necessary, because a validator
that is called outside of model / view update (e.g. from an observer)
otherwise might only an undefined modelValue, because a previous
view update has found a validation $error and set the model
to undefined (as is tradition in angular)

This is problematic as validators that are run immediately after
the ngModelController initializes would parse the modelValue
and replace the model, even though there had been no user input.

The solution is to go back to an older design: the ngModelController
will now internally record the $$rawModelValue. This means a model
or view update will store the set / parsed modelValue regardless
of validity, that is, it will never set it to undefined because of
validation errors.

When $validate is called, the $$rawModelValue will passed to the
validators. If the validity has changed, the usual behavior is kept:
if it became invalid, set the model to undefined, if valid,
restore the last available modelValue - the $$rawModelValue.

Additionally, $validate will only update the model when the validity
changed. This is to prevent setting initially invalid models other
than undefined to undefined (see angular#9063)

Fixes: angular#9063
Fixes: angular#9959
Fixes: angular#9996
Fixes: angular#10025

Closes: angular#9890
Closes: angular#9913
Closes: angular#9997
Closes: angular#10048
@Narretz
Copy link
Contributor

Narretz commented Nov 25, 2014

This is fixed in 1.3.4 :)

@Narretz Narretz closed this as completed Nov 25, 2014
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.