Skip to content
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

Unified Control State Change Events (FormControl should also support pristine/touched/untouched/valid events) #10887

Closed
btrauma8 opened this issue Aug 17, 2016 · 97 comments
Assignees
Labels
area: forms canonical This issue represents a canonical design issue in Angular. design complexity: major feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature forms: Controls API Issues related to AbstractControl, FormControl, FormGroup, FormArray. forms: ControlValueAccessor P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent state: Needs Design

Comments

@btrauma8
Copy link

btrauma8 commented Aug 17, 2016

Original report is below -- we have merged #42862 into this bug while organizing our issue tracker.

Which @angular/* package(s) are relevant/releated to the feature request?

forms

Description

The ability for AbstractControl-based classes (such as FormControl) to produce more events is a highly requested feature (lots of feedback in #10887, also #41088 and others; #24444 is also a use case). Adding more observables similar to the current valueChanges and statusChanges is the most straightforward way to extend AbstractControl, however, as the number of such observables increases (for example pristine, dirty, etc) it might become less ergonomic and more expensive to maintain. Additionally, if we wish to support the same state changes in ControlValueAccessor, we would be duplicating a lot of individual cases.

Proposed solution

We could introduce a new observable (and still support valueChanges and statusChanges for backwards compatibility) containing all events produced in various situations, so it's possible to subscribe, stream, and filter as needed. Each event in this stream might contain additional meta information such as a reference to an instance that produced the event, event type, etc. We would need a unified payload design that works with all the event types, and satisfies the use cases in the above linked issues.

However before making a final decision, we'd need to perform more research and design work, to make sure that this approach is viable.

Also note that adding this stream could simplify ControlValueAccessor, as per #24444 and #27315.

Alternatives considered

Adding individual observables on a case-by-case basis is likely infeasible, both from an API consistency perspective and an API bloat perspective.


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

[ ] bug report => search github for a similar issue or PR before submitting
[x ] 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
FormControl doesn't have an observable to subscribe to for when one of the following has changed:
pristine/touched/untouched/valid (well, okay, it does for valid, but i want to include the others)
statusChanges just tells me VALID or INVALID and only fires when valid changes.
i need to know when touched changed

Expected/desired behavior
either an observable for every one or one that combines them all...
fc.touchedChanges.subscribe(x => ....stuff);

What is the motivation / use case for changing the behavior?
The reason i want this is that i am making little form components and sending FormControl as input to one of them. I don't want a lot of logic in the component's html.
i would like to avoid <span *ngIf="myControl.valid && myControl.pristine || !myCointrol.blah ...
instead, i want: <span *ngIf="showWarning">
and showWarning could be set from a subscription in the component looking at pristine/touched etc

  • Angular version: 2.0.0-rc.5
@goldenbearkin
Copy link

goldenbearkin commented Aug 19, 2016

agree. say, if an input field gets focus and then lose focus without enter anything, the valueChanges/statusChanges won't fire event and therefore the validation.require warning got no way to capture.

@kara kara added the feature Issue that requests a new feature label Aug 26, 2016
@jvbianchi
Copy link

any news?

@ghost
Copy link

ghost commented Aug 2, 2017

related #17736

@magnattic
Copy link

It feels really awkward to work with forms right now when you are trying to follow a pure stream-based approach but still have to access properties like "dirty" in a synchronous manner. Observables would be greatly appreciated!

@ctrl-brk
Copy link

ctrl-brk commented Nov 2, 2017

Any progress? If you subscribe to the statusChanges and modify some control's value the observable will emit. But if you for instance call form.markAsPristine (even if it was dirty before) nothing comes out of the observable.

@dominique-mueller
Copy link

dominique-mueller commented Nov 15, 2017

Looking into the source code, it seems the effort to implement this isn't that huge. I could imagine two new Observables, for instance dirtyStateChanges and touchedStateChanges, both either emitting true or false when their state change.

Any opinions? Can the community support here?

@boazrymland
Copy link

As a temporary workaround to be able to listen to 'dirtiness' of a form, I have implemented the following simple pseudo-code:

Subscribe to valueChanges
// the "problem" with valueChanges is that the subscription handling code also fires during initial
// rendering of the form - and that's something we'd like to filter out, of course. So...:
In subscription handling code:
if (!this.yourForm.pristine) {
Only in this block do whatever you want. In my case, I needed to 'notify' another component
(not a child or parent) about this dirtiness situation.
}

@dgroh
Copy link

dgroh commented Dec 12, 2017

can someone post a proper solution for this and properly described?

@jsgoupil
Copy link

jsgoupil commented Dec 12, 2017

I have a temporary solution. Definitely sad to not see this in the framework, but I can't be waiting for it to merge anything. So I came up with a solution with unit tests on my side. Here is the code.
Before you jump with happiness, I reach for private stuff from Angular. So it can break at any moment.

type StatuChange = (property: string, previousValue: any, value: any) => void;
interface EmpoweredStatus {
    property: string;
    value: any;
    previousValue: any;
}

class EmpoweredFormControl<T extends AbstractControl> {
    private static readonly EMPOWER_KEY = '##empower##';
    private statusChangeHandlers: StatuChange[] = [];
    private empoweredStatusSubject = new Subject<EmpoweredStatus>();

    static getEmpoweredControl<T extends AbstractControl>(abstractControl: T): EmpoweredFormControl<T> {
        if (!abstractControl[EmpoweredFormControl.EMPOWER_KEY]) {
            abstractControl[EmpoweredFormControl.EMPOWER_KEY] = new EmpoweredFormControl(abstractControl);
        }

        return abstractControl[EmpoweredFormControl.EMPOWER_KEY];
    }

    static destroyEmpoweredControl<T extends AbstractControl>(abstractControl: T): void {
        if (abstractControl[EmpoweredFormControl.EMPOWER_KEY]) {
            delete abstractControl[EmpoweredFormControl.EMPOWER_KEY];
        }
    }

    private constructor(readonly abstractControl: T) {
        this.empowerAbstractControl(this.abstractControl);
    }

    destroy() {
        this.statusChangeHandlers = null;
        EmpoweredFormControl.destroyEmpoweredControl(this.abstractControl);
    }

    registerStatusChange(fn: StatuChange): void {
        this.statusChangeHandlers.push(fn);
    }

    get empoweredStatusChange(): Observable<EmpoweredStatus> {
        return this.empoweredStatusSubject.asObservable();
    }

    private change(property: string, previousValue: any, currentValue: any) {
        this.statusChangeHandlers.forEach(handler => {
            handler(property, previousValue, currentValue);
        });
    }

    private empowerAbstractControl(abstractControl: T) {
        ['touched', 'pristine'].forEach(property => {
            for (let baseProperty of ['_' + property, property]) {
                let propertyValue: any = abstractControl[baseProperty];
                if (propertyValue !== undefined) {
                    Object.defineProperty(abstractControl, baseProperty, {
                        get: () => propertyValue,
                        set: (value: any) => {
                            let previousValue = propertyValue;
                            propertyValue = value;
                            this.change(property, previousValue, propertyValue);
                        }
                    });
                    break;
                }
            }
        });

        this.registerStatusChange((property: string, previousValue: any, value: any) => {
            this.empoweredStatusSubject.next({
                property,
                value,
                previousValue
            });
        });
    }
}

@intellix
Copy link

intellix commented Jan 13, 2018

My use-case is that I created a FormErrorsComponent which displays error messages for a control. It looks something like:

<!-- some-form.component.html -->
<cw-form-errors [control]="form.controls.name" [force]="submitted"></cw-form-errors>

<!-- form-errors.component.html -->
<div class="invalid-feedback" *ngIf="control.invalid && (control.touched || force)">
  <div [hidden]="!control.errors[key]" *ngFor="let key of keys">{{ errors[key] }}</div>
  <ng-content></ng-content>
</div>

After adding ChangeDetectionStrategy.OnPush throughout my application, it no longer knows when control.touched has changed. My current workaround is to change force to show and duplicate that logic throughout, which isn't THAT bad:

<cw-form-errors [control]="form.controls.name" [show]="form.controls.name.touched || submitted"></cw-form-errors>

This issue has Design needed tag so guess we should at least discuss potentials here:

A state object containing each of the state booleans that gets emitted and can be checked:

interface FormState {
  dirty: boolean;
  pending: boolean;
  pristine: boolean;
  touched: boolean;
  untouched: boolean;
}
control.stateChanges: Observable<FormState>;

Or, like statusChanges and valueChanges, an Observable for each state individually:

control.dirtyChanges: Observable<boolean>;
control.dirtyChanges: Observable<boolean>;
control.pendingChanges: Observable<boolean>;
control.pristineChanges: Observable<boolean>;
control.touchedChanges: Observable<boolean>;
control.untouchedChanges: Observable<boolean>;

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

lucasbasquerotto commented Feb 27, 2018

Similar to @intellix, I have a component that show input errors, and because I can't listen the changes of touched and dirty through the control object I'm using ChangeDetectionStrategy.Default instead of ChangeDetectionStrategy.OnPush to be able to use it.

Among hundreds of components in the project, this is the only one with ChangeDetectionStrategy.Default because there is no other way to listen to the changes, aside from including some input property that would need to be passed whenever the component is used.

I hope that this issue be fixed soon. I don't see why it's taking too long. I think the 2 best options to do are either using a state object to be passed in the stateChanges or creating an observable for each state change, like is shown in the post above by @intellix. Is it too hard to implement some of the options above, or is it because this issue is not considered too important?

@seedy
Copy link

seedy commented Mar 21, 2018

This feature is taking quite some time to get designed. Any news?

@k-schneider
Copy link

k-schneider commented Mar 21, 2018

Wanted to drop a line and say I've bumped into this as well. Exactly the same use cases as the people before me. Using OnPush and wanting to have a structural directive that will hide/show errors but only after the control has been touched or the form has been submitted.

Ended up having to add a superficial input on the directive called 'touched' so that change detection would pick up on it. An easy hack to remove in the future should this ever be addressed.

@tommck
Copy link

tommck commented Mar 27, 2018

It seems really odd that, with all the performance improvements in Angular over AngularJS, we still don't have a way to listen for this instead of having to bind to function calls that get called on each digest cycle

@imadhy
Copy link

imadhy commented Jun 27, 2018

Any up on this issue ? It's really weird that we didn't get any news yet about this mandatory feature ?!

@emoriarty
Copy link

emoriarty commented Aug 21, 2018

One easy workaround until this feature will be a reality is to wrap the statusChanges subscription in a setTimeout function. This way the function body will get evaluated after the necessary time to update the FormControl/FormGroup/FormArray instance update the different status booleans (pristine/touched/untouched/dirty).

formGroup.statusChanges.subscribe(status => {
  console.log(formGroup.pristine); // Not changed
  setTimeout(() => {
    console.log(formGroup.pristine); // Changed
  }, 0);
});

In order to notify the pristine you can create a cold observable and pass changes within the setTimeout. I guess another option can be done using pipe and timeoutfunctions from rxjs.

I don't like this solution, personally. In an average running this works fine but in the end the functionality is relying on how the VM manages the process, so you could end up having random unexpected issues. Use it with care.

EDIT: as @lucasbasquerotto pointed out this workaround does not work completely. For pristine and dirty works fine but the touched untouched props are not properly updated.

@lucasbasquerotto
Copy link

lucasbasquerotto commented Aug 21, 2018

@emoriarty I don't think what you posted is a solution for this issue. The main problem I see is not about updating the FormControl/FormGroup/FormArray (to see an old or new value), but listening to it's changes (of course, your use case may be different).

The way it is currently, I can't listen to changes in the pristine or touched properties at all programatically in typescript, because the statusChanges observable doesn't emit any events, so subscribe is pointless there. I only achieve this by declaring them in the template (HTML).

Edit: I'm not completely sure about pristine, because it changes because of the control value changing, so it may trigger the statusChanges (and what you posted may work in this case), but in the case of touched it will not work.

@waterplea
Copy link
Contributor

Have to bump this too since no reply from the team was ever posted on this and it is a rather requested feature and a rather simple one as well. I'm with the guys above here, with my entire UI library using OnPush and that scoundrel FieldError component spoiling the party.

@montella1507
Copy link

Well, temporar workarround exists in creation of derived classes for *Control, FormGroup and FormBuilder... however, real solution should be very easy to implement, wonder why it isn't in reactive forms till start.

JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 15, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 23, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 23, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 23, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 24, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 25, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 26, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 27, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 27, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 27, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 27, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 27, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 28, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Mar 28, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Apr 1, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Apr 1, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
JeanMeche added a commit to JeanMeche/angular that referenced this issue Apr 1, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887
Feature Requests automation moved this from Needs Project Proposal to Closed Apr 3, 2024
@godspeed529
Copy link

godspeed529 commented Apr 3, 2024 via email

@ko1ebayev
Copy link

🫡

@flensrocker
Copy link

Is there a defined order in which the events are emitted if more than one thing changes? If a new value is entered, status and value events are emitted. What could I do to filter the valid values of the event stream? Is the value on the control uptodate when the status event fires so I can get it from the source?

I expect a signal based form integration in some future, which should fit better... Don't feel pushed. 🍻😎

I'm curious to experiment with this new event API.

@JeanMeche
Copy link
Member

JeanMeche commented Apr 3, 2024

@flensrocker We wanted to ship it early to get as mush feedback as possible before the final release. We appreciate any constructive feedback. Expect a release in the 18.0.0-next.3 version probably later today.

Btw your suggestion led to #55198 👍

JeanMeche added a commit to JeanMeche/angular that referenced this issue Apr 3, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887

PR Close angular#54579
JeanMeche added a commit to JeanMeche/angular that referenced this issue Apr 3, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887

PR Close angular#54579
ilirbeqirii pushed a commit to ilirbeqirii/angular that referenced this issue Apr 6, 2024
This commit introduces a new method to subscribe to on every `AbstractControl` subclass.
It allows to track value, pristine, touched and status changes on a given control.

Fixes angular#10887

PR Close angular#54579
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators May 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: forms canonical This issue represents a canonical design issue in Angular. design complexity: major feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature forms: Controls API Issues related to AbstractControl, FormControl, FormGroup, FormArray. forms: ControlValueAccessor P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent state: Needs Design
Projects
No open projects
Development

Successfully merging a pull request may close this issue.