diff --git a/README.md b/README.md index 82bc071..a852b0f 100755 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ The `hasError` method informs you if your control has the given error. This can > Example: Adds `class="required"` when "myError" has the `required` error. ```html -
+
@@ -240,7 +240,7 @@ You can optionally pass in conditions in which to activate the error. > Example: Adds `class="required"` when "myError" has the `required` error _and_ the states are `'dirty'` and `'touched'`. ```html -
+
@@ -270,14 +270,67 @@ You can also use the "catch-all" selector to get the state of your entire contro
``` +#### isValid(name: string, conditions?: string | string[]): boolean; + +The `isValid` method informs you if a your control is valid, or a property is valid. This can be useful for styling elsewhere in your template based off the control's validity state. + +> Example: Adds `class="valid"` when "myError" has no `required` error. + +```html +
+ +
+ +
+
+ Field is required +
+
+``` + +You can optionally pass in conditions in which to evaluate alongside the property you're checking is valid. + +> Example: Adds `class="valid"` when "myError" has no `required` error _and_ the states are `'dirty'` and `'touched'`. + +```html +
+ +
+ +
+
+ Field is required +
+
+``` + +You can also use the "catch-all" selector to check if the control is valid, with no specific error properties, alongside on an optional state collection. + +```html +
+
+
+ +
+ +
+
+ Field is required +
+
+``` + #### hasErrors: boolean; The `hasErrors` property returns `true` if your control has any number of errors. This can be useful for styling elsewhere in your template on a global control level rather than individual errors. -> Example: Adds `class="hasErrors"` when "myError" has any errors. +> Example: Adds `class="errors"` when "myError" has any errors. ```html -
+
diff --git a/example/app/stock-inventory/components/stock-branch/stock-branch.component.ts b/example/app/stock-inventory/components/stock-branch/stock-branch.component.ts index 7615240..373aa6e 100755 --- a/example/app/stock-inventory/components/stock-branch/stock-branch.component.ts +++ b/example/app/stock-inventory/components/stock-branch/stock-branch.component.ts @@ -26,24 +26,24 @@ import { FormGroup } from '@angular/forms';

Errors: {{ myError.hasError('*', ['touched']) | json }}

-

No Errors: {{ !myError.hasError('*', ['touched']) | json }}

+

No Errors: {{ myError.isValid('required', ['dirty']) | json }}

+ [class.errors]="myError.hasError('*', ['dirty'])" + [class.no-errors]="myError.isValid('*', ['dirty'])">
-
+
Field is required
-
+
Min-length is {{ myError.getError('minlength')?.requiredLength }}
-
+
Max-length is {{ myError.getError('maxlength')?.requiredLength }}
diff --git a/src/ngxerrors.directive.ts b/src/ngxerrors.directive.ts index 4854f0b..9e1f577 100755 --- a/src/ngxerrors.directive.ts +++ b/src/ngxerrors.directive.ts @@ -1,4 +1,4 @@ -import { Directive, Input, OnChanges, AfterViewInit, SimpleChanges } from '@angular/core'; +import { Directive, Input, OnChanges, OnDestroy, AfterViewInit } from '@angular/core'; import { FormGroupDirective, AbstractControl } from '@angular/forms'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @@ -13,7 +13,7 @@ import { toArray } from './utils/toArray'; selector: '[ngxErrors]', exportAs: 'ngxErrors' }) -export class NgxErrorsDirective implements OnChanges, AfterViewInit { +export class NgxErrorsDirective implements OnChanges, OnDestroy, AfterViewInit { @Input('ngxErrors') controlName: string; @@ -23,10 +23,10 @@ export class NgxErrorsDirective implements OnChanges, AfterViewInit { control: AbstractControl; ready: boolean = false; - + constructor( private form: FormGroupDirective - ) {} + ) { } get errors() { if (!this.ready) return; @@ -37,17 +37,12 @@ export class NgxErrorsDirective implements OnChanges, AfterViewInit { return !!this.errors; } - checkControlProps(props: ErrorOptions) { - return !props ? true : toArray(props).every((prop: string) => this.control[prop]); - } - hasError(name: string, conditions: ErrorOptions): boolean { - if (!this.ready) return; - const controlPropsState = this.checkControlProps(conditions); - if (name.charAt(0) === '*') { - return this.control.invalid && controlPropsState; - } - return this.control.hasError(name) && controlPropsState; + return this.checkPropState('invalid', name, conditions); + } + + isValid(name: string, conditions: ErrorOptions): boolean { + return this.checkPropState('valid', name, conditions); } getError(name: string) { @@ -55,6 +50,33 @@ export class NgxErrorsDirective implements OnChanges, AfterViewInit { return this.control.getError(name); } + private checkPropState(prop: string, name: string, conditions: ErrorOptions): boolean { + if (!this.ready) return; + const controlPropsState = ( + !conditions || toArray(conditions).every((condition: string) => this.control[condition]) + ); + if (name.charAt(0) === '*') { + return this.control[prop] && controlPropsState; + } + return ( + prop === 'valid' ? !this.control.hasError(name) : this.control.hasError(name) && controlPropsState + ); + } + + private checkStatus() { + const control = this.control; + const errors = control.errors; + this.ready = true; + if (!errors) return; + for (const errorName in errors) { + this.subject.next({ control, errorName }); + } + } + + ngOnChanges() { + this.control = this.form.control.get(this.controlName); + } + ngAfterViewInit() { setTimeout(() => { this.checkStatus(); @@ -62,17 +84,8 @@ export class NgxErrorsDirective implements OnChanges, AfterViewInit { }); } - ngOnChanges(changes: SimpleChanges) { - this.control = this.form.control.get(changes.controlName.currentValue); - } - - checkStatus() { - const errors = this.control.errors; - this.ready = true; - if (!errors) return; - for (const error in errors) { - this.subject.next({ control: this.control, errorName: error }); - } + ngOnDestroy() { + this.subject.unsubscribe(); } } \ No newline at end of file diff --git a/src/test/ngxerrors.spec.ts b/src/test/ngxerrors.spec.ts index 35668cf..c0149ae 100755 --- a/src/test/ngxerrors.spec.ts +++ b/src/test/ngxerrors.spec.ts @@ -28,6 +28,8 @@ TestBed.initTestEnvironment(
{{ prop.errors | json }}
{{ prop.hasErrors | json }}
+
{{ prop.isValid('*', ['dirty']) | json }}
+
{{ prop.isValid('required', ['dirty']) | json }}
@@ -136,6 +138,9 @@ describe('Directives: ngxErrors, ngxError, when', () => { }); it('should provide a template ref API via ngxErrors exportAs', async (done) => { + + const parse = (name) => JSON.parse(el.query(By.css(name)).nativeElement.textContent); + await fixture.whenStable(); fixture.changeDetectorRef.markForCheck(); @@ -146,6 +151,8 @@ describe('Directives: ngxErrors, ngxError, when', () => { expect(element.nativeElement.classList.contains('requiredVisibleAtRuntime')).toBe(true); expect(element.nativeElement.classList.contains('requiredVisibleWhenDirty')).toBe(false); expect(element.nativeElement.classList.contains('requiredVisibleWhenDirtyTouched')).toBe(false); + expect(parse('.errorProp3')).toBe(false); + expect(parse('.errorProp4')).toBe(false); component.form.patchValue({ prop: 'ngxErrors' }); component.form.get('prop').markAsDirty(); @@ -154,12 +161,16 @@ describe('Directives: ngxErrors, ngxError, when', () => { await fixture.whenStable(); expect(component.form.get('prop').dirty).toBe(true); expect(component.form.get('prop').touched).toBe(true); + expect(parse('.errorProp3')).toBe(true); + expect(parse('.errorProp4')).toBe(true); expect(element.nativeElement.classList.contains('requiredVisibleAtRuntime')).toBe(false); component.form.patchValue({ prop: '' }); fixture.detectChanges(); await fixture.whenStable(); expect(element.nativeElement.classList.contains('requiredVisibleWhenDirty')).toBe(true); expect(element.nativeElement.classList.contains('requiredVisibleWhenDirtyTouched')).toBe(true); + expect(parse('.errorProp3')).toBe(false); + expect(parse('.errorProp4')).toBe(false); component.form.patchValue({ prop: 'ngx' }); fixture.detectChanges(); @@ -172,6 +183,8 @@ describe('Directives: ngxErrors, ngxError, when', () => { expect(component.form.get('prop').hasError('minlength')).toBe(true); expect(component.form.get('prop').hasError('maxlength')).toBe(false); expect(el.query(By.css('.errorMinLength')).nativeElement.textContent).toContain('5 characters minimum'); + expect(parse('.errorProp3')).toBe(false); + expect(parse('.errorProp4')).toBe(true); component.form.patchValue({ prop: 'ngxErrors!!!!!' }); fixture.detectChanges(); @@ -184,12 +197,12 @@ describe('Directives: ngxErrors, ngxError, when', () => { expect(component.form.get('prop').hasError('minlength')).toBe(false); expect(component.form.get('prop').hasError('maxlength')).toBe(true); expect(el.query(By.css('.errorMinLength')).nativeElement.textContent).toContain('10 characters maximum'); - - const parse = (name) => JSON.parse(el.query(By.css(name)).nativeElement.textContent); expect(parse('.errorProp1').maxlength.requiredLength).toBe(10); expect(parse('.errorProp1').maxlength.actualLength).toBe(14); expect(parse('.errorProp2')).toBe(true); + expect(parse('.errorProp3')).toBe(false); + expect(parse('.errorProp4')).toBe(true); done();