// app/custom-validation/customer-request.ts
export interface CustomerRequest {
id: number;
customerId: string;
date: Date;
message: string;
}
// app/custom-validation/not-in-year-validator.directive.ts
import { Directive, Input } from '@angular/core';
import {
AbstractControl,
NG_VALIDATORS,
ValidationErrors,
Validator,
ValidatorFn,
} from '@angular/forms';
export function notInYearValidator(notInYear: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) {
return null;
}
const date = control.value as Date;
return date.getFullYear() == notInYear
? { notInYear: { value: control.value } }
: null;
};
}
// IMPORTANT: The directive is not needed if you're using reactive forms
@Directive({
selector: '[appNotInYear]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: NotInYearValidatorDirective,
multi: true,
},
],
})
export class NotInYearValidatorDirective implements Validator {
@Input('appNotInYear')
notInYear?: number;
validate(control: AbstractControl): ValidationErrors | null {
return this.notInYear ? notInYearValidator(this.notInYear)(control) : null;
}
}
// app/custom-validation/valid-customer-id-validator.directive.ts
import { Directive } from '@angular/core';
import {
AbstractControl,
NG_VALIDATORS,
ValidationErrors,
Validator,
ValidatorFn,
} from '@angular/forms';
export function validCustomerIdValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) {
return null;
}
const valid = /^[A-Z]{2}[0-9]{4}$/.test(control.value);
return valid ? null : { validCustomerId: { value: control.value } };
};
}
// IMPORTANT: The directive is not needed if you're using reactive forms
@Directive({
selector: '[appValidCustomerId]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: ValidCustomerIdValidatorDirective,
multi: true,
},
],
})
export class ValidCustomerIdValidatorDirective implements Validator {
validate(control: AbstractControl): ValidationErrors | null {
return validCustomerIdValidator()(control);
}
}
<!-- app/custom-validation/customer-request-form-ngxs/customer-request-form-ngxs.component.html -->
<h1>Customer Request (NGXS)</h1>
<mat-card>
<form (ngSubmit)="submit()" #customerRequestForm="ngForm">
<mat-form-field>
<mat-label>Customer ID</mat-label>
<input
matInput
required
appValidCustomerId
name="customerId"
[ngModel]="(customerRequest$ | async)?.customerId"
(ngModelChange)="setCustomerId($event)"
#customerId="ngModel"
/>
<mat-error *ngIf="customerId.errors?.required"
>You must enter a value</mat-error
>
<mat-error *ngIf="customerId.errors?.validCustomerId"
>The ID must start with two uppercase letters followed by four
numbers</mat-error
>
</mat-form-field>
<mat-form-field>
<mat-label>Date</mat-label>
<input
matInput
required
[appNotInYear]="2020"
name="date"
[matDatepicker]="picker"
[ngModel]="(customerRequest$ | async)?.date"
(ngModelChange)="setDate($event)"
#date="ngModel"
/>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
<mat-error *ngIf="date.errors?.required"
>You must enter a value</mat-error
>
<mat-error *ngIf="date.errors?.notInYear"
>The date cannot be in 2020</mat-error
>
</mat-form-field>
<mat-form-field>
<mat-label>Message</mat-label>
<textarea
matInput
required
name="message"
[ngModel]="(customerRequest$ | async)?.message"
(ngModelChange)="setMessage($event)"
#message="ngModel"
></textarea>
<mat-error *ngIf="message.errors?.required"
>You must enter a value</mat-error
>
</mat-form-field>
<div class="form-buttons">
<button
mat-raised-button
type="submit"
color="primary"
[disabled]="(loading$ | async) || !customerRequestForm.form.valid"
>
Submit
</button>
</div>
</form>
</mat-card>
<h2>Customer Request (JSON)</h2>
<pre
>{{ customerRequest$ | async | json }}
</pre>
// app/custom-validation/customer-request-form-ngxs/customer-request-form-ngxs.component.ts
import { Component } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';
import { CustomerRequest } from 'src/app/custom-validation/customer-request';
import { CustomerRequestFormNgxs } from 'src/app/custom-validation/customer-request-form-ngxs/customer-request-form-ngxs.actions';
import { CustomerRequestFormNgxsSelectors } from 'src/app/custom-validation/customer-request-form-ngxs/customer-request-form-ngxs.selectors';
@Component({
selector: 'app-customer-request-form-ngxs',
templateUrl: './customer-request-form-ngxs.component.html',
})
export class CustomerRequestFormNgxsComponent {
@Select(CustomerRequestFormNgxsSelectors.customerRequest)
customerRequest$!: Observable<CustomerRequest>;
@Select(CustomerRequestFormNgxsSelectors.loading)
loading$!: Observable<boolean>;
constructor(
private readonly store: Store,
private readonly snackBar: MatSnackBar
) {}
setCustomerId(customerId: string) {
this.store.dispatch(new CustomerRequestFormNgxs.SetCustomerId(customerId));
}
setDate(date: Date) {
this.store.dispatch(new CustomerRequestFormNgxs.SetDate(date));
}
setMessage(message: string) {
this.store.dispatch(new CustomerRequestFormNgxs.SetMessage(message));
}
submit(): void {
this.store
.dispatch(new CustomerRequestFormNgxs.Submit())
.pipe(withLatestFrom(this.customerRequest$))
.subscribe(([, { id }]) => {
this.snackBar.open(`Customer request saved with ID #${id}`, 'Close');
});
}
}
// app/custom-validation/customer-request.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { CustomerRequest } from 'src/app/custom-validation/customer-request';
export interface CustomerRequestSaveResponse {
id: number;
}
@Injectable({
providedIn: 'root',
})
export class CustomerRequestService {
save(
customerRequest: CustomerRequest
): Observable<CustomerRequestSaveResponse> {
console.log('Save:', customerRequest);
return of({
id: 1,
}).pipe(delay(1000));
}
}
// app/custom-validation/customer-request-form-ngxs/customer-request-form-ngxs.state.ts
import { Injectable } from '@angular/core';
import { Action, State, StateContext } from '@ngxs/store';
import { finalize, tap } from 'rxjs/operators';
import { CustomerRequest } from 'src/app/custom-validation/customer-request';
import { CustomerRequestFormNgxs } from 'src/app/custom-validation/customer-request-form-ngxs/customer-request-form-ngxs.actions';
import { CustomerRequestService } from 'src/app/custom-validation/customer-request.service';
export interface CustomerRequestFormNgxsStateModel {
customerRequest: CustomerRequest;
loading: boolean;
}
@State<CustomerRequestFormNgxsStateModel>({
name: 'customerRequestFormNgxs',
defaults: {
customerRequest: {
id: 0,
customerId: '',
date: new Date(),
message: '',
},
loading: false,
},
})
@Injectable()
export class CustomerRequestFormNgxsState {
constructor(
private readonly customerRequestService: CustomerRequestService
) {}
@Action(CustomerRequestFormNgxs.SetCustomerId)
setCustomerId(
context: StateContext<CustomerRequestFormNgxsStateModel>,
{ customerId }: CustomerRequestFormNgxs.SetCustomerId
) {
const state = context.getState();
context.patchState({
customerRequest: { ...state.customerRequest, customerId },
});
}
@Action(CustomerRequestFormNgxs.SetDate)
setDate(
context: StateContext<CustomerRequestFormNgxsStateModel>,
{ date }: CustomerRequestFormNgxs.SetDate
) {
const state = context.getState();
context.patchState({
customerRequest: { ...state.customerRequest, date },
});
}
@Action(CustomerRequestFormNgxs.SetMessage)
setMessage(
context: StateContext<CustomerRequestFormNgxsStateModel>,
{ message }: CustomerRequestFormNgxs.SetMessage
) {
const state = context.getState();
context.patchState({
customerRequest: { ...state.customerRequest, message },
});
}
@Action(CustomerRequestFormNgxs.Submit)
submit(context: StateContext<CustomerRequestFormNgxsStateModel>) {
const state = context.getState();
context.patchState({
loading: true,
});
return this.customerRequestService.save(state.customerRequest).pipe(
tap(({ id }) =>
context.patchState({
customerRequest: {
...state.customerRequest,
id,
},
})
),
finalize(() =>
context.patchState({
loading: false,
})
)
);
}
}
// app/custom-validation/customer-request-form-ngxs/customer-request-form-ngxs.actions.ts
export namespace CustomerRequestFormNgxs {
export class SetCustomerId {
static readonly type = '[CustomerRequestFormNgxs] SetCustomerId';
constructor(readonly customerId: string) {}
}
export class SetDate {
static readonly type = '[CustomerRequestFormNgxs] SetDate';
constructor(readonly date: Date) {}
}
export class SetMessage {
static readonly type = '[CustomerRequestFormNgxs] SetMessage';
constructor(readonly message: string) {}
}
export class Submit {
static readonly type = '[CustomerRequestFormNgxs] Submit';
}
}
// app/custom-validation/customer-request-form-ngxs/customer-request-form-ngxs.selectors.ts
import { Selector } from '@ngxs/store';
import { CustomerRequest } from 'src/app/custom-validation/customer-request';
import {
CustomerRequestFormNgxsState,
CustomerRequestFormNgxsStateModel,
} from 'src/app/custom-validation/customer-request-form-ngxs/customer-request-form-ngxs.state';
export class CustomerRequestFormNgxsSelectors {
@Selector([CustomerRequestFormNgxsState])
static customerRequest({
customerRequest,
}: CustomerRequestFormNgxsStateModel): CustomerRequest {
return customerRequest;
}
@Selector([CustomerRequestFormNgxsState])
static loading({ loading }: CustomerRequestFormNgxsStateModel): boolean {
return loading;
}
}