I'm fairly new to custom form controls in Angular and have been struggling with setting required fields while using a mat-stepper. I am trying to make a reusable address template. I've set required in both HTML and Ts but when I click on the next button it moves to the next stepper. Any suggestions would be greatly appreciated.
Address Class Model
export declare class Address {
unitNumber: string;
streetName: string;
suburb: string;
city: string;
province: string;
postalCode: string;
}
Address TS
import { Component, ElementRef, HostBinding, Inject,
Input, OnDestroy, Optional, Self } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { Address, Helper, SelectOption } from '@trade-up/common';
import { Subject } from 'rxjs';
import { MatFormField, MatFormFieldControl, MAT_FORM_FIELD } from '@angular/material/form-field';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
@Component({
selector: 'app-address-form',
templateUrl: './address-form.component.html',
styleUrls: ['./address-form.component.scss'],
providers: [
{provide: MatFormFieldControl, useExisting: AddressFormComponent}
]
})
export class AddressFormComponent implements OnDestroy, ControlValueAccessor, MatFormFieldControl<Address> {
@HostBinding('attr.id')
externalId: string;
@HostBinding('class.floating')
get shouldLabelFloat(): boolean {
return this.focused || !this.empty;
}
@HostBinding('class.invalid')
get valid(): boolean {
return this.touched;
}
@Input()
set id(value: string) {
this._ID = value;
this.externalId = null;
}
get id(): string {
return this._ID;
}
provinces: SelectOption[] = Helper.provinces;
address: Address;
stateChanges = new Subject<void>();
/*tslint:disable-next-line*/
private _ID: string;
/*tslint:disable-next-line*/
private _placeholder: string;
/*tslint:disable-next-line*/
private _required: boolean;
/*tslint:disable-next-line*/
private _disabled: boolean;
focused: boolean;
errorState: boolean;
controlType?: string;
autofilled?: boolean;
userAriaDescribedBy?: string;
touched: boolean;
get empty(): boolean {
return !this.address.unitNumber && !this.address.streetName && !this.address.suburb
&& !this.address.province && !this.address.city && !this.address.postalCode;
}
@Input()
get placeholder(): string {
return this._placeholder;
}
set placeholder(plh) {
this._placeholder = plh;
this.stateChanges.next();
}
@Input()
get required(): boolean {
return this._required;
}
set required(req) {
this._required = coerceBooleanProperty(req);
this.stateChanges.next();
}
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(dis) {
this._disabled = coerceBooleanProperty(dis);
this.stateChanges.next();
}
onChange = () => {};
onTouched = () => {};
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
writeValue(add: Address): void {
this.address = add;
}
get value(): Address {
return this.address;
}
set value(add: Address) {
this.address = add;
this.stateChanges.next();
}
setDescribedByIds(ids: string[]): void {
this.userAriaDescribedBy = ids.join(' ');
}
onContainerClick(): void {
}
ngOnDestroy(): void {
this.stateChanges.complete();
}
/*tslint:disable-next-line*/
constructor(@Optional() @Self() public ngControl: NgControl, @Optional() @Inject(MAT_FORM_FIELD) public _formField: MatFormField,
/*tslint:disable-next-line*/
private _elementRef: ElementRef<HTMLElement>) {
if (ngControl !== null) {
ngControl.valueAccessor = this;
}
this.address = new Address();
}
}
Address Html
<div role="group" class="input-container"
[class.invalid]="valid"
[attr.aria-labelledby]="_formField?.getLabelId()">
<mat-form-field>
<mat-label>Unit Number</mat-label>
<input matInput
[(ngModel)]="address.unitNumber"
[class.invalid]="valid"
type="text"
maxlength="5"
required>
</mat-form-field>
<br>
<mat-form-field>
<input matInput
[(ngModel)]="address.streetName"
[class.invalid]="valid"
type="text"
placeholder="Street Name"
maxLength="45"
required>
</mat-form-field>
<br>
<mat-form-field>
<input matInput
[(ngModel)]="address.suburb"
[class.invalid]="valid"
type="text"
placeholder="Suburb"
maxLength="45"
required>
</mat-form-field>
<br>
<mat-form-field>
<mat-label>Province</mat-label>
<mat-select placeholder="Province"
[(ngModel)]="address.province"
[class.invalid]="valid"
required>
<mat-option *ngFor="let province of provinces" [value]="province.value">
{{province.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
<br>
<mat-form-field>
<input matInput
type="text"
[(ngModel)]="address.city"
[class.invalid]="valid"
placeholder="City"
maxLength="45"
required>
</mat-form-field>
<br>
<mat-form-field>
<input matInput
type="text"
[(ngModel)]="address.postalCode"
[class.invalid]="valid"
placeholder="Postal Code"
maxlength="4"
pattern="[0-9]*"
required>
</mat-form-field>
</div>
Collection Address TS
this.homeCollectionForm = this.formBuilder.group({
homeAddress: [new Address(), Validators.required],
});
Collection Address HTML
<mat-horizontal-stepper linear="true">
<mat-step [stepControl]="homeCollectionForm">
<form [formGroup]="homeCollectionForm">
<mat-card>
<mat-card-content>
<mat-form-field class="mat-form-field-center">
<app-address-form formControlName="homeAddress" required></app-address-form>
</mat-form-field>
<button
mat-raised-button
matStepperNext
color="warn">
Next
</button>
</mat-card-content>
</mat-card>
</form>
</mat-step>
</mat-horizontal-stepper>
make custom validator as below: