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, } ]); } 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-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-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-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 new file mode 100644 index 00000000000..da5f06ab6d6 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.html @@ -0,0 +1,8 @@ +
+ + + + +
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..6e02ee03ba0 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/abp-form-field.component.ts @@ -0,0 +1,33 @@ +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 { + 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..529106c889e --- /dev/null +++ b/npm/ng-packs/packages/components/abp-form-field/src/lib/abp-form-field/index.ts @@ -0,0 +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-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/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/abp-input.component.html b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html new file mode 100644 index 00000000000..e31818a2ff5 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.html @@ -0,0 +1,35 @@ +@if(abpFormField) { +
+ +
+} @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 new file mode 100644 index 00000000000..3a3dbe13328 --- /dev/null +++ b/npm/ng-packs/packages/components/abp-input/src/abp-input.component.ts @@ -0,0 +1,109 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef, + forwardRef, + inject, + OnInit, + 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'; +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, + useExisting: forwardRef(() => AbpInputComponent), + multi: true, +}; + +@Component({ + selector: 'abp-input', + 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 implements OnInit, ControlValueAccessor { + label = input(); + type = input<'text' | 'number' | 'password'>('text'); + id = input(''); + placeholder = input(''); + hint = input(''); + control: FormControl; + readonly formBuilder = inject(FormBuilder); + readonly changeDetectorRef = inject(ChangeDetectorRef); + readonly destroyRef = inject(DestroyRef); + readonly injector = inject(Injector); + readonly abpFormField = inject(ABP_FORM_FIELD, { optional: true }); + 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: [''], + }); + + 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; + } + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.value.disable(); + } else { + this.value.enable(); + } + } + + get errors(): string[] { + if (this.control && this.control.errors) { + return [] + } + return [] + } + + get value(): AbstractControl { + return this.abpInputFormGroup.get('value'); + } + + private onChange: (value: any) => void = () => {}; + private onTouched: () => void = () => {}; +} 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..319fe5737bf --- /dev/null +++ b/npm/ng-packs/packages/components/abp-input/src/public-api.ts @@ -0,0 +1 @@ +export * from './abp-input.component'; diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json index 496d83ca676..0e91a27b3eb 100644 --- a/npm/ng-packs/tsconfig.base.json +++ b/npm/ng-packs/tsconfig.base.json @@ -20,6 +20,8 @@ "@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"], "@abp/ng.components/lookup": ["packages/components/lookup/src/public-api.ts"],