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

feat(ngModelOptions): Model update behavior can now be customized #6945

Closed
wants to merge 4 commits into from

Conversation

lrlopez
Copy link
Contributor

@lrlopez lrlopez commented Apr 1, 2014

By default, any change on the content will trigger an immediate model update and form validation. This PR implements a new directive ng-model-options that allow you to override this default behavior in several ways.

You should specify an object with the different parameters.

For example, it allows to trigger an update only when a particular event or list of events is received by the input using the updateOn key. Should you need multiple events, just use a space delimited string.

I.e. ng-model-options="{ updateOn: 'blur' }" will update and validate only after the control loses focus.

If you want to keep the default behavior and just add new events that may trigger the model update and validation, add "default" as one of the specified events.

I.e. ng-model-options="{ updateOn: 'default submit' }"

Also, with the debounce option, ng-model-options will allow deferring the actual model update until a timer expires. The timer will be reset each time an event is triggered.

I.e. `ng-model-options="{ debounce: 500 }" for 500ms after the latest event.

Custom timeouts for each event can be set for each event if you use an object in debounce. This can be useful to force immediate updates on some specific circumstances (like blur events).

I.e. ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0} }"

You can use the directive in any tag so its contents became the default settings for any child control, although they can be overridden.

Closes #1285, #2129

@mary-poppins
Copy link

Thanks for the PR! Please check the items below to help us merge this faster. See the contributing docs for more information.

  • Uses the issue template (#6945)

If you need to make changes to your pull request, you can update the commit with git commit --amend.
Then, update the pull request with git push -f.

Thanks again for your help!

@Narretz
Copy link
Contributor

Narretz commented Apr 2, 2014

What about setting $dirty etc. when there's a viewValue, but the model hasn't been set? Is this supported or is everything delayed?

@@ -181,6 +181,69 @@ This allows us to extend the above example with these features:



# Non-immediate (debounced) or custom triggered model updates
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a simpler title here? Perhaps even split out the two different features ("custom triggers" and "debounced updates") into two sections?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

@lrlopez
Copy link
Contributor Author

lrlopez commented Apr 2, 2014

@Narretz, everything is delayed until the model is set.

If you want to keep the default behavior and just add new events that may trigger the model update
and validation, add "default" as one of the specified events. I.e. `ng-model-options="{ updateOn: ["default", "blur"] }"`

You can delay the model update/validation by writing a `debounce` key. I.e. `ng-model-options="{ debounce: 500 }"` will wait for half a second since
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably make it clear that no update of any kind occurs till the debounce resolved. I.E. No parsers, nor validators are run and $pristine is not affected.

By default, any change on the content will trigger an immediate model update
and form validation. This PR implements a new directive `ng-model-options`
that allow you to override this default behavior in several ways.

You should specify an object with the different parameters.

For example, it allows to trigger an update only when a particular event or
list of events is received by the input using the `updateOn` key. Should you
need multiple events, just assign an array to it.

I.e. `ng-model-options="{ updateOn: 'blur' }"` will update and validate only
after the control loses focus.

If you want to keep the default behavior and just add new events that may
trigger the model update and validation, add "default" as one of the specified
events.

I.e. `ng-model-options="{ updateOn: ['default','submit'] }"`

Also, with the `debounce` option, `ng-model-options` will allow deferring the
actual model update until a timer expires. The timer will be reset each time
an event is triggered.

I.e. `ng-model-options="{ debounce: 500 }" for 500ms after the latest event.

Custom timeouts for each event can be set for each event if you use an object
in `debounce`. This can be useful to force immediate updates on some specific
circumstances (like blur events).

I.e. `ng-model-options="{ updateOn: ['default', 'blur'], debounce: { default: 500, blur: 0} }"`

You can use the directive in any tag so its contents became the default settings
for any child control, although they can be overridden.

Closes angular#1285, angular#2129
@lrlopez
Copy link
Contributor Author

lrlopez commented Apr 2, 2014

Thanks for the suggestions @caitp and @IgorMinar. They make the PR simpler. I've amended the commit.

* when a timer expires; this timer will be reset after another change takes place.
*
* @param {Object=} Object that contains options to apply to the current model. Valid keys are:
* - updateOn: string specifying which event should be the input bound to. If an array is supplied instead,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have to support list of events as an array? if we said that only space delimited strings are allowed ('blur click') then we wouldn't need forEach when registering listeners jquery/jqlite on support space delimited list of events and we could generally simplify quite a bit of code. (var events = options.$updateOn || options.$updateOnDefault; element.on(events, function() { ... });)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or even

element.on(options.$updateOn || options.$updateOnDefault, function() { ... });

@IgorMinar
Copy link
Contributor

ok, I know that I left a lot of comments. @lrlopez, I don't mean to discourage you. This is a really good change, it just needs a bit more polish and then we can ship it.

Good work! (also @petebacondarwin and @auser)

@donaldpipowitch
Copy link

Great feature 👍


element.on('keydown', function(event) {
var key = event.keyCode;
var deferListener = function(ev) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method takes the ev param but is not passed a param when called further down...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Corrected L958

@jeffbcross jeffbcross modified the milestones: 1.3.0-beta.6, 1.3.0-beta.5 Apr 3, 2014
@lrlopez
Copy link
Contributor Author

lrlopez commented Apr 3, 2014

Ok. This PR seems to be ready and got the green light from the AngularJS team. There is no hurry as we missed the beta5 deadline, so let's get this right. If anyone finds something wrong or has any proposal, I'll be glad to hear it.

At the end of the road, I'd like to publicly thank to @petebacondarwin for investing so much time in this PR. His help has been invaluable from the beginning, so kudos to him!

@IgorMinar
Copy link
Contributor

LGTM!

@petebacondarwin unless you know of some unresolved issues let's get this in.

thanks guys!

petebacondarwin pushed a commit that referenced this pull request Apr 4, 2014
By default, any change to an input will trigger an immediate model update,
form validation and run a $digest. This is not always desirable, especially
when you have a large number of bindings to update.

This PR implements a new directive `ngModelOptions`, which allow you to
override this default behavior in several ways. It is implemented as an
attribute, to which you pass an Angular expression, which evaluates to an
**options** object.

All inputs, using ngModel, will search for this directive in their ancestors
and use it if found.  This makes it easy to provide options for a whole
form or even the whole page, as well as specifying exceptions for
individual inputs.

* You can specify what events trigger an update to the model by providing
  an `updateOn` property on the **options** object. This property takes a
  string containing a space separated list of events.

  For example, `ng-model-options="{ updateOn: 'blur' }"` will update the
  model only after the input loses focus.

  There is a special pseudo-event, called "default", which maps to the
  default event used by the input box normally. This is useful if you
  want to keep the default behavior and just add new events.

* You can specify a debounce delay, how long to wait after the last triggering
  event before updating the model, by providing a `debounce` property on
  the **options** object.

  This property can be a simple number, the
  debounce delay for all events. For example,
  `ng-model-options="{ debounce: 500 }" will ensure the model is updated
  only when there has been a period 500ms since the last triggering event.

  The property can also be an object, where the keys map to events and
  the values are a corresponding debounce delay for that event.
  This can be useful to force immediate updates on some specific
  circumstances (like blur events). For example,
  `ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0} }"`

This commit also brings to an end one of the longest running Pull Requests
in the history of AngularJS (#2129)!  A testament to the patience of @lrlopez.

Closes #1285, #2129, #6945
@petebacondarwin
Copy link
Contributor

Landed!

dbe381f

Thanks @lrlopez and everyone else who has provided input into this new feature.

@shahata
Copy link
Contributor

shahata commented Apr 4, 2014

@lrlopez @petebacondarwin Sadly there are still issues with programmatic updates of the model:
http://plnkr.co/edit/C8IzuF?p=preview

  • In the first input, we have a debounced update. Try to start typing in the input and then click the clear button. The debounced is canceled alright, but the value in the input is not reset. :( This obviously happens because the model update did not reach the view since the model was not actually changed. Maybe this can be fixed by forcing a view update when debounce is canceled?
  • In the second input, we have an {updateOn: 'focus'}, so if we enter a value and then click the clear, the value also is not reset. This is essentially the same issue as the previous one, but here I don't have a good suggestion because we don't have anything like the $cancelDebounce() to indicate us to force the view change.

What do you think?

@petebacondarwin
Copy link
Contributor

Good spots @shahata . Can we start two new issues to deal with these?

@petebacondarwin
Copy link
Contributor

@shahata has created the issue at #6994

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Please provide option for binding ng-model for input onblur
9 participants