diff --git a/angular.json b/angular.json index f9a82963128d48ae85b6f328e552f85630325107..0348f3534bccf87ab13ea65efaea6cde6b3cf21d 100644 --- a/angular.json +++ b/angular.json @@ -420,6 +420,34 @@ "styleext": "scss" } } + }, + "utils-form": { + "projectType": "library", + "root": "libs/utils/form", + "sourceRoot": "libs/utils/form/src", + "prefix": "ut", + "architect": { + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": ["libs/utils/form/tsconfig.lib.json", "libs/utils/form/tsconfig.spec.json"], + "exclude": ["**/node_modules/**", "!libs/utils/form/**"] + } + }, + "test": { + "builder": "@nrwl/jest:jest", + "options": { + "jestConfig": "libs/utils/form/jest.config.js", + "tsConfig": "libs/utils/form/tsconfig.spec.json", + "setupFile": "libs/utils/form/src/test-setup.ts" + } + } + }, + "schematics": { + "@nrwl/angular:component": { + "styleext": "scss" + } + } } }, "cli": { diff --git a/libs/utils/form/README.md b/libs/utils/form/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ee861b1d15ca69457f4b8e126fd172ce72c9aea9 --- /dev/null +++ b/libs/utils/form/README.md @@ -0,0 +1,7 @@ +# utils-form + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test utils-form` to execute the unit tests. diff --git a/libs/utils/form/jest.config.js b/libs/utils/form/jest.config.js new file mode 100644 index 0000000000000000000000000000000000000000..0be2df24878549c688793173f3e4eb31f1edd1f5 --- /dev/null +++ b/libs/utils/form/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + name: 'utils-form', + preset: '../../../jest.config.js', + coverageDirectory: '../../../coverage/libs/utils/form', + snapshotSerializers: [ + 'jest-preset-angular/AngularSnapshotSerializer.js', + 'jest-preset-angular/HTMLCommentSerializer.js' + ] +}; diff --git a/libs/utils/form/ng-package.json b/libs/utils/form/ng-package.json new file mode 100644 index 0000000000000000000000000000000000000000..c781f0df4675ef304c7d745c98df9f59daa25dee --- /dev/null +++ b/libs/utils/form/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/utils/form/src/index.ts b/libs/utils/form/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..40331ab888e061ece17ae3c97ece08d507d82dd6 --- /dev/null +++ b/libs/utils/form/src/index.ts @@ -0,0 +1,3 @@ +export * from './lib/disable-control/disable-control.directive'; +export * from './lib/disable-control/disable-control.module'; +export * from './lib/utils/form.utils'; diff --git a/libs/utils/form/src/lib/disable-control/disable-control.directive.spec.ts b/libs/utils/form/src/lib/disable-control/disable-control.directive.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..64e04be6491c759a531f19e86a18f1c261692ef6 --- /dev/null +++ b/libs/utils/form/src/lib/disable-control/disable-control.directive.spec.ts @@ -0,0 +1,57 @@ +import { Component, Input } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { DisableControlDirective } from './disable-control.directive'; + +@Component({ + template: ` + + ` +}) +class TestComponent { + @Input() + public disabled: boolean; + public control: FormControl; + + constructor() { + this.control = new FormControl(); + } +} + +describe('DisableControlDirective', () => { + let fixture: ComponentFixture; + let component: TestComponent; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule], + declarations: [TestComponent, DisableControlDirective] + }); + + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeDefined(); + }); + + function getInput(): HTMLInputElement { + return fixture.debugElement.query(By.css('input')).nativeElement; + } + + it('should disable input when disabled is true', () => { + component.disabled = true; + fixture.detectChanges(); + expect(component.control.disabled).toBe(true); + expect(getInput().disabled).toBe(true); + }); + + it('should enable input when disabled is false', () => { + component.disabled = false; + fixture.detectChanges(); + expect(component.control.disabled).toBe(false); + expect(getInput().disabled).toBe(false); + }); +}); diff --git a/libs/utils/form/src/lib/disable-control/disable-control.directive.ts b/libs/utils/form/src/lib/disable-control/disable-control.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..2bb4eafaeb7112057b80b6fb991f1194852ad5e9 --- /dev/null +++ b/libs/utils/form/src/lib/disable-control/disable-control.directive.ts @@ -0,0 +1,15 @@ +import { Directive, Input } from '@angular/core'; +import { NgControl } from '@angular/forms'; + +@Directive({ + selector: '[utDisableControl]' +}) +export class DisableControlDirective { + constructor(private ngControl: NgControl) {} + + @Input('utDisableControl') + public set disableControl(condition: boolean) { + const action = !!condition ? 'disable' : 'enable'; + this.ngControl.control[action](); + } +} diff --git a/libs/utils/form/src/lib/disable-control/disable-control.module.ts b/libs/utils/form/src/lib/disable-control/disable-control.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..c43b914d74423931bd8618aaf8f2dbfbb7603fb2 --- /dev/null +++ b/libs/utils/form/src/lib/disable-control/disable-control.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { DisableControlDirective } from './disable-control.directive'; + +@NgModule({ + imports: [CommonModule, FormsModule, ReactiveFormsModule], + declarations: [DisableControlDirective], + exports: [DisableControlDirective] +}) +export class DisableControlModule {} diff --git a/libs/utils/form/src/lib/utils/form.utils.spec.ts b/libs/utils/form/src/lib/utils/form.utils.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4137157125da262c2fab3f36a9b65a895b7f2040 --- /dev/null +++ b/libs/utils/form/src/lib/utils/form.utils.spec.ts @@ -0,0 +1,17 @@ +import { FormUtils } from './form.utils'; + +describe('FormUtils', () => { + describe('safeTrim', () => { + it('should return original value when value is falsy', () => { + expect(FormUtils.safeTrim(undefined)).toBeUndefined(); + }); + + it('should return original value when value has no whitespace', () => { + expect(FormUtils.safeTrim('me')).toBe('me'); + }); + + it('should trim leading and trailing whitespace', () => { + expect(FormUtils.safeTrim(' some ')).toBe('some'); + }); + }); +}); diff --git a/libs/utils/form/src/lib/utils/form.utils.ts b/libs/utils/form/src/lib/utils/form.utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..548043dba791c9b8863ddc1beebfdd74c6e03ecd --- /dev/null +++ b/libs/utils/form/src/lib/utils/form.utils.ts @@ -0,0 +1,23 @@ +import { FormGroup, AbstractControl, FormArray } from '@angular/forms'; + +export class FormUtils { + public static touchAllFields(group: FormGroup): void { + Object.keys(group.controls).forEach(controlName => { + const control: AbstractControl = group.get(controlName); + if (control instanceof FormArray) { + const arr = control as FormArray; + for (let i = 0; i < arr.length; ++i) { + FormUtils.touchAllFields(arr.at(i) as FormGroup); + } + } else if (control instanceof FormGroup) { + FormUtils.touchAllFields(control); + } else { + control.markAsTouched({ onlySelf: true }); + } + }); + } + + public static safeTrim(value: string): string { + return value ? value.trim() : value; + } +} diff --git a/libs/utils/form/src/test-setup.ts b/libs/utils/form/src/test-setup.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea6fbefa5107bf0742b20f740fe208d15657248b --- /dev/null +++ b/libs/utils/form/src/test-setup.ts @@ -0,0 +1,2 @@ +import 'jest-preset-angular'; +import '../../../../jestGlobalMocks'; diff --git a/libs/utils/form/tsconfig.json b/libs/utils/form/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..08c7db8c96729fc7e5d208344835a678c973f96c --- /dev/null +++ b/libs/utils/form/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": ["**/*.ts"] +} diff --git a/libs/utils/form/tsconfig.lib.json b/libs/utils/form/tsconfig.lib.json new file mode 100644 index 0000000000000000000000000000000000000000..1c600457d394acbbc693cc87f480a170a4b277cd --- /dev/null +++ b/libs/utils/form/tsconfig.lib.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "target": "es2015", + "declaration": true, + "inlineSources": true, + "types": [], + "lib": ["dom", "es2018"] + }, + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "skipTemplateCodegen": true, + "strictMetadataEmit": true, + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true, + "enableResourceInlining": true + }, + "exclude": ["src/test-setup.ts", "**/*.spec.ts"] +} diff --git a/libs/utils/form/tsconfig.spec.json b/libs/utils/form/tsconfig.spec.json new file mode 100644 index 0000000000000000000000000000000000000000..fd405a65ef42fc2a9dece7054ce3338c0195210b --- /dev/null +++ b/libs/utils/form/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["**/*.spec.ts", "**/*.d.ts"] +} diff --git a/libs/utils/form/tslint.json b/libs/utils/form/tslint.json new file mode 100644 index 0000000000000000000000000000000000000000..5c1c5f0ecc3b54c086b81dbe1161948f212a4b49 --- /dev/null +++ b/libs/utils/form/tslint.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tslint.json", + "rules": { + "directive-selector": [true, "attribute", "ut", "camelCase"], + "component-selector": [true, "element", "ut", "kebab-case"] + } +} diff --git a/nx.json b/nx.json index 5da35188642b4b2c478d04548b36e289022571fd..21c89845f907de45bddf5fcda9e3b845683d0b9c 100644 --- a/nx.json +++ b/nx.json @@ -49,6 +49,9 @@ }, "utils-cdn": { "tags": [] + }, + "utils-form": { + "tags": [] } } } diff --git a/tsconfig.json b/tsconfig.json index 4be4cb82053f3ae71a44754630e7550d9892ce3b..6d485d6dec8c61fb81863590278b95e230b872bd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,8 @@ "@psu/utils/logger": ["libs/utils/logger/src/index.ts"], "@psu/utils/loading-events": ["libs/utils/loading-events/src/index.ts"], "@psu/utils/ngrx": ["libs/utils/ngrx/src/index.ts"], - "@psu/utils/cdn": ["libs/utils/cdn/src/index.ts"] + "@psu/utils/cdn": ["libs/utils/cdn/src/index.ts"], + "@psu/utils/form": ["libs/utils/form/src/index.ts"] } }, "exclude": ["node_modules", "tmp"]