angular icon indicating copy to clipboard operation
angular copied to clipboard

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

Open btrauma8 opened this issue 9 years ago • 71 comments

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

btrauma8 avatar Aug 17 '16 15:08 btrauma8

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.

goldenbearkin avatar Aug 19 '16 11:08 goldenbearkin

any news?

jvbianchi avatar Jun 01 '17 19:06 jvbianchi

related https://github.com/angular/angular/issues/17736

ghost avatar Aug 02 '17 20:08 ghost

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!

magnattic avatar Sep 17 '17 22:09 magnattic

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.

ctrl-brk avatar Nov 02 '17 23:11 ctrl-brk

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?

itsdevdom avatar Nov 15 '17 11:11 itsdevdom

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. }

boazrymland avatar Dec 10 '17 15:12 boazrymland

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

dgroh avatar Dec 12 '17 17:12 dgroh

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
            });
        });
    }
}

jsgoupil avatar Dec 12 '17 17:12 jsgoupil

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>;

intellix avatar Jan 13 '18 19:01 intellix

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?

lucasbasquerotto avatar Feb 27 '18 20:02 lucasbasquerotto

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

seedy avatar Mar 21 '18 13:03 seedy

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.

k-schneider avatar Mar 21 '18 23:03 k-schneider

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

tommck avatar Mar 27 '18 16:03 tommck

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

imadhy avatar Jun 27 '18 12:06 imadhy

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.

emoriarty avatar Aug 21 '18 10:08 emoriarty

@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.

lucasbasquerotto avatar Aug 21 '18 12:08 lucasbasquerotto

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.

waterplea avatar Sep 14 '18 09:09 waterplea

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.

montella1507 avatar Oct 30 '18 15:10 montella1507

@javix This issue is currently Open.

lucasbasquerotto avatar Dec 12 '18 10:12 lucasbasquerotto

+1

pjwalmsley avatar Jan 14 '19 16:01 pjwalmsley

+1

paddyfay avatar Jan 25 '19 13:01 paddyfay

@pjwalmsley , @antontemchenko , @paddyfay It is desired to avoid using comment for +1. Check about reaction here.

Reasons is you are spamming everybody that are subscribed to this thread.

MaklaCof avatar Jan 26 '19 15:01 MaklaCof

My use case is very similar to that of @intellix and @lucasbasquerotto , we have a similar validation message component that is associated with form controls.

Per our requirements, we have large forms (100+ controls) , which means 100+ validation message components recalculating their state every change detection cycle.

A 'touched' observable is the only missing piece that would allow us to convert the Change Detection strategy to OnPush and eliminate much of these unnecessary calls.

ramtennae avatar Feb 28 '19 16:02 ramtennae

That's definitely an useful extension for the forms API.

For those who are triggering valid, touched, untouched or pristine programmatically (for example through a form validator service) and would like to detect these events in other components without extending AbstractControl, this workaround could be used until an official implementation is provided:

Some generic Service / Component / Directive:

control.markAsTouched({ onlySelf: true }); // Or any other method which changes the control state.
(control.statusChanges as EventEmitter<any>).emit('TOUCHED'); // Access the underlying EventEmitter to emit custom status types.

Other specific component:

myControl.statusChanges.subscribe((status) => {
    if (status === 'TOUCHED') {
      /* Do something */
    }
});

Disclaimer: only use this workaround if you know and understand the implications of it.

almeidap avatar Apr 10 '19 15:04 almeidap

Without this feature composing nested FormControl is harder. Here is an example, I originally posted on here without an answer.

I build custom form controls by implementing ControlValueAccessor. I want primitive custom form controls to be composable and higher order form controls are based on those primitives.

Simple example:

So say I built a DateField custom from control, it only consists of an <input />.
I then build a Calendar custom from control, it's a calendar component with its own inherent complexity with a bunch of <div>s and <button>s as a template.
I then want to build a DatePicker custom from control composing a DateField and a Calendar components.

Inside my form controls, I need to handle updateOn options by properly implementing touched notifications.

For DateField it's easy, the example in the doc says it, just notify touched when input's blur.
For Calendar it's a bit less easy but doable by checking when blurring out of a button and the next focused item is not inside the Calendar.
So those two form controls can adhere to the updateOn options quite easily.

Let's now look at the DatePicker. It composes a DateField and a Calendar. So to properly handle touched notification I need to trigger it when the DateField and the Calendar are losing focus. I can't simply listen to change events because even if they bubble it would break the specific mechanism of touching provided by the Calendar custom form control.
As I compose those two form controls I have access to their FormControl models. The FormControl#touched property is handy but not useful in my case because I would need to 'listen' to touch changes but it seems their is no such event or stream.

I have not found a good way to achieve this using simple ControlValueAccessor implementations. So I come to creating a specialized Output on my custom form controls to let them trigger "touched" notifications but it's not satisfactory because if I share my customs form controls one has to learn how to use Angular forms and then learn my specialized Output and what it means.

sroucheray avatar May 09 '19 08:05 sroucheray

Oh look, another Angular issue many years out of date due to them implementing features such as ControlValueAccessor but not actually completing it. Have no idea how they made something like a custom form control so convoluted.

Emobe avatar Jul 04 '19 08:07 Emobe

Once change detection is done the form.touched will be turned into true, this is what I will be personally doing until this is added.

this.form.valueChanges.pipe(
  // We want to wait for change detection to be done as the touched is done the next CD round...
  // https://github.com/angular/angular/issues/10887
  //
  mergeMap(() => {
    return fromPromise(
      this.zone.onStable.pipe(take(1)).toPromise()
    );
  }),
  first(() => this.form.touched)
).subscribe(() => {
  // do stuff
});

Edit

This will only work if the value is changed.. Which isn't really touched :/

abdel-ships-it avatar Jul 29 '19 11:07 abdel-ships-it

Still waiting for an update...

akshaygolash avatar Aug 12 '19 20:08 akshaygolash

Still an issue for my team. We did alot of work putting a form into components. Hoping for a fix soon.

CarissaThomas avatar Sep 11 '19 20:09 CarissaThomas

Is this issue free to open a PR, or only the internal google team can make it?

kwiniarski97 avatar Sep 18 '19 17:09 kwiniarski97

@kwiniarski97 anyone can contribute, if nobody else is working on a fix, go for it 👍

ericmartinezr avatar Sep 18 '19 22:09 ericmartinezr

I also encountered this. My use case was styling a custom 'inner' control made of multiple inputs when invalid and touched. Calling form.markAllAsTouched() on the parent form sets the 'outer' control as touched, but then I couldn't find a good way of triggering touched = true on the custom 'inner' control itself. In the end I turned off view encapsulation on the custom control and styled it based on the 'outer' control having .ng-invalid.ng-touched.

adam-marshall avatar Sep 30 '19 15:09 adam-marshall

I need pendingCahnges event. In my case I'm disabling submit button when form is pending (checking for validality) but with ChangeDetection set to OnPush I cannot markForCheck when pending changes and therefor submitt button stays disabled :/

piernik avatar Oct 08 '19 07:10 piernik

For those interested, I've made a proposal (#31963) to update the ReactiveFormsModule such that you can subscribe to the changes of any property. It specifically allows for observing touched/submitted/disabled/readonly/valid etc properties via control.observe('touched') or control.observeChanges('valid'). It also addresses a whole slew of other problems.

You can learn more over in #31963. Feedback is most definitely welcomed!

jorroll avatar Oct 14 '19 14:10 jorroll

Lol never fails, anytime I run into a known Angular issue it always seems to be multiple years old with no clear direction from any contributors. Because it is open source I realize I have to adjust my expectations accordingly, but is this considered normal for a project of this size?

etay2000 avatar Oct 21 '19 20:10 etay2000

Had this same issue - put together this helper method to extract an observable which you can subscribe to in a form to be notified when touched status changes:

// Helper types

/**
 * Extract arguments of function
 */
export type ArgumentsType<F> = F extends (...args: infer A) => any ? A : never;

/**
 * Creates an object like O. Optionally provide minimum set of properties P which the objects must share to conform
 */
type ObjectLike<O extends object, P extends keyof O = keyof O> = Pick<O, P>;


 * Extract a touched changed observable from an abstract control
 * @param control AbstractControl like object with markAsTouched method
 */
export const extractTouchedChanges = (control: ObjectLike<AbstractControl, 'markAsTouched' | 'markAsUntouched'>): Observable<boolean> => {
  const prevMarkAsTouched = control.markAsTouched;
  const prevMarkAsUntouched = control.markAsUntouched;

  const touchedChanges$ = new Subject<boolean>();

  function nextMarkAsTouched(...args: ArgumentsType<AbstractControl['markAsTouched']>) {
    touchedChanges$.next(true);
    prevMarkAsTouched.bind(control)(...args);
  }

  function nextMarkAsUntouched(...args: ArgumentsType<AbstractControl['markAsUntouched']>) {
    touchedChanges$.next(false);
    prevMarkAsUntouched.bind(control)(...args);
  }
  
  control.markAsTouched = nextMarkAsTouched;
  control.markAsUntouched = nextMarkAsUntouched;

  return touchedChanges$;
}
// Usage (in component file)

...
    this.touchedChanged$ = extractTouchedChanges(this.form);
...

m-b-davis avatar Oct 29 '19 12:10 m-b-davis

@m-b-davis it works really well. Thanks! For reference, here is the Pristine/Dirty version I made from your Touched/Untouched:

import { AbstractControl } from '@angular/forms';
import { Observable, Subject } from 'rxjs';

// Helper types

/**
 * Extract arguments of function
 */
export type ArgumentsType<F> = F extends (...args: infer A) => any ? A : never;

/**
 * Creates an object like O. Optionally provide minimum set of properties P which the objects must share to conform
 */
type ObjectLike<O extends object, P extends keyof O = keyof O> = Pick<O, P>;


 /* Extract a pristine changed observable from an abstract control
 * @param control AbstractControl like object with markAsPristine method
 */
export const extractPristineChanges = (control: ObjectLike<AbstractControl, 'markAsPristine' | 'markAsDirty'>): Observable<boolean> => {
  const prevMarkAsPristine = control.markAsPristine;
  const prevMarkAsDirty = control.markAsDirty;

  const pristineChanges$ = new Subject<boolean>();

  function nextMarkAsPristine(...args: ArgumentsType<AbstractControl['markAsPristine']>) {
    pristineChanges$.next(true);
    prevMarkAsPristine.bind(control)(...args);
  }

  function nextMarkAsDirty(...args: ArgumentsType<AbstractControl['markAsDirty']>) {
    pristineChanges$.next(false);
    prevMarkAsDirty.bind(control)(...args);
  }

  control.markAsPristine = nextMarkAsPristine;
  control.markAsDirty = nextMarkAsDirty;

  return pristineChanges$;
}

And here is how I invoke it:

        extractPristineChanges(this.formGroup).pipe(
            takeUntil(this.destroyed$),
            startWith(true),
            delay(0),
            tap((isPristine: boolean) => {
                if (this.input.submitButton) {
                    this.input.submitButton.input.isDisabled = isPristine;
                }
            })
        ).subscribe();

FirstVertex avatar Dec 02 '19 17:12 FirstVertex

The nested ControlValueAccessor technique (https://github.com/angular/angular/issues/10887#issuecomment-490803771) is common: https://medium.com/angular-in-depth/angular-nested-reactive-forms-using-cvas-b394ba2e5d0d

Without touched events, there is no clean way to implement this. (It can be done, by hooking ngDoCheck()).

pauldraper avatar Dec 16 '19 17:12 pauldraper

Any updates on this?

haskelcurry avatar Sep 22 '20 13:09 haskelcurry

How can be possible that this very basic functionality has not yet been implemented? This feature request has been opened years ago back from v2.0.0.rc5!!

CesarD avatar Nov 02 '20 07:11 CesarD

How I picture the Angular team:

image

etay2000 avatar Nov 02 '20 13:11 etay2000

Stop crying here.. it will not help with anything...

A) do your own extension - it took 2hours to make my own B) use something like https://github.com/ngneat/reactive-forms

montella1507 avatar Nov 02 '20 13:11 montella1507

@montella1507 Lol great advice! How dare these developers come here to the issue tracker and report issues! I'm sure your 2 hour extension will solve everyone's issue and force everyone to realize that filing issues are for crybabies and that real developers like you develop their own solutions. You Angular apologists are funny. I'm starting to vue things differently now.

etay2000 avatar Nov 02 '20 13:11 etay2000

@montella1507 Lol great advice! How dare these developers come here to the issue tracker and report issues! I'm sure your 2 hour extension will solve everyone's issue and force everyone to realize that filing issues are for crybabies and that real developers like you develop their own solutions. You Angular apologists are funny. I'm starting to vue things differently now.

Your level of toxicity Is so high.

montella1507 avatar Nov 02 '20 13:11 montella1507

@montella1507 You literally started your post by saying "Stop crying here...". What kind of response did you expect? Did you think I was going to tell you how awesome I think you are because you had to develop your own third party solution because the Angular team doesn't care? You seem to vue the Angular team in a positive light, these days I don't know how most people will react to that.

etay2000 avatar Nov 02 '20 14:11 etay2000

@PapaNappa You are welcome to share your opinion, please elaborate, I don't know how to react to your thumbs down.

etay2000 avatar Nov 02 '20 14:11 etay2000

Have to agree with @etay2000, especially while #32806 is hanging for more than a year now...

LiadIdan avatar Nov 02 '20 14:11 LiadIdan

@montella1507 Can I get another reading on the Toxicity meter? How are my levels?

I was simply trying to:

Voice my opinion. Understand your point of view. Engage in conversation.

Please don't over-react.

etay2000 avatar Nov 02 '20 15:11 etay2000

Nice to see somebody is trying to silence everyone. You guys do know about the Streisand effect, do you?

Stop crying here.. it will not help with anything...

A) do your own extension - it took 2hours to make my own B) use something like https://github.com/ngneat/reactive-forms

Not helpful at all. First because this is so basic functionality, that it's ridiculous that Angular doesn't bring it out of the box. Second, because like @LiadIdan said (though, surprisingly, another hidden reply even when seems to be from a collaborator), there's been a PR (#32806) already submitted more than a year ago and it's still stuck there, despite someone took the effort to make it. So, if you're not going to help, at least don't speak nonsense.

CesarD avatar Nov 04 '20 11:11 CesarD

if somebody wants workaround: https://stackoverflow.com/a/65484672/6804292

spdi avatar Dec 28 '20 22:12 spdi

If you are using the Validator interface, you can cheat by calling outerControl.updateValueAndValidity(); right after you call outerControl.markAsTouched(); and inside your widget when using the function validate(outerControl: AbstractControl){} add if (outerControl.touched) this.innerControl.markAsTouched();

Rstar-37 avatar Feb 02 '21 22:02 Rstar-37

@waterplea There is solution with PR for that but unfortunately it is stuck on review. It passes tests , we need somehow to force them to approve this PR lol. https://github.com/angular/angular/pull/32806

vugar005 avatar Mar 08 '21 22:03 vugar005

We'd like to solve this together with the larger issue of high-quality events and payloads for all AbstractControl behaviors -- the problem extends beyond just pristine and friends. ~~We have opened #42862 to track that larger effort.~~

dylhunn avatar Jul 14 '21 23:07 dylhunn

@dylhunn this ticket says it will be tracked via #42862, but on that ticket you say this ticket is to be used (https://github.com/angular/angular/issues/42862#issuecomment-912128146).

I think that you may just want to delete/edit this comment to untangle the loop 😄 👊 : https://github.com/angular/angular/issues/10887#issuecomment-880284557

mdentremont avatar Dec 14 '21 15:12 mdentremont

@mdentremont I went ahead and struck through that line.

I've been pretty busy with typed forms recently, but this issue has still been rattling around in my head. I'm increasingly feeling that it's not a good idea to have one unified stream for all events, because people could depend on the event ordering, making it hard to extend in the future. Not sure exactly what the solution is yet.

dylhunn avatar Dec 14 '21 17:12 dylhunn

How about a (non-public?) unified stream, and getters for each of the event type streams that returns a lazily created filtered view of the unified stream?

johncrim avatar Dec 17 '21 17:12 johncrim

Hello from 2022. We still need this feature... Oh, why did I become an Angular developer? :(

vpetrusevici avatar Jan 25 '22 20:01 vpetrusevici

@vpetrusevici I am sure there can always be a reason to not like specific library/framework because of issue. For now I suggest to do this temporary without hacking. Pass onTouched event manually via input:

<form> 
 <child-form-component [isTouched]="myForm.get('address').touched"> </child-form-component>
</form

and onChild component

public onChanges(changes: SimpleChanges): void {
// do something onTouched changed.
}

But yes I agree it can be much better handled if form has a method for listening to those events without onChanges.

vugar005 avatar Jan 27 '22 20:01 vugar005

@vugar005 I needed this for touch on submit for nested controls (my custom control has own controls), so I created a service with Observable. I just trigger source on form submit and my component subsribed on this. But thanks, that's a good trick for the future

vpetrusevici avatar Jan 27 '22 21:01 vpetrusevici

So, the initial post refers to Angular 2.0.0rc5, is 6 years old and has 300+ thumbs up. How much interest does a issue need to get enough priority to be solved earlier than soonTM?

I see a handful of closed PRs, referencing each other stating others are superior. My vote goes to ANY solution, as long as it doesn't take more years of waiting and implementing hacky workarounds. :/

jbjhjm avatar Mar 24 '22 16:03 jbjhjm

@jbjhjm I feel your pain, but I really want to get this right in a way that will be extensible and consistent. A general-purpose design is trickier than it looks, and we need buy-in from the team to land the design change, since it will commit us to a direction for evolving CVAs.

In particular:

  • I strongly believe it's not enough to just add these events to the model classes -- we should really do the same for ControlValueAccessor. After all, what good is a FormControl event that doesn't also work with third party controls? It's a big can of worms though, since CVA is a very inflexible and confusing API.
  • We can't just add a bunch of new observables and CVA methods -- that would bloat the bundle, and be inflexible if we want new events in the future.
  • We can't break existing CVAs, of course, which is trickier than it looks when changing the CVA interface.

With 320 upvotes, this is currently the #2 issue for the forms package (behind typed forms), and the #5 issue for the framework as a whole. I'm currently fully occupied trying to land typed forms, but I am pushing to schedule this for the 14.x minors (although I cannot make promises until it's actually penciled in).

dylhunn avatar Mar 25 '22 23:03 dylhunn

@jbjhjm I feel your pain, but I really want to get this right in a way that will be extensible and consistent. A general-purpose design is trickier than it looks, and we need buy-in from the team to land the design change, since it will commit us to a direction for evolving CVAs.

In particular:

  • I strongly believe it's not enough to just add these events to the model classes -- we should really do the same for ControlValueAccessor. After all, what good is a FormControl event that doesn't also work with third party controls? It's a big can of worms though, since CVA is a very inflexible and confusing API.
  • We can't just add a bunch of new observables and CVA methods -- that would bloat the bundle, and be inflexible if we want new events in the future.
  • We can't break existing CVAs, of course, which is trickier than it looks when changing the CVA interface.

With 320 upvotes, this is currently the #2 issue for the forms package (behind typed forms), and the #5 issue for the framework as a whole. I'm currently fully occupied trying to land typed forms, but I am pushing to schedule this for the 14.x minors (although I cannot make promises until it's actually penciled in).

@dylhunn adding to this. we also need something similar to .valueChanges but one that is solely triggered by user input(or the controlValueAccessor). i know that there is setValue { emitEvent: false }. but it would be really nice to have a way to distinguish .valueChanges triggered by formControl api vs changes triggered by the controllValueAccessor.onChange

anavalaka avatar Apr 06 '22 13:04 anavalaka

I just encounter a situation, where I need a combination of statusChanges and valueChanges, but these two observables are nearly impossible to get in sync.

What I really would like to see is a unified observable with a value like (in respect to typed forms coming with v14)

interface FormChanges<TForm> {
  value: TForm;
  defaultValue: TForm;
  status: FormControlStatus;
  disabled: boolean;
  touched: boolean;
  dirty: boolean;
}

(pristine, enabled, untouched are just derived values from dirty, disabled, touched)

It's way easier to map some or a single property from a broader observable than to combine different observables into one, which are not emitting "perfectly" in sync.

With this it's easier to filter the "VALID" form values (e.g. for submitting a request).

The current statusChanges and valueChanges can be created from the "Uber Observable" so they can be kept for backwards compatibility.

statusChanges = this.formChanges.pipe(map(c => c.status), dictinctUntilChanged());
valueChanges = this.formChanges.pipe(map(c => c.value), dictinctUntilChanged());

flensrocker avatar May 31 '22 11:05 flensrocker