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

Fix: set $submitted on child forms when form is submitted #15778

Merged
merged 2 commits into from
Dec 13, 2017

Conversation

Narretz
Copy link
Contributor

@Narretz Narretz commented Mar 6, 2017

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Fix

What is the current behavior? (You can also link to an open issue here)
When submitting a form, $submitted is not set on child forms

Does this PR introduce a breaking change?
I don't think so.
No one in #10071 said that his might be breaking.
AngularJS doesn't have nested form isolation, and ngForm is only for grouping sub-forms. I think that it's expected that a submission event will affect all sub-forms.
Probably: #11023 (comment)

Please check if the PR fulfills these requirements

Follow up to #11023

@googlebot
Copy link

So there's good news and bad news.

👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there.

😕 The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter. We need to confirm that they're okay with their commits being contributed to this project. Please have them confirm that here in the pull request.

Note to project maintainer: This is a terminal state, meaning the cla/google commit status will not change from this state. It's up to you to confirm consent of the commit author(s) and merge this pull request when appropriate.

@googlebot
Copy link

So there's good news and bad news.

👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there.

😕 The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter. We need to confirm that they're okay with their commits being contributed to this project. Please have them confirm that here in the pull request.

Note to project maintainer: This is a terminal state, meaning the cla/google commit status will not change from this state. It's up to you to confirm consent of the commit author(s) and merge this pull request when appropriate.

this.$$animate.addClass(this.$$element, SUBMITTED_CLASS);
this.$submitted = true;
this.$$parentForm.$setSubmitted();
$setSubmitted: function(onlySetOnChildren) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The onlySetOnChildren param name is misleading as it might imply that we don't set it on this :-)
How about something about not setting it on the parent?

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this param even necessary? I don't see it used anywhere?
Perhaps it would be best to leave it out for this PR?

Copy link
Contributor Author

@Narretz Narretz Mar 23, 2017

Choose a reason for hiding this comment

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

It's used in line 278 - we need it because if we always set it on both parent and children we can go into an infinite loop.
You're right with the parameter name. How about doNotSetOnParent?

Copy link
Contributor

Choose a reason for hiding this comment

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

oh right!
+1 on the new param name

Copy link
Member

Choose a reason for hiding this comment

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

This is another boolean trap in a public API, isn't it? :/

Copy link
Member

Choose a reason for hiding this comment

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

We have quite a lot of boolean parameters in our providers config methods but then they're not so bad as the convention is shared across the library and in all cases it just enables or disables the setting indicated by the method name.

Here there's no obvious relation between the parameter and the method name; it's impossible to know what's happening just by looking at the code. Naming the variable properly only helps us. Note, though, that negative parameters are confusing to people as when you set it to false you have double negation and it's been scientifically proven that it's harder for people to process.

If this is going to extent the public API, I think we should use an object with a parameter so that it's named (and then rather setOnParent than doNotSetOnParent). It's way easier to do it in ES6 but it's still possible in ES5.

Copy link
Contributor

Choose a reason for hiding this comment

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

The alternative is to create a new private function. Perhaps something like this:

$setSubmitted: function() {
  var rootForm = this;
  while(rootForm.$$parentForm) { rootForm = rootForm.$$parentForm; }
  rootForm.$$setSubmitted();
}

$$setSubmitted() {
  this.$$animate.addClass(this.$$element, SUBMITTED_CLASS);
  this.$submitted = true;
  forEach(this.$$controls, function(control) {
    if (control.$$setSubmitted && !control.$submitted) {
      control.$setSubmitted(false);
    }
  });
}

@Narretz
Copy link
Contributor Author

Narretz commented Mar 23, 2017

@petebacondarwin wdyt about the breaking-changeness of the PR? @gkalpak had some reservations in the previous PR: #11023 (comment)

this.$$animate.addClass(this.$$element, SUBMITTED_CLASS);
this.$submitted = true;
this.$$parentForm.$setSubmitted();
$setSubmitted: function(onlySetOnChildren) {
Copy link
Member

Choose a reason for hiding this comment

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

We have quite a lot of boolean parameters in our providers config methods but then they're not so bad as the convention is shared across the library and in all cases it just enables or disables the setting indicated by the method name.

Here there's no obvious relation between the parameter and the method name; it's impossible to know what's happening just by looking at the code. Naming the variable properly only helps us. Note, though, that negative parameters are confusing to people as when you set it to false you have double negation and it's been scientifically proven that it's harder for people to process.

If this is going to extent the public API, I think we should use an object with a parameter so that it's named (and then rather setOnParent than doNotSetOnParent). It's way easier to do it in ES6 but it's still possible in ES5.

@petebacondarwin
Copy link
Contributor

Regarding the BC - I guess that it is a breaking change. And this also raises the importance of being able to "detach" or "isolate" subforms from their parent. In which case you could block the child/parent form from being submitted if it was not "attached" to the form on which the setSubmit was called.

@Narretz Narretz modified the milestones: 1.6.4, 1.7.0 Mar 23, 2017
@Narretz
Copy link
Contributor Author

Narretz commented Mar 23, 2017

Ok, then let's put into 1.7.0 and rethink the API.

@aeslinger0
Copy link

I'm not sure why this needs to be a breaking change. I'm probably missing something. If the goal is to set child forms to submitted when the parent form is submitted, couldn't you add an attribute to ngForm such as inherit-submit="true" and use that as a flag to conditionally apply the new functionality? Since it'd be an optional attribute that results in the current behavior when omitted, I don't think it'd be breaking. Plus, it'd give you the ability to control form-by-form which subforms inherit the submit action.

@petebacondarwin
Copy link
Contributor

@aeslinger0 that is an interesting idea. What do you think @Narretz ?

@Narretz
Copy link
Contributor Author

Narretz commented Aug 17, 2017

Hm, I have to think about it, but in general the form API isn't very consistent as is, and I don't think an option that affects a single part of the API is very elegant.

@Narretz Narretz self-assigned this Nov 7, 2017
@Narretz Narretz force-pushed the feat-form-submitted branch from beb33a4 to 7787d00 Compare November 7, 2017 16:49
@Narretz
Copy link
Contributor Author

Narretz commented Nov 7, 2017

I've implemented @petebacondarwin 's approach to setting submitted.
Wrt the need for having a way to detach the form from its parent, here are a few notes on the consequences that would follow #10193 (comment)

Detaching the form is easy (just call $removeControl on the parent form), but the behavior that the detached form should have is not.

@Narretz
Copy link
Contributor Author

Narretz commented Dec 7, 2017

Here's a re-cap on what I think would be needed to support "detached" forms, i.e. a form that does not inherit or propagate state from / to its parent form.

Requirements:

  1. A way to detach / re-attach a form. This can be done with $removeControl on linking, and could be wrapped in a directive that watches an expression to toggle this behavior. (When detaching / re-attaching later, the state would only be propagated after the form changes again)

  2. To specify / implement the behavior of a detached form with regard to form submission.

Problems:
2a) the submit rules for forms (see https://code.angularjs.org/1.6.7/docs/api/ng/directive/form#submitting-a-form-and-preventing-the-default-action) also only apply to form elements. i.e. if you have ngForm with only one text input, pressing enter in the text input should trigger a submit event.

2b) only the form element fires a "submit" event when you press a submit button etc., ngForm does not. This means, if you want to submit detached forms individually, you would have to wrap forms in ngForm. That however would mean, you cannot submit the whole form.
Also, if you have form -> ngForm, and you press a submit button in the detached ngForm, the parent form will still receive the submit event.

Solution:
Imo can both 1) and 2) be implemented in user land - in fact, there are already libs that do 1) (although not perfect). For 2), a) is also already partly available (https://github.com/MarkPieszak/Angular1-ngForm-ngSubmit-fixes).
b) is more difficult, but should be possible by firing synthetic submit event on ngForm and overriding the form
submission handling with a function that checks if the submit event was triggered by an element that sits inside a detached ngForm (in which case it would not be fired on this form).

We should also update the docs with the info that in core ngForm, when used standalone, does not support form-specific submit handling.

Finally, I would introduce this PR as the default, without an option - if you have nested forms that are not detached, then I find it reasonable that the parent form sets $submitted on child forms.

@gkalpak
Copy link
Member

gkalpak commented Dec 7, 2017

I still don't like the idea of detached forms fwiw 😁

@Narretz
Copy link
Contributor Author

Narretz commented Dec 7, 2017

That's why I would move this whole topic to user-land. I agree that the semantics are unclear, it can become very complex very quickly, we don't know what kind of behavior devs actually want.
From my perspective, we only need to decide if this PR is breaking or not, or if we should make it configurable.

Copy link
Contributor

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

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

This PR looks good to me as it stands. Let's punt on detached forms for the moment.

@Narretz
Copy link
Contributor Author

Narretz commented Dec 11, 2017

Ok, so then I will squash the commits, add a BC notice and merge only into master.

this.$$animate.addClass(this.$$element, SUBMITTED_CLASS);
this.$submitted = true;
this.$$parentForm.$setSubmitted();
forEach(this.$$controls, function(control) {
if (control.$$setSubmitted && !control.$submitted) {
Copy link
Member

Choose a reason for hiding this comment

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

Why the !control.$submitted check? Under what circumstances can a sub-form be $submitted (while the parent form is not) and does that guarantee that all sub-forms of the sub-form are also $submitted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically, the form could have been removed with "removeControl", then have been changed to submitted via "setSubmitted" and re-attached to the parent form with "addControl". Unlikely, but possible. I don't think it's worth optimzing this, though. I think I took the implementation from this comment: #15778 (comment)

@Narretz Narretz force-pushed the feat-form-submitted branch 2 times, most recently from 118a547 to 6bf01af Compare December 12, 2017 11:48
…tted

Closes angular#10071

BREAKING CHANGE:

Forms will now set $submitted on child forms when they are submitted.
For example:
```
<form name="parentform" ng-submit="$ctrl.submit()">
  <ng-form name="childform">
    <input type="text" name="input" ng-model="my.model" />
  </ng-form>
  <input type="submit" />
</form>

Submitting this form will set $submitted on "parentform" and "childform".
Previously, it was only set on "parentform".

This change was introduced because mixing form and ngForm does not create
logically separate forms, but rather something like input groups.
Therefore, child forms should inherit the submission state from their parent form.
@Narretz Narretz force-pushed the feat-form-submitted branch from 44a6659 to e98bb35 Compare December 12, 2017 13:13
@Narretz Narretz merged commit f6e60c1 into angular:master Dec 13, 2017
@Narretz Narretz deleted the feat-form-submitted branch December 13, 2017 18:23
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.

6 participants