Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Commit

Permalink
feat: integrate with MatInput; support arbitrary showWhen
Browse files Browse the repository at this point in the history
  • Loading branch information
DmitryEfimenko committed Jan 9, 2022
1 parent 27e6bbf commit 55fd44a
Show file tree
Hide file tree
Showing 21 changed files with 555 additions and 93 deletions.
65 changes: 46 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ The design of this library promotes less boilerplate code, which keeps your temp

## Table of Contents

- [How it works](#how_it_works)
- [How it works](#how-it-works)
- [Installation](#installation)
- [Usage](#usage)
- [Advanced configuration](#configuration)
- [Handling form submission](#handling_form_submission)
- [Getting error details](#getting_error_details)
- [Handling form submission](#handling-form-submission)
- [Getting error details](#getting-error-details)
- [Styling](#styling)
- [Miscellaneous](#miscellaneous)
- [Development](#development)
Expand All @@ -51,8 +51,8 @@ For more info about this see [Advanced configuration](#configuration).

## Installation

* For Angular >= v13 use @ngspot/ngx-errors@3.x
* For Angular < v13 use @ngspot/ngx-errors@2.x
- For Angular >= v13 use @ngspot/ngx-errors@3.x
- For Angular < v13 use @ngspot/ngx-errors@2.x

### NPM

Expand Down Expand Up @@ -121,6 +121,24 @@ export class MyComponent implements OnInit {
}
```

### Use case with a template driven form control:

```ts
@Component({
selector: 'my-component',
template: `
<input [(ngModel)]="email" #emailModel="ngModel" required type="email" />
<div [ngxErrors]="emailModel.control">
<div ngxError="required">Email is required</div>
</div>
`,
})
export class MyComponent implements OnInit {
email: string;
}
```

## Configuration

Configure when to show messages for whole module by using `.configure()` method:
Expand Down Expand Up @@ -185,9 +203,7 @@ You can override the configuration specified at the module level by using `[show
This will be shown when control is dirty
</div>

<div ngxError="min">
This will be shown when control is touched and dirty
</div>
<div ngxError="min">This will be shown when control is touched and dirty</div>
</div>
```

Expand Down Expand Up @@ -245,53 +261,64 @@ Include something similar to the following in global CSS file:
ngx-errors library provides a couple of misc function that ease your work with forms.

### **dependentValidator**

Makes it easy to trigger validation on the control, that depends on a value of a different control

Example with using `FormBuilder`:

```ts
import { dependentValidator } from '@ngspot/ngx-errors';

export class LazyComponent {
constructor(fb: FormBuilder) {
this.form = fb.group({
password: ['', Validators.required],
confirmPassword: ['', dependentValidator<string>({
watchControl: f => f!.get('password')!,
validator: (passwordValue) => isEqualToValidator(passwordValue)
})],
confirmPassword: [
'',
dependentValidator<string>({
watchControl: (f) => f!.get('password')!,
validator: (passwordValue) => isEqualToValidator(passwordValue),
}),
],
});
}
}

function isEqualToValidator<T>(compareVal: T): ValidatorFn {
return function(control: AbstractControl): ValidationErrors | null {
return function (control: AbstractControl): ValidationErrors | null {
return control.value === compareVal
? null
: { match: { expected: compareVal, actual: control.value } };
}
};
}
```

The `dependentValidator` may also take `condition`. If provided, it needs to return true for the validator to be used.

```ts
const controlA = new FormControl('');
const controlB = new FormControl('', dependentValidator<string>({
watchControl: () => controlA,
validator: () => Validators.required,
condition: (val) => val === 'fire'
}));
const controlB = new FormControl(
'',
dependentValidator<string>({
watchControl: () => controlA,
validator: () => Validators.required,
condition: (val) => val === 'fire',
})
);
```

In the example above, the `controlB` will only be required when `controlA` value is `'fire'`

### **extractTouchedChanges**

As of today, the FormControl does not provide a way to subscribe to the changes of `touched` status. This function lets you do just that:

```ts
* const touchedChanged$ = extractTouchedChanges(formControl);
```

### **markDescendantsAsDirty**

As of today, the FormControl does not provide a way to mark the control and all its children as `dirty`. This function lets you do just that:

```ts
Expand Down
10 changes: 8 additions & 2 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@
"projects/playground/src/favicon.ico",
"projects/playground/src/assets"
],
"styles": ["projects/playground/src/styles.scss"],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"projects/playground/src/styles.scss"
],
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
Expand Down Expand Up @@ -122,7 +125,10 @@
"projects/playground/src/favicon.ico",
"projects/playground/src/assets"
],
"styles": ["projects/playground/src/styles.scss"],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"projects/playground/src/styles.scss"
],
"scripts": []
}
}
Expand Down
65 changes: 65 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
"private": true,
"dependencies": {
"@angular/animations": "~13.1.0",
"@angular/cdk": "^13.1.1",
"@angular/common": "~13.1.0",
"@angular/compiler": "~13.1.0",
"@angular/core": "~13.1.0",
"@angular/forms": "~13.1.0",
"@angular/material": "^13.1.1",
"@angular/platform-browser": "~13.1.0",
"@angular/platform-browser-dynamic": "~13.1.0",
"@angular/router": "~13.1.0",
Expand Down
12 changes: 12 additions & 0 deletions projects/ngx-errors/src/lib/custom-error-state-matchers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { InjectionToken } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';

export type CustomErrorStateMatchers = { [key: string]: ErrorStateMatcher };

/**
* Provides a way to add to available options for when to display an error for
* an invalid control. Options that come by default are
* `'touched'`, `'dirty'`, `'touchedAndDirty'`, `'formIsSubmitted'`.
*/
export const CUSTOM_ERROR_STATE_MATCHERS =
new InjectionToken<CustomErrorStateMatchers>('CUSTOM_ERROR_STATE_MATCHERS');
81 changes: 81 additions & 0 deletions projects/ngx-errors/src/lib/error-state-matchers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Inject, Injectable, Optional } from '@angular/core';
import { AbstractControl, FormGroupDirective, NgForm } from '@angular/forms';
import {
ErrorStateMatcher,
ShowOnDirtyErrorStateMatcher,
} from '@angular/material/core';
import {
CustomErrorStateMatchers,
CUSTOM_ERROR_STATE_MATCHERS,
} from './custom-error-state-matchers';

@Injectable({ providedIn: 'root' })
export class ErrorStateMatchers {
private matchers: { [key: string]: ErrorStateMatcher } = {};

constructor(
showOnTouchedErrorStateMatcher: ShowOnTouchedErrorStateMatcher,
showOnDirtyErrorStateMatcher: ShowOnDirtyErrorStateMatcher,
showOnTouchedAndDirtyErrorStateMatcher: ShowOnTouchedAndDirtyErrorStateMatcher,
showOnSubmittedErrorStateMatcher: ShowOnSubmittedErrorStateMatcher,
@Optional()
@Inject(CUSTOM_ERROR_STATE_MATCHERS)
customErrorStateMatchers: CustomErrorStateMatchers
) {
this.matchers['touched'] = showOnTouchedErrorStateMatcher;
this.matchers['dirty'] = showOnDirtyErrorStateMatcher;
this.matchers['touchedAndDirty'] = showOnTouchedAndDirtyErrorStateMatcher;
this.matchers['formIsSubmitted'] = showOnSubmittedErrorStateMatcher;
if (customErrorStateMatchers) {
this.matchers = { ...this.matchers, ...customErrorStateMatchers };
}
}

get(showWhen: string): ErrorStateMatcher | undefined {
return this.matchers[showWhen];
}

validKeys(): string[] {
return Object.keys(this.matchers);
}
}

@Injectable()
export class ShowOnTouchedErrorStateMatcher {
isErrorState(
control: AbstractControl | null,
form: FormGroupDirective | NgForm | null
): boolean {
return !!(
control &&
control.invalid &&
(control.touched || (form && form.submitted))
);
}
}

@Injectable()
export class ShowOnTouchedAndDirtyErrorStateMatcher
implements ErrorStateMatcher
{
isErrorState(
control: AbstractControl | null,
form: FormGroupDirective | NgForm | null
): boolean {
return !!(
control &&
control.invalid &&
((control.dirty && control.touched) || (form && form.submitted))
);
}
}

@Injectable()
export class ShowOnSubmittedErrorStateMatcher implements ErrorStateMatcher {
isErrorState(
control: AbstractControl | null,
form: FormGroupDirective | NgForm | null
): boolean {
return !!(control && control.invalid && form && form.submitted);
}
}
Loading

0 comments on commit 55fd44a

Please sign in to comment.