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>();
}
In your code I see no "submit" part. But your code work as well. This is the main component:
import 'zone.js/dist/zone';
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { InputComponent } from './input/input.component';
@Component({
selector: 'my-app',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, FormsModule, InputComponent],
template: `
<h1>Hello from {{name}}!</h1>
<a target="_blank" href="https://angular.io/start">
Learn more about Angular
</a>
<form [formGroup]="loginForm" (ngSubmit)="handleSubmit()">
<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>
<button>SUBMIT</button>
</form>
`,
})
export class App implements OnInit {
name = 'Angular';
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.value);
}
}
bootstrapApplication(App);
And this the input component:
import { CommonModule } from '@angular/common';
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { FormControl, ReactiveFormsModule, 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"
>
`,
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
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);
})
}
}
I have only added the (ngSubmit)
and changed the handleSubmit()
to print the value. Try it on Stackblitz and tell me if anything goes wrong.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With