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"]