Why isn't the validation errors in this custom Angular control visible from the parent...

Discussão em 'Angular' iniciado por James Skemp, Outubro 25, 2024.

  1. James Skemp

    James Skemp Guest

    I'm working on my first custom control in Angular (16), for a Reactive Form, that contains a slider and an input. If the slider is true, then the input should have a value. If it is not, then the input shouldn't have a value.

    Custom component HTML:

    <div class="benefit-field">
    <div class="slide-toggle">
    <mat-slide-toggle [checked]="slideEnabled" (change)="updateEnabled($event)">Enabled?</mat-slide-toggle>
    </div>
    <div [ngClass]="{'disabled': !slideEnabled}">
    <mat-form-field>
    <mat-label>{{ typeName }}</mat-label>
    <input type="text" matInput [value]="valueId" [disabled]="!slideEnabled" (change)="onValueChanged($event.target)" />
    </mat-form-field>
    </div>
    </div>


    Custom component TypeScript:

    import { Component, forwardRef, Input } from '@angular/core';
    import { AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms';
    import { MatSlideToggleChange } from '@angular/material/slide-toggle';

    @Component({
    selector: 'app-optional-benefit-field',
    templateUrl: './optional-benefit-field.component.html',
    styleUrls: ['./optional-benefit-field.component.scss'],
    providers: [
    {
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: OptionalBenefitFieldComponent
    },
    {
    provide: NG_VALIDATORS,
    multi: true,
    useExisting: forwardRef(() => OptionalBenefitFieldComponent)
    },
    ]
    })
    export class OptionalBenefitFieldComponent implements ControlValueAccessor, Validator {
    @Input() typeId: number;
    @Input() typeName: number;

    valueId: number;
    slideEnabled = false;

    onChange = (_valueId) => { /* nothing needs to be done here, let it trickle up */ };
    onTouched = () => { /* nothing needs to be done here, let it trickle up */ };
    touched = false;
    disabled = false;

    writeValue(valueId: number): void {
    if (valueId && valueId > 0) {
    this.slideEnabled = true;
    }
    this.valueId = valueId;
    }

    registerOnChange(onChange: any): void {
    this.onChange = onChange;
    }

    registerOnTouched(onTouched: any): void {
    this.onTouched = onTouched;
    }

    markAsTouched(): void {
    if (!this.touched) {
    this.onTouched();
    this.touched = true;
    }
    }

    setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
    }

    updateEnabled(event: MatSlideToggleChange): void {
    this.markAsTouched();
    this.slideEnabled = event.checked;
    if (!this.slideEnabled) {
    this.onChange(null);
    } else {
    this.onChange(this.valueId);
    }
    }

    onValueChanged(input: HTMLInputElement): void {
    if (input.value && (+input.value) > 0) {
    this.onChange(+input.value);
    } else {
    this.onChange(null);
    }
    }

    validate(control: AbstractControl): ValidationErrors | null {
    return (!this.slideEnabled || (this.slideEnabled && control.value))
    ? null
    : { required: true };
    }
    }


    Parent component HTML:

    <app-optional-benefit-field
    [typeId]="..."
    [typeName]="..."
    formControlName="valueId"
    ></app-optional-benefit-field>


    When I put the custom control into an invalid state, I can see that validate is returning a { required: true }.

    If I look at the parent form, however, .value has the current control's values but .errors is null.

    What am I missing in my custom control or parent component for it to properly send/receive the validation error?

    Continue reading...

Compartilhe esta Página