olefrank
olefrank

Reputation: 6810

Validation on Custom Angular Material Form Field

I'm trying to make a wrapper component for a color picker using ngx-color-picker and Angular Materials FormField. I configured it as required but it's outline doesn't turn red when invalid like the other form fields... How can I achieve this behavior on my custom form field?

color-picker.component.ts

import { Component, Input, forwardRef } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'

// inspiration:
// https://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html

export const requiredValidator = (c: FormControl): ValidationErrors | null => {
    return
    c.value === '' ||
    c.value === null ||
    c.value === undefined ||
    c.value.length === 0
        ? {
              required: {
                  valid: false,
              },
          }
        : null
}

@Component({
    selector: 'nda-color-picker',
    templateUrl: './color-picker.component.html',
    styleUrls: ['./color-picker.component.scss'],
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => ColorPickerComponent),
            multi: true,
        },
    ],
})
export class ColorPickerComponent implements ControlValueAccessor, Validator {
    @Input() label: string
    _color: string

    get color() {
        return this._color
    }
    set color(value: string) {
        this._color = value
        this.propagateChange(this._color)
    }

    constructor() {}

    writeValue(value: any) {
        if (value !== undefined) {
            this.color = value
        }
    }

    propagateChange = (_: any) => {}

    registerOnChange(fn) {
        this.propagateChange = fn
    }

    registerOnTouched() {}

    validate(control: AbstractControl): ValidationErrors | null {
        return requiredValidator(control as FormControl)
    }
}

color-picker.component.html

<mat-form-field appearance="outline" [floatLabel]="'always'">
    <mat-label>{{ label }}</mat-label>
    <input
        matTooltip="{{ color }}"
        cpOKButton="true"
        cpCancelButton="true"
        [required]="required"
        matInput
        [ngStyle]="{ color: color, background: color }"
        [colorPicker]="color"
        (colorPickerChange)="color = $event"
    />
</mat-form-field>

form-component.html

<form
    [formGroup]="transportForm"
    autocomplete="off"
    (ngSubmit)="handleSubmit()"
>A
    <div mat-dialog-content>
        <div fxLayout="row wrap" fxLayoutGap="1rem">
            <mat-form-field appearance="outline">
                <mat-label>Icon</mat-label>
                <input
                    matInput
                    placeholder="Icon"
                    formControlName="icon"
                    required
                />
            </mat-form-field>
            <nda-color-picker
                label="Badge Color Code"
                formControlName="badgeColorCode"
                required
            ></nda-color-picker>
            <nda-color-picker
                label="Text Color Code"
                formControlName="textColorCode"
                required
            ></nda-color-picker>
            <mat-form-field appearance="outline">
                <mat-label>Badge Type</mat-label>
                <input
                    matInput
                    placeholder="Badge Type"
                    formControlName="badgeType"
                />
            </mat-form-field>
        </div>
    </div>

    <!-- dialog actions -->
    <div mat-dialog-actions fxLayout="row" fxLayoutAlign="space-between center">
        <div>
            <button
                type="button"
                *ngIf="data && data.code"
                color="warn"
                mat-flat-button
                (click)="handleDelete()"
            >
                DELETE
            </button>
        </div>
        <div class="btn-container">
            <button type="reset" mat-stroked-button (click)="handleCancel()">
                CANCEL
            </button>
            <button
                mat-flat-button
                color="accent"
                [disabled]="transportForm.pristine"
            >
                SAVE
            </button>
        </div>
        <div
            *ngIf="!transportForm.valid"
            class="mat-small dialog-error-message"
        >
            * Fill out all required fields
        </div>
    </div>
</form>

------ UPDATE ------

I updated the color-picker.component with a validator function and class implementing Validator interface. The validator function is actually called and works as expected. But the form field isn't marked as invalid as reactive form fields normally are (css class .ng-invalid).

Upvotes: 1

Views: 2570

Answers (2)

Eliseo
Eliseo

Reputation: 57909

ibenjelloun, there a bug in your code. Replace your function get value() to return null if ratting equals 0 else if select an start and deselect don't give you error

get value(): number {
    let ratting= this.stars.reduce((total, starred) => {
      return total + (starred ? 1 : 0);
    }, 0);
    return ratting?ratting:null;
  }

Olefrank if you want that the control was who give the error add providers and create function validate, see the ibenjelloun forked stackblitz

Upvotes: 0

ibenjelloun
ibenjelloun

Reputation: 7713

You can use ReactiveFormsModule custom classes to set styles for different statuses :

.ng-invalid {
  border: 1px solid red;
}

You can find more documentation about ReactiveFormsModule css classes in angular.io...css-classes.

Upvotes: 1

Related Questions