Is it possible to trigger statusChanges without triggering valueChanges on a reactive form control?
Asked Answered
P

2

7

I have a FormGroup with multiple nested groups and controls. Most of these contain validators such as Validators.required. Whenever the user clicks the submit button, I recursively loop through the form to make sure each validator is triggered and each control is valid. This is done by marking each control as touched and calling control.updateValueAndValidity().

However, this triggers both the valueChanged and statusChanged event. I have added subscribers to the statusChanged event which should handle any errors but I do not wish to trigger valueChanged since the value did not change.

Would it be possible to manually fire statusChanged without firing valueChanged? Passing { emitEvent: false } to updateValueAndValidity would prevent both events from being fired.

public ngOnInit(): void {
    const form = this.formBuilder.group({
        'name': ['', Validators.required],
        'address': this.formBuilder.group({
            'street': ['', Validators.required],
            'number': ['', Validators.required]
        })
    });

    form.valueChanges.subscribe(() => console.log('Should not fire'));
    form.statusChanges.subscribe(() => console.log('Should fire'));

    this.validateForm(form);

    console.log(form.valid);
}

private validateForm(control: AbstractControl): void {
    if (control instanceof FormControl) {
        control.markAsTouched();
        control.markAsDirty();
        control.updateValueAndValidity();
    } else if (control instanceof FormGroup) {
        Object.keys(control.controls).forEach(field => {
            this.validateForm(control.get(field));
        });
    }
}

// should print 'Should fire' and 'false' but not 'Should not fire'
Pathogenic answered 1/7, 2019 at 8:45 Comment(9)
why don't use Custom validators ? With validators, you just have to check global validation of your form.Keneth
@ThierryFalvo Could you give me an example which would make my code sample work? I don't quite understand how using a custom validator would solve the issue.Pathogenic
maybe I didn't clearly understand your need. Why is the goal of validateForm ? Custom validators as mentioned in Angular doc (link provided) give you the ability to check and validate each form control, and then no need to iterate on each field to check them manually.Keneth
@ThierryFalvo Because I have subscribed to statusChanges to listen for validation changes. Whenever it's triggered, I check for any validation errors and show them to the user. According to the angular docs this is "a multicasting observable that emits an event every time the validation status of the control recalculates", which seems perfect for what I used it for. If I didn't call validateForm, valueChanges would never be triggered and no validation messages would be shown.Pathogenic
I mean with custom validators you could avoid to do this, errors could be displayed inside template <div *ngIf="name.errors.forbiddenName">Name cannot be Bob.</div>. You just have to prevent submitting the form if invalid.Keneth
@ThierryFalvo For most simple validators that would be enough. However, I have several use cases where showing and hiding validators is more complex. For example, only a single validation messages may be shown at a time for below field. There's also a bunch more checks which I would like to do for validation messages which are more expensive. Putting checks like that in the template would make it harder to read, harder to maintain and expensive because they would be done every change cycle. I have a validation message component which listens to statusChanges and handles showing messages.Pathogenic
as you want. for your information, checks are not done inside template, but inside validators, multiples messages could be displayed for one field, and they are not more expensive than listen to valueChanges. Just to let you know. But you're way is not bad either.Keneth
@ThierryFalvo I probably wasn't clear enough. You're right when it comes to checks to determine whether the validator is valid or not. I was referring to checks to determine whether they are shown on the page or not. Any checks inside an ngIf directive are done every time a change happens, including calls to expensive functions. Thanks for your input though!Pathogenic
Let us continue this discussion in chat.Keneth
R
8

The statusChanges property is actually an EventEmitter, so you can cast it as such to emit the status manually:

(<EventEmitter<any>> control.statusChanges).emit(control.status);
Ritualize answered 23/1, 2020 at 8:9 Comment(2)
Please consider adding some explanation and details to your answer. While it might answer the question, just adding some code does not help OP or future community members understand the issue or solution.Eskew
Noted, I have added more details to the solution.Ritualize
A
-2

You can have code like this:

form.statusChanges.pipe(startWith("Invalid")).subscribe(() => console.log('Should fire'));
Acquiesce answered 9/9, 2022 at 7:51 Comment(1)
This will always emit 'Invalid' at firstTuneful

© 2022 - 2024 — McMap. All rights reserved.