I am trying to create a reusable field as we do in react
. But I failed to do that. I need some suggestions or guidance to fix my issue. Actually, I created a component that holds the input field. Now I want to use that field everywhere in the Angular forms. Please help me to fix this.
Whenever i try to submit it always return undefined.
Any solution appreciated!
Login Form
<Modal [isOpen]="true" title="Login" actionLabel="Sign in" [onSubmit]="handleSubmit">
<form [formGroup]="loginForm">
<div class="flex flex-col gap-4">
<app-input placeholder="Email" type="email" controlName="email"
(formControlChange)="handleFormControl('email',$event)" />
<app-input placeholder="Password" type="password" controlName="password"
(formControlChange)="handleFormControl('password',$event)" />
</div>
</form>
</Modal>
export class LoginModalComponent implements OnInit {
loginForm!: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
this.loginForm = this.fb.group({
email: new FormControl(''),
password: new FormControl('')
})
}
handleFormControl(formControlName: string, formControl: FormControl) {
this.loginForm.setControl(formControlName, formControl);
}
handleSubmit(): void {
console.log(this.loginForm);
}
}
Input Component
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-input',
template: `
<input
[placeholder]="placeholder"
[formControl]="formControl"
class="
w-full
p-4
text-lg
bg-black
border-2
border-neutral-800
rounded-md
outline-none
text-white
focus:border-sky-500
focus:border-2
transition
disabled:bg-neutral-900
disabled:opacity-70
disabled:cursor-not-allowed
"
[type]="type"
>
`,
styles: [
]
})
export class InputComponent implements OnInit {
@Input('placeholder') placeholder: string = '';
@Input('controlName') controlName: string = '';
@Input('type') type: string = 'text';
@Input() required: boolean = false;
@Input() email: boolean = false;
@Output() formControlChange = new EventEmitter<FormControl>();
formControl!: FormControl;
ngOnInit(): void {
this.initializeFormControl()
this.subscribeToValueChange()
// this.formControlChange.emit(this.formControl);
}
initializeFormControl(): void {
const validators = [];
if (this.required) {
validators.push(Validators.required)
}
if (this.email) {
validators.push(Validators.email);
}
this.formControl = new FormControl('', validators);
}
subscribeToValueChange(): void {
this.formControl.valueChanges.subscribe((value) => {
this.formControlChange.emit(this.formControl);
})
}
}
You can use NG_VALUE_ACCESSOR implementation for reusable custom form control components
demo : https://stackblitz.com/edit/angular-module-4xcvpm?file=src%2Fapp%2Fmodel.compoennt.ts
input.component.ts
import { Component, Input, forwardRef, OnInit } from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
Validators,
FormControl,
ValidatorFn,
} from '@angular/forms';
@Component({
selector: 'app-input',
template: `
<input
[placeholder]="placeholder"
[formControl]="formControl"
class="
w-full
p-4
text-lg
bg-black
border-2
border-neutral-800
rounded-md
outline-none
text-white
focus:border-sky-500
focus:border-2
transition
disabled:bg-neutral-900
disabled:opacity-70
disabled:cursor-not-allowed
"
[type]="type"
>`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputComponent),
multi: true,
},
],
styles: [],
})
export class InputComponent implements OnInit, ControlValueAccessor {
@Input('placeholder') placeholder: string = '';
@Input('type') type: string = 'text';
@Input() required: boolean = false;
@Input() email: boolean = false;
formControl!: FormControl;
onTouched: any;
onChange: any;
ngOnInit(): void {
const validators: ValidatorFn[] = [];
if (this.required) {
validators.push(Validators.required);
}
if (this.email) {
validators.push(Validators.email);
}
this.formControl = new FormControl('', validators);
}
writeValue(value: any): void {
this.formControl.setValue(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
this.formControl.valueChanges.subscribe(fn);
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
isDisabled ? this.formControl.disable() : this.formControl.enable();
}
}
login-modal.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-login-modal',
template: `
<Modal [isOpen]="true" title="Login" actionLabel="Sign in" (submit)="handleSubmit()">
<form [formGroup]="loginForm">
<div class="flex flex-col gap-4">
<app-input placeholder="Email" type="email" required="true" email="true" formControlName="email"></app-input>
<app-input placeholder="Password" type="password" required="true" formControlName="password"></app-input>
</div>
</form>
</Modal>
`,
styles: [],
})
export class LoginModalComponent implements OnInit {
loginForm!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.loginForm = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', Validators.required],
});
this.loginForm.get('email')!.valueChanges.subscribe((value) => {
console.log('email value changed:', value);
});
this.loginForm.get('password')!.valueChanges.subscribe((value) => {
console.log('password value changed:', value);
});
}
handleSubmit(): void {
console.log('heloo');
console.log(this.loginForm.value);
}
}
modal.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'Modal',
template: `
<div *ngIf="isOpen" class="modal">
<h2>{{ title }}</h2>
<ng-content></ng-content>
<button (click)="submit.emit()">{{ actionLabel }}</button>
</div>
`,
})
export class ModalComponent {
@Input() isOpen: boolean = false;
@Input() title: string = '';
@Input() actionLabel: string = '';
@Output() submit = new EventEmitter<void>();
}