Extend signal form’s FormField directive

Summary

The AxFormFieldDirective is designed to extend the functionality of the FormField directive in Angular 21, adding custom classes to show valid/invalid states on input fields. However, the current implementation does not add classes when the field is touched, and the typed value is not reflected in the form model.

Root Cause

The root cause of the issue lies in the incorrect usage of inject and input decorators, as well as the missing implementation of the formField input in the directive. The causes include:

  • Incorrect usage of inject decorator for FormField
  • Missing implementation of formField input in the directive
  • Incorrect usage of input decorator for validClass and invalidClass

Why This Happens in Real Systems

This issue occurs in real systems due to:

  • Lack of understanding of Angular’s dependency injection system
  • Insufficient knowledge of Angular’s directive lifecycle
  • Incorrect usage of Angular’s decorators and APIs
  • Inadequate testing and debugging of custom directives

Real-World Impact

The impact of this issue includes:

  • Invalid or incomplete form data
  • Inconsistent user interface
  • Difficulty in debugging and troubleshooting form-related issues
  • Potential security vulnerabilities due to incorrect form handling

Example or Code

import { Directive, ElementRef, inject, input, Renderer2 } from '@angular/core';
import { FormField } from '@angular/forms/signals';

@Directive({
  selector: '[axFormField]',
  hostDirectives: [
    {
      directive: FormField,
      inputs: ['formField: axFormField']
    }
  ]
})
export class AxFormFieldDirective {
  private el = inject(ElementRef);
  private renderer = inject(Renderer2);
  @input() formField: any;
  @input() validClass: string;
  @input() invalidClass: string;

  constructor() { }

  ngAfterViewInit() {
    const state = this.formField.formField();
    const touched = state.touched;
    const valid = state.valid;
    const invalid = state.invalid;

    this.toggleClass(this.validClass, touched && valid);
    this.toggleClass(this.invalidClass, touched && invalid);
  }

  private toggleClass(className: string, enabled: boolean) {
    const element = this.el.nativeElement;
    if (enabled) {
      this.renderer.addClass(element, className);
    } else {
      this.renderer.removeClass(element, className);
    }
  }
}

How Senior Engineers Fix It

Senior engineers fix this issue by:

  • Correctly implementing the formField input in the directive
  • Using the @input decorator to bind the formField property
  • Utilizing the ngAfterViewInit lifecycle hook to access the form field state
  • Correctly using the Renderer2 to add and remove classes from the input element

Why Juniors Miss It

Juniors may miss this issue due to:

  • Lack of experience with Angular’s dependency injection system
  • Insufficient knowledge of Angular’s directive lifecycle
  • Incorrect usage of Angular’s decorators and APIs
  • Inadequate testing and debugging of custom directives
  • Overlooking the importance of correctly implementing inputs and lifecycle hooks in custom directives.

Leave a Comment