Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i create reusable input field in Angular?

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);
    })
  }
}

like image 266
Robin Singh Avatar asked Oct 16 '25 19:10

Robin Singh


2 Answers

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>();
}
like image 108
Joel Joseph Avatar answered Oct 18 '25 09:10

Joel Joseph


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.

like image 26
Flo Avatar answered Oct 18 '25 09:10

Flo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!