From 598f2a159b7f493ab4ca21afc1c9ac33ed733211 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Wed, 15 Oct 2025 11:16:25 +0300 Subject: [PATCH 01/12] abp-input component created --- npm/ng-packs/packages/components/abp-input/README.md | 3 +++ .../packages/components/abp-input/ng-package.json | 6 ++++++ npm/ng-packs/packages/components/abp-input/src/index.ts | 1 + .../components/abp-input/src/lib/abp-input.component.ts | 8 ++++++++ .../packages/components/abp-input/src/public-api.ts | 1 + npm/ng-packs/packages/components/tsconfig.lib.json | 2 +- npm/ng-packs/tsconfig.base.json | 1 + 7 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 npm/ng-packs/packages/components/abp-input/README.md create mode 100644 npm/ng-packs/packages/components/abp-input/ng-package.json create mode 100644 npm/ng-packs/packages/components/abp-input/src/index.ts create mode 100644 npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts create mode 100644 npm/ng-packs/packages/components/abp-input/src/public-api.ts diff --git a/npm/ng-packs/packages/components/abp-input/README.md b/npm/ng-packs/packages/components/abp-input/README.md new file mode 100644 index 00000000000..1a7b116febf --- /dev/null +++ b/npm/ng-packs/packages/components/abp-input/README.md @@ -0,0 +1,3 @@ +# @abp/ng.components/abp-input + +Secondary entry point of `@abp/ng.components`. It can be used by importing from `@abp/ng.components/abp-input`. diff --git a/npm/ng-packs/packages/components/abp-input/ng-package.json b/npm/ng-packs/packages/components/abp-input/ng-package.json new file mode 100644 index 00000000000..e09fb3fd037 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-input/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/public-api.ts" + } +} diff --git a/npm/ng-packs/packages/components/abp-input/src/index.ts b/npm/ng-packs/packages/components/abp-input/src/index.ts new file mode 100644 index 00000000000..e3fb8354505 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-input/src/index.ts @@ -0,0 +1 @@ +export * from './lib/abp-input.component'; diff --git a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts new file mode 100644 index 00000000000..00656a61782 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'abp-input', + template: `` +}) +export class AbpInputComponent {} diff --git a/npm/ng-packs/packages/components/abp-input/src/public-api.ts b/npm/ng-packs/packages/components/abp-input/src/public-api.ts new file mode 100644 index 00000000000..e3fb8354505 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-input/src/public-api.ts @@ -0,0 +1 @@ +export * from './lib/abp-input.component'; diff --git a/npm/ng-packs/packages/components/tsconfig.lib.json b/npm/ng-packs/packages/components/tsconfig.lib.json index 22d2695db80..478ba03368a 100644 --- a/npm/ng-packs/packages/components/tsconfig.lib.json +++ b/npm/ng-packs/packages/components/tsconfig.lib.json @@ -10,6 +10,6 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": ["test-setup.ts", "**/*.spec.ts", "jest.config.ts"], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json index f863f18520d..1a575289a7b 100644 --- a/npm/ng-packs/tsconfig.base.json +++ b/npm/ng-packs/tsconfig.base.json @@ -20,6 +20,7 @@ "@abp/ng.account.core/proxy": ["packages/account-core/proxy/src/public-api.ts"], "@abp/ng.account/config": ["packages/account/config/src/public-api.ts"], "@abp/ng.components": ["packages/components/src/public-api.ts"], + "@abp/ng.components/abp-input": ["packages/components/abp-input/src/index.ts"], "@abp/ng.components/chart.js": ["packages/components/chart.js/src/public-api.ts"], "@abp/ng.components/extensible": ["packages/components/extensible/src/public-api.ts"], "@abp/ng.components/page": ["packages/components/page/src/public-api.ts"], From ec2ced0ff92e6c7bd1855f72eec64e8f514b2f21 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Wed, 15 Oct 2025 15:56:55 +0300 Subject: [PATCH 02/12] reactive form implementation --- .../src/lib/abp-input.component.html | 9 +++ .../abp-input/src/lib/abp-input.component.ts | 78 ++++++++++++++++++- 2 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html diff --git a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html new file mode 100644 index 00000000000..5f9ba173a56 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html @@ -0,0 +1,9 @@ +
+ + +
diff --git a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts index 00656a61782..63ae7b5e905 100644 --- a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts +++ b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts @@ -1,8 +1,78 @@ -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef, + forwardRef, + inject, + OnInit, + input +} from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALUE_ACCESSOR, + ReactiveFormsModule, +} from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { LocalizationPipe } from '@abp/ng.core'; + +const ABP_INPUT_CONTROL_VALUE_ACCESSOR = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AbpInputComponent), + multi: true, +}; @Component({ selector: 'abp-input', - template: `` + templateUrl: './abp-input.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ReactiveFormsModule, LocalizationPipe], + exportAs: 'abpInput', + host: { + class: 'abp-input' + }, + providers: [ + ABP_INPUT_CONTROL_VALUE_ACCESSOR + ] }) -export class AbpInputComponent {} +export class AbpInputComponent implements OnInit, ControlValueAccessor { + label = input.required(); + type = input<'text' | 'number' | 'password'>('text'); + readonly formBuilder = inject(FormBuilder); + readonly changeDetectorRef = inject(ChangeDetectorRef); + readonly destroyRef = inject(DestroyRef); + abpInputFormGroup: FormGroup; + + ngOnInit() { + this.abpInputFormGroup = this.formBuilder.group({ + value: [''] + }) + + this.value.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => { + this.onChange(val); + }) + } + + writeValue(value: any[]): void { + this.value.setValue(value); + this.changeDetectorRef.markForCheck(); + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + get value(): AbstractControl { + return this.abpInputFormGroup.get('value'); + } + + private onChange: (value: any) => void = () => {}; + private onTouched: () => void = () => {}; +} From 086ccb282e90be271f08584080347a6ffa8214d2 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Wed, 15 Oct 2025 16:14:42 +0300 Subject: [PATCH 03/12] refactoring --- .../abp-input/src/lib/abp-input.component.html | 5 +++-- .../abp-input/src/lib/abp-input.component.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html index 5f9ba173a56..c2774b5b3ed 100644 --- a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html +++ b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html @@ -1,9 +1,10 @@
- +
diff --git a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts index 63ae7b5e905..eb60e460871 100644 --- a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts +++ b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts @@ -41,6 +41,8 @@ const ABP_INPUT_CONTROL_VALUE_ACCESSOR = { export class AbpInputComponent implements OnInit, ControlValueAccessor { label = input.required(); type = input<'text' | 'number' | 'password'>('text'); + id = input(''); + placeholder = input(''); readonly formBuilder = inject(FormBuilder); readonly changeDetectorRef = inject(ChangeDetectorRef); readonly destroyRef = inject(DestroyRef); @@ -69,6 +71,14 @@ export class AbpInputComponent implements OnInit, ControlValueAccessor { this.onTouched = fn; } + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.value.disable(); + } else { + this.value.enable(); + } + } + get value(): AbstractControl { return this.abpInputFormGroup.get('value'); } From 089a97bd48ed915666d76dd70c6a7ef276c9aec3 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Wed, 15 Oct 2025 18:17:56 +0300 Subject: [PATCH 04/12] refactoring --- .../src/lib/abp-input.component.html | 8 +++++ .../abp-input/src/lib/abp-input.component.ts | 34 ++++++++++++++----- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html index c2774b5b3ed..3a86a223cbf 100644 --- a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html +++ b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html @@ -7,4 +7,12 @@ [placeholder]="placeholder()" formControlName="value" /> + @if (errors.length > 0) { +
+ @for (error of errors; track error) { +
{{ error }}
+ } +
+ } +
diff --git a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts index eb60e460871..c78817a46e4 100644 --- a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts +++ b/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts @@ -6,14 +6,19 @@ import { forwardRef, inject, OnInit, - input + input, + Injector } from '@angular/core'; import { AbstractControl, ControlValueAccessor, FormBuilder, + FormControl, + FormControlName, FormGroup, + FormGroupDirective, NG_VALUE_ACCESSOR, + NgControl, ReactiveFormsModule, } from '@angular/forms'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -32,30 +37,36 @@ const ABP_INPUT_CONTROL_VALUE_ACCESSOR = { imports: [ReactiveFormsModule, LocalizationPipe], exportAs: 'abpInput', host: { - class: 'abp-input' + class: 'abp-input', }, - providers: [ - ABP_INPUT_CONTROL_VALUE_ACCESSOR - ] + providers: [ABP_INPUT_CONTROL_VALUE_ACCESSOR], }) export class AbpInputComponent implements OnInit, ControlValueAccessor { label = input.required(); type = input<'text' | 'number' | 'password'>('text'); id = input(''); placeholder = input(''); + control: FormControl; readonly formBuilder = inject(FormBuilder); readonly changeDetectorRef = inject(ChangeDetectorRef); readonly destroyRef = inject(DestroyRef); + readonly injector = inject(Injector); abpInputFormGroup: FormGroup; ngOnInit() { + + const ngControl = this.injector.get(NgControl, null); + if (ngControl) { + this.control = this.injector.get(FormGroupDirective).getControl(ngControl as FormControlName); + } + this.abpInputFormGroup = this.formBuilder.group({ - value: [''] - }) + value: [''], + }); this.value.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => { this.onChange(val); - }) + }); } writeValue(value: any[]): void { @@ -79,6 +90,13 @@ export class AbpInputComponent implements OnInit, ControlValueAccessor { } } + get errors(): string[] { + if (this.control && this.control.errors) { + return [] + } + return [] + } + get value(): AbstractControl { return this.abpInputFormGroup.get('value'); } From 31a0c0b094b5adaf4162d29b876ad38b4dc72857 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Mon, 20 Oct 2025 13:58:05 +0300 Subject: [PATCH 05/12] refactoring --- .../components/abp-input/src/{lib => }/abp-input.component.html | 0 .../components/abp-input/src/{lib => }/abp-input.component.ts | 0 npm/ng-packs/packages/components/abp-input/src/index.ts | 1 - npm/ng-packs/packages/components/abp-input/src/public-api.ts | 2 +- npm/ng-packs/tsconfig.base.json | 2 +- 5 files changed, 2 insertions(+), 3 deletions(-) rename npm/ng-packs/packages/components/abp-input/src/{lib => }/abp-input.component.html (100%) rename npm/ng-packs/packages/components/abp-input/src/{lib => }/abp-input.component.ts (100%) delete mode 100644 npm/ng-packs/packages/components/abp-input/src/index.ts diff --git a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html similarity index 100% rename from npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.html rename to npm/ng-packs/packages/components/abp-input/src/abp-input.component.html diff --git a/npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts similarity index 100% rename from npm/ng-packs/packages/components/abp-input/src/lib/abp-input.component.ts rename to npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts diff --git a/npm/ng-packs/packages/components/abp-input/src/index.ts b/npm/ng-packs/packages/components/abp-input/src/index.ts deleted file mode 100644 index e3fb8354505..00000000000 --- a/npm/ng-packs/packages/components/abp-input/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './lib/abp-input.component'; diff --git a/npm/ng-packs/packages/components/abp-input/src/public-api.ts b/npm/ng-packs/packages/components/abp-input/src/public-api.ts index e3fb8354505..319fe5737bf 100644 --- a/npm/ng-packs/packages/components/abp-input/src/public-api.ts +++ b/npm/ng-packs/packages/components/abp-input/src/public-api.ts @@ -1 +1 @@ -export * from './lib/abp-input.component'; +export * from './abp-input.component'; diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json index 1a575289a7b..ca18b4032c6 100644 --- a/npm/ng-packs/tsconfig.base.json +++ b/npm/ng-packs/tsconfig.base.json @@ -20,7 +20,7 @@ "@abp/ng.account.core/proxy": ["packages/account-core/proxy/src/public-api.ts"], "@abp/ng.account/config": ["packages/account/config/src/public-api.ts"], "@abp/ng.components": ["packages/components/src/public-api.ts"], - "@abp/ng.components/abp-input": ["packages/components/abp-input/src/index.ts"], + "@abp/ng.components/abp-input": ["packages/components/abp-input/src/public-api.ts"], "@abp/ng.components/chart.js": ["packages/components/chart.js/src/public-api.ts"], "@abp/ng.components/extensible": ["packages/components/extensible/src/public-api.ts"], "@abp/ng.components/page": ["packages/components/page/src/public-api.ts"], From a138889e34ce8d7cb54f9ffacac9fb710f723d36 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Mon, 20 Oct 2025 15:02:50 +0300 Subject: [PATCH 06/12] hint input added --- .../components/abp-input/src/abp-input.component.html | 5 ++++- .../packages/components/abp-input/src/abp-input.component.ts | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html index 3a86a223cbf..37db0e893f3 100644 --- a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html +++ b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html @@ -1,5 +1,5 @@
- + + @if (hint()) { + {{ hint() | abpLocalization }} + } @if (errors.length > 0) {
@for (error of errors; track error) { diff --git a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts index c78817a46e4..cb34b5c0648 100644 --- a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts +++ b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts @@ -46,6 +46,7 @@ export class AbpInputComponent implements OnInit, ControlValueAccessor { type = input<'text' | 'number' | 'password'>('text'); id = input(''); placeholder = input(''); + hint = input(''); control: FormControl; readonly formBuilder = inject(FormBuilder); readonly changeDetectorRef = inject(ChangeDetectorRef); @@ -70,6 +71,7 @@ export class AbpInputComponent implements OnInit, ControlValueAccessor { } writeValue(value: any[]): void { + console.log(value); this.value.setValue(value); this.changeDetectorRef.markForCheck(); } From 2d44ac9aa10ed27e8d34dd8db04c8056c1c30432 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Mon, 20 Oct 2025 15:20:15 +0300 Subject: [PATCH 07/12] style update --- .../components/abp-input/src/abp-input.component.html | 2 +- .../packages/components/abp-input/src/abp-input.component.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html index 37db0e893f3..c6f4e490e8b 100644 --- a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html +++ b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html @@ -1,4 +1,4 @@ -
+
(''); placeholder = input(''); hint = input(''); + containerClass = input('mb-3'); control: FormControl; readonly formBuilder = inject(FormBuilder); readonly changeDetectorRef = inject(ChangeDetectorRef); @@ -71,7 +73,6 @@ export class AbpInputComponent implements OnInit, ControlValueAccessor { } writeValue(value: any[]): void { - console.log(value); this.value.setValue(value); this.changeDetectorRef.markForCheck(); } From 747c33d91155cfb13c7bb0366b1dbe8e4cd4543b Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Mon, 20 Oct 2025 17:42:47 +0300 Subject: [PATCH 08/12] abp form field component added --- .../components/abp-form-field/ng-package.json | 6 ++++++ .../abp-form-field-hint.component.ts | 11 +++++++++++ .../abp-form-field.component.html | 4 ++++ .../abp-form-field.component.ts | 19 +++++++++++++++++++ .../src/lib/abp-form-field/index.ts | 2 ++ .../abp-form-field/src/lib/index.ts | 1 + .../abp-form-field/src/public-api.ts | 1 + .../abp-input/src/abp-input.component.html | 2 +- .../abp-input/src/abp-input.component.ts | 6 ++---- npm/ng-packs/tsconfig.base.json | 1 + 10 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 npm/ng-packs/packages/components/abp-form-field/ng-package.json create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-hint.component.ts create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/index.ts create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/index.ts create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/public-api.ts diff --git a/npm/ng-packs/packages/components/abp-form-field/ng-package.json b/npm/ng-packs/packages/components/abp-form-field/ng-package.json new file mode 100644 index 00000000000..e09fb3fd037 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/public-api.ts" + } +} diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-hint.component.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-hint.component.ts new file mode 100644 index 00000000000..222ca6163cd --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-hint.component.ts @@ -0,0 +1,11 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'abp-form-field-hint', + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AbpFormFieldHintComponent { +} diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html new file mode 100644 index 00000000000..e56372aab1e --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts new file mode 100644 index 00000000000..e0f6ad931a9 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts @@ -0,0 +1,19 @@ +import { Component, ChangeDetectionStrategy, input, HostBinding } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'abp-form-field', + templateUrl: './abp-form-field.component.html', + imports: [CommonModule], + exportAs: 'abpFormField', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AbpFormFieldComponent { + + containerClass = input('mb-3'); + + @HostBinding('class') + get hostClasses(): string { + return `d-block mb-3 ${this.containerClass()}`; + } +} diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/index.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/index.ts new file mode 100644 index 00000000000..4a8a8ad7a7d --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/index.ts @@ -0,0 +1,2 @@ +export * from './abp-form-field.component'; +export * from './abp-form-field-hint.component'; diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/index.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/index.ts new file mode 100644 index 00000000000..e65c3233d9b --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/index.ts @@ -0,0 +1 @@ +export * from './abp-form-field'; diff --git a/npm/ng-packs/packages/components/abp-form-field/src/public-api.ts b/npm/ng-packs/packages/components/abp-form-field/src/public-api.ts new file mode 100644 index 00000000000..f41a696fd20 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/public-api.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html index c6f4e490e8b..37db0e893f3 100644 --- a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html +++ b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html @@ -1,4 +1,4 @@ -
+
(''); placeholder = input(''); hint = input(''); - containerClass = input('mb-3'); control: FormControl; readonly formBuilder = inject(FormBuilder); readonly changeDetectorRef = inject(ChangeDetectorRef); @@ -72,7 +70,7 @@ export class AbpInputComponent implements OnInit, ControlValueAccessor { }); } - writeValue(value: any[]): void { + writeValue(value: any): void { this.value.setValue(value); this.changeDetectorRef.markForCheck(); } diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json index ca18b4032c6..0b2b7edc0ae 100644 --- a/npm/ng-packs/tsconfig.base.json +++ b/npm/ng-packs/tsconfig.base.json @@ -20,6 +20,7 @@ "@abp/ng.account.core/proxy": ["packages/account-core/proxy/src/public-api.ts"], "@abp/ng.account/config": ["packages/account/config/src/public-api.ts"], "@abp/ng.components": ["packages/components/src/public-api.ts"], + "@abp/ng.components/abp-form-field": ["packages/components/abp-form-field/src/public-api.ts"], "@abp/ng.components/abp-input": ["packages/components/abp-input/src/public-api.ts"], "@abp/ng.components/chart.js": ["packages/components/chart.js/src/public-api.ts"], "@abp/ng.components/extensible": ["packages/components/extensible/src/public-api.ts"], From e7ba609ea3e55acd96d7b4097ed9e44e96cae6cf Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Tue, 21 Oct 2025 11:54:11 +0300 Subject: [PATCH 09/12] abp form field component added --- .../abp-form-field-error.component.ts | 13 ++++++ .../abp-form-field-label.component.ts | 11 +++++ .../abp-form-field.component.html | 5 ++- .../abp-form-field.component.ts | 16 +++++++- .../src/lib/abp-form-field/index.ts | 1 + .../packages/components/abp-input/README.md | 3 -- .../abp-input/src/abp-input.component.html | 40 ++++++++++++------- .../abp-input/src/abp-input.component.ts | 4 +- 8 files changed, 73 insertions(+), 20 deletions(-) create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-error.component.ts create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-label.component.ts delete mode 100644 npm/ng-packs/packages/components/abp-input/README.md diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-error.component.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-error.component.ts new file mode 100644 index 00000000000..15dfb226962 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-error.component.ts @@ -0,0 +1,13 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'abp-form-field-error', + template: `
+ +
`, + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class AbpFormFieldErrorComponent { + +} diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-label.component.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-label.component.ts new file mode 100644 index 00000000000..634263cdc95 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-label.component.ts @@ -0,0 +1,11 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; + +@Component({ + selector: 'abp-form-field-label', + template: ``, + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class AbpFormFieldLabelComponent { + for= input(''); +} diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html index e56372aab1e..aa1540c5f63 100644 --- a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html @@ -1,4 +1,7 @@ -
+
+
diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts index e0f6ad931a9..6e02ee03ba0 100644 --- a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts @@ -1,16 +1,30 @@ -import { Component, ChangeDetectionStrategy, input, HostBinding } from '@angular/core'; +import { + Component, + ChangeDetectionStrategy, + input, + HostBinding, + InjectionToken, + QueryList, + ContentChild, + contentChild, +} from '@angular/core'; import { CommonModule } from '@angular/common'; +import { AbpFormFieldLabelComponent } from './abp-form-field-label.component'; + +export const ABP_FORM_FIELD = new InjectionToken('AbpFormFieldComponent'); @Component({ selector: 'abp-form-field', templateUrl: './abp-form-field.component.html', imports: [CommonModule], exportAs: 'abpFormField', + providers: [{ provide: ABP_FORM_FIELD, useExisting: AbpFormFieldComponent }], changeDetection: ChangeDetectionStrategy.OnPush, }) export class AbpFormFieldComponent { containerClass = input('mb-3'); + labelComponent = contentChild(AbpFormFieldLabelComponent); @HostBinding('class') get hostClasses(): string { diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/index.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/index.ts index 4a8a8ad7a7d..529106c889e 100644 --- a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/index.ts +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/index.ts @@ -1,2 +1,3 @@ export * from './abp-form-field.component'; export * from './abp-form-field-hint.component'; +export * from './abp-form-field-label.component'; diff --git a/npm/ng-packs/packages/components/abp-input/README.md b/npm/ng-packs/packages/components/abp-input/README.md deleted file mode 100644 index 1a7b116febf..00000000000 --- a/npm/ng-packs/packages/components/abp-input/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @abp/ng.components/abp-input - -Secondary entry point of `@abp/ng.components`. It can be used by importing from `@abp/ng.components/abp-input`. diff --git a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html index 37db0e893f3..c9f3d279a73 100644 --- a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html +++ b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html @@ -1,5 +1,4 @@ -
- +@if(abpFormField) { - @if (hint()) { - {{ hint() | abpLocalization }} - } - @if (errors.length > 0) { -
- @for (error of errors; track error) { -
{{ error }}
- } -
- } -
-
+} @else { +
+ @if (label()) { + + } + + @if (hint()) { + {{ hint() | abpLocalization }} + } + @if (errors.length > 0) { +
+ @for (error of errors; track error) { +
{{ error }}
+ } +
+ } +
+
+} diff --git a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts index efbcfdaffc5..3a3dbe13328 100644 --- a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts +++ b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts @@ -23,6 +23,7 @@ import { } from '@angular/forms'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { LocalizationPipe } from '@abp/ng.core'; +import { ABP_FORM_FIELD } from '@abp/ng.components/abp-form-field'; const ABP_INPUT_CONTROL_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, @@ -42,7 +43,7 @@ const ABP_INPUT_CONTROL_VALUE_ACCESSOR = { providers: [ABP_INPUT_CONTROL_VALUE_ACCESSOR], }) export class AbpInputComponent implements OnInit, ControlValueAccessor { - label = input.required(); + label = input(); type = input<'text' | 'number' | 'password'>('text'); id = input(''); placeholder = input(''); @@ -52,6 +53,7 @@ export class AbpInputComponent implements OnInit, ControlValueAccessor { readonly changeDetectorRef = inject(ChangeDetectorRef); readonly destroyRef = inject(DestroyRef); readonly injector = inject(Injector); + readonly abpFormField = inject(ABP_FORM_FIELD, { optional: true }); abpInputFormGroup: FormGroup; ngOnInit() { From c8c89345e192ddbe98dfc8a1c946605f6b7c2d20 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Tue, 21 Oct 2025 12:11:02 +0300 Subject: [PATCH 10/12] refactoring --- .../abp-form-field/abp-form-field.component.html | 1 + .../abp-input/src/abp-input.component.html | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html index aa1540c5f63..da5f06ab6d6 100644 --- a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html @@ -4,4 +4,5 @@ +
diff --git a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html index c9f3d279a73..e31818a2ff5 100644 --- a/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html +++ b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html @@ -1,11 +1,13 @@ @if(abpFormField) { - + +
} @else {
@if (label()) { From b5214827b80e4d25d7b2a7eb433b71c48bfc957a Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Wed, 3 Dec 2025 13:19:36 +0300 Subject: [PATCH 11/12] example --- .../abp-form-field-demo.component.html | 30 +++++++++++++ .../abp-form-field-demo.component.ts | 42 +++++++++++++++++++ .../apps/dev-app/src/app/app.routes.ts | 4 ++ .../apps/dev-app/src/app/route.provider.ts | 7 ++++ 4 files changed, 83 insertions(+) create mode 100644 npm/ng-packs/apps/dev-app/src/app/abp-form-field-demo/abp-form-field-demo.component.html create mode 100644 npm/ng-packs/apps/dev-app/src/app/abp-form-field-demo/abp-form-field-demo.component.ts diff --git a/npm/ng-packs/apps/dev-app/src/app/abp-form-field-demo/abp-form-field-demo.component.html b/npm/ng-packs/apps/dev-app/src/app/abp-form-field-demo/abp-form-field-demo.component.html new file mode 100644 index 00000000000..6ed16cdd39f --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/app/abp-form-field-demo/abp-form-field-demo.component.html @@ -0,0 +1,30 @@ + + +

Abp Form Field Demo

+
+ +
+ + Name + + + + + Email + + + + + Password + + + + + Age + + + + +
+
+
diff --git a/npm/ng-packs/apps/dev-app/src/app/abp-form-field-demo/abp-form-field-demo.component.ts b/npm/ng-packs/apps/dev-app/src/app/abp-form-field-demo/abp-form-field-demo.component.ts new file mode 100644 index 00000000000..c0e1f430edd --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/app/abp-form-field-demo/abp-form-field-demo.component.ts @@ -0,0 +1,42 @@ +import { Component, inject } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { AbpFormFieldComponent, AbpFormFieldLabelComponent } from '@abp/ng.components/abp-form-field'; +import { CardComponent, CardBodyComponent, CardHeaderComponent } from '@abp/ng.theme.shared'; +import { CommonModule } from '@angular/common'; +import { AbpInputComponent } from '@abp/ng.components/abp-input'; + +@Component({ + selector: 'app-abp-form-field-demo', + templateUrl: './abp-form-field-demo.component.html', + standalone: true, + imports: [ + CommonModule, + ReactiveFormsModule, + AbpFormFieldComponent, + CardComponent, + CardBodyComponent, + CardHeaderComponent, + AbpInputComponent, + AbpFormFieldLabelComponent, + ], +}) +export class AbpFormFieldDemoComponent { + private fb = inject(FormBuilder); + + form = this.fb.group({ + name: ['', [Validators.required, Validators.minLength(3)]], + email: ['', [Validators.required, Validators.email]], + password: ['', [Validators.required, Validators.minLength(6)]], + age: [null, [Validators.required, Validators.min(18)]], + description: ['', [Validators.maxLength(200)]], + agree: [false, [Validators.requiredTrue]], + }); + + submit() { + if (this.form.valid) { + console.log(this.form.value); + } else { + console.log('Form is invalid'); + } + } +} diff --git a/npm/ng-packs/apps/dev-app/src/app/app.routes.ts b/npm/ng-packs/apps/dev-app/src/app/app.routes.ts index 47462b7f8fa..a56bf459fe1 100644 --- a/npm/ng-packs/apps/dev-app/src/app/app.routes.ts +++ b/npm/ng-packs/apps/dev-app/src/app/app.routes.ts @@ -6,6 +6,10 @@ export const appRoutes: Routes = [ pathMatch: 'full', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent), }, + { + path: 'form-field-demo', + loadComponent: () => import('./abp-form-field-demo/abp-form-field-demo.component').then(m => m.AbpFormFieldDemoComponent), + }, { path: 'account', loadChildren: () => import('@abp/ng.account').then(m => m.createRoutes()), diff --git a/npm/ng-packs/apps/dev-app/src/app/route.provider.ts b/npm/ng-packs/apps/dev-app/src/app/route.provider.ts index f60e7ca3309..9b11cbc4105 100644 --- a/npm/ng-packs/apps/dev-app/src/app/route.provider.ts +++ b/npm/ng-packs/apps/dev-app/src/app/route.provider.ts @@ -16,6 +16,13 @@ function configureRoutes() { iconClass: 'fas fa-home', order: 1, layout: eLayoutType.application, + }, + { + path: '/form-field-demo', + name: 'Form Field Demo', + iconClass: 'fas fa-file-alt', + order: 2, + layout: eLayoutType.application, } ]); } From 5552fafde0eb5892c7dbceb2a8d984df75bc0df3 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Fri, 13 Feb 2026 14:13:25 +0300 Subject: [PATCH 12/12] refactoring --- .../abp-form-field-error.component.spec.ts | 26 ++++ .../abp-form-field-hint.component.spec.ts | 26 ++++ .../abp-form-field-label.component.spec.ts | 31 ++++ .../abp-form-field.component.scss | 39 +++++ .../abp-form-field.component.spec.ts | 140 ++++++++++++++++++ .../abp-form-field.component.ts | 1 + 6 files changed, 263 insertions(+) create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-error.component.spec.ts create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-hint.component.spec.ts create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-label.component.spec.ts create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.scss create mode 100644 npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.spec.ts diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-error.component.spec.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-error.component.spec.ts new file mode 100644 index 00000000000..3266f94408b --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-error.component.spec.ts @@ -0,0 +1,26 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { AbpFormFieldErrorComponent } from './abp-form-field-error.component'; + +describe('AbpFormFieldErrorComponent', () => { + let spectator: SpectatorHost; + const createHost = createHostFactory({ + component: AbpFormFieldErrorComponent, + }); + + it('should create', () => { + spectator = createHost('Test error'); + expect(spectator.component).toBeTruthy(); + }); + + it('should render content', () => { + spectator = createHost('Test error'); + expect(spectator.element).toHaveText('Test error'); + }); + + it('should have correct CSS classes', () => { + spectator = createHost('Test error'); + const div = spectator.query('div'); + expect(div).toHaveClass('invalid-feedback'); + expect(div).toHaveClass('d-block'); + }); +}); diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-hint.component.spec.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-hint.component.spec.ts new file mode 100644 index 00000000000..4be7890ff23 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-hint.component.spec.ts @@ -0,0 +1,26 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { AbpFormFieldHintComponent } from './abp-form-field-hint.component'; + +describe('AbpFormFieldHintComponent', () => { + let spectator: SpectatorHost; + const createHost = createHostFactory({ + component: AbpFormFieldHintComponent, + }); + + it('should create', () => { + spectator = createHost('Test hint'); + expect(spectator.component).toBeTruthy(); + }); + + it('should render content', () => { + spectator = createHost('Test hint'); + expect(spectator.element).toHaveText('Test hint'); + }); + + it('should have correct CSS classes', () => { + spectator = createHost('Test hint'); + const small = spectator.query('small'); + expect(small).toHaveClass('form-text'); + expect(small).toHaveClass('text-muted'); + }); +}); diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-label.component.spec.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-label.component.spec.ts new file mode 100644 index 00000000000..368fd473443 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field-label.component.spec.ts @@ -0,0 +1,31 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { AbpFormFieldLabelComponent } from './abp-form-field-label.component'; + +describe('AbpFormFieldLabelComponent', () => { + let spectator: SpectatorHost; + const createHost = createHostFactory({ + component: AbpFormFieldLabelComponent, + }); + + it('should create', () => { + spectator = createHost('Test Label'); + expect(spectator.component).toBeTruthy(); + }); + + it('should render content', () => { + spectator = createHost('Test Label'); + expect(spectator.element).toHaveText('Test Label'); + }); + + it('should have for input property', () => { + spectator = createHost( + 'Test Label' + ); + expect(spectator.component.for()).toBe('test-input'); + }); + + it('should have empty for by default', () => { + spectator = createHost('Test Label'); + expect(spectator.component.for()).toBe(''); + }); +}); diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.scss b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.scss new file mode 100644 index 00000000000..f2471cb8f98 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.scss @@ -0,0 +1,39 @@ +:host { + display: block; +} + +.form-group { + margin-bottom: 0.75rem; +} + +.form-label { + display: inline-block; + margin-bottom: 0.5rem; + font-weight: 500; +} + +.form-text { + display: block; + margin-top: 0.25rem; + font-size: 0.875rem; +} + +.invalid-feedback { + display: block; + width: 100%; + margin-top: 0.25rem; + font-size: 0.875rem; + color: var(--bs-danger, #dc3545); +} + +// Disabled state +:host-context([disabled]) { + opacity: 0.6; + pointer-events: none; +} + +// Required field indicator +.form-label.required::after { + content: ' *'; + color: var(--bs-danger, #dc3545); +} diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.spec.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.spec.ts new file mode 100644 index 00000000000..4ee163976d1 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.spec.ts @@ -0,0 +1,140 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { AbpFormFieldComponent } from './abp-form-field.component'; +import { AbpFormFieldLabelComponent } from './abp-form-field-label.component'; +import { AbpFormFieldHintComponent } from './abp-form-field-hint.component'; +import { AbpFormFieldErrorComponent } from './abp-form-field-error.component'; + +describe('AbpFormFieldComponent', () => { + let spectator: SpectatorHost; + const createHost = createHostFactory({ + component: AbpFormFieldComponent, + declarations: [ + AbpFormFieldLabelComponent, + AbpFormFieldHintComponent, + AbpFormFieldErrorComponent, + ], + }); + + describe('Basic rendering', () => { + it('should create', () => { + spectator = createHost(` + + Test Label + + `); + expect(spectator.component).toBeTruthy(); + }); + + it('should render with default container class', () => { + spectator = createHost(` + + Test Label + + `); + expect(spectator.hostElement).toHaveClass('mb-3'); + expect(spectator.hostElement).toHaveClass('d-block'); + }); + + it('should apply custom container class', () => { + spectator = createHost(` + + Test Label + + `); + expect(spectator.hostElement).toHaveClass('custom-class'); + }); + }); + + describe('Label integration', () => { + it('should render label component', () => { + spectator = createHost(` + + Test Label + + `); + const label = spectator.query('label'); + expect(label).toBeTruthy(); + expect(label).toHaveText('Test Label'); + }); + + it('should bind label for attribute', () => { + spectator = createHost(` + + Test Label + + `); + const label = spectator.query('label'); + expect(label).toHaveAttribute('for', 'test-input'); + }); + }); + + describe('Content projection', () => { + it('should project abp-input', () => { + spectator = createHost(` + + Test Label + + + `); + const input = spectator.query('input'); + expect(input).toBeTruthy(); + }); + + it('should project hint component', () => { + spectator = createHost(` + + Test Label + Test hint + + `); + const hint = spectator.query('abp-form-field-hint'); + expect(hint).toBeTruthy(); + }); + + it('should project error component', () => { + spectator = createHost(` + + Test Label + Test error + + `); + const error = spectator.query('abp-form-field-error'); + expect(error).toBeTruthy(); + }); + }); + + describe('Host binding', () => { + it('should have correct host classes', () => { + spectator = createHost(` + + Test Label + + `); + expect(spectator.hostElement).toHaveClass('d-block'); + expect(spectator.hostElement).toHaveClass('mb-3'); + }); + + it('should combine default and custom classes', () => { + spectator = createHost(` + + Test Label + + `); + expect(spectator.hostElement).toHaveClass('d-block'); + expect(spectator.hostElement).toHaveClass('mb-3'); + expect(spectator.hostElement).toHaveClass('mt-4'); + }); + }); + + describe('Export as', () => { + it('should be accessible via exportAs', () => { + spectator = createHost(` + + Test Label + + `); + const formField = spectator.queryHost('abp-form-field'); + expect(formField).toBeTruthy(); + }); + }); +}); diff --git a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts index 6e02ee03ba0..71cdb488682 100644 --- a/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts @@ -16,6 +16,7 @@ export const ABP_FORM_FIELD = new InjectionToken('AbpForm @Component({ selector: 'abp-form-field', templateUrl: './abp-form-field.component.html', + styleUrls: ['./abp-form-field.component.scss'], imports: [CommonModule], exportAs: 'abpFormField', providers: [{ provide: ABP_FORM_FIELD, useExisting: AbpFormFieldComponent }],