wertzui
wertzui

Reputation: 5720

Angular 19: Implement Directive like FormControlName

I want to implement my own Property directive which should act similar to the existing FormControlName directive. It should use the name of the given Property to act as if it where given as formControlName.

interface Property {
    name: string;
    // more
}

Basically I want these two to be equivalent:

<form [formGroup]="myFormGroup">
    <input type="text" [property]="myProperty" />
</form>
<form [formGroup]="myFormGroup">
    <input type="text" [formControlName]="myProperty.name" />
</form>

My approach was to extend FormControlName but that did not work out, as the <input> element stayed empty.

@Directive({
    selector: '[property]',
    providers: [
        {
            provide: NgControl,
            useExisting: forwardRef(() => PropertyDirective),
        }
    ],
})
export class PropertyDirective<TProperty extends Property<SimpleValue, string, string>> extends FormControlName {
    public readonly property = input.required<TProperty>();
    constructor(
        @Optional() @Host() @SkipSelf() parent: ControlContainer,
        @Optional() @Self() @Inject(NG_VALIDATORS) validators: (Validator | ValidatorFn)[],
        @Optional()
        @Self()
        @Inject(NG_ASYNC_VALIDATORS)
        asyncValidators: (AsyncValidator | AsyncValidatorFn)[],
        @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
    ) {
        super(parent, validators, asyncValidators, valueAccessors, null);
        effect(() => {
            const property = this.property();
            if (!property) {
                return;
            }

            this.name = property.name;

        });
    }
}

How can I create a directive that works like the example template given above?

Upvotes: 1

Views: 60

Answers (1)

Nikolya Shirshov
Nikolya Shirshov

Reputation: 289

  1. In addition to the correct implementation of the Property directive with overriding the ngOnChanges hook to initialize the control without errors, since ngOnChanges for a formControlName with an empty name property will work before effect.

Code:

@Directive({
  selector: '[property]',
})
export class PropertyDirective<TProperty extends Property> extends FormControlName{
  public readonly property = input.required<TProperty>();
  constructor(
    @Optional() @Host() @SkipSelf() parent: ControlContainer,
    @Optional() @Self() @Inject(NG_VALIDATORS) validators: (Validator | ValidatorFn)[],
    @Optional()
    @Self()
    @Inject(NG_ASYNC_VALIDATORS)
    asyncValidators: (AsyncValidator | AsyncValidatorFn)[],
    @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
  ) {
    super(parent, validators, asyncValidators, valueAccessors, null);
  }

  override ngOnChanges(changes:SimpleChanges) {
    this.name = this.property().name;
    super.ngOnChanges(changes);
  }
}
  1. You will also need to implement controlValueAccessor for Property since defaultValueAccessor is bound to specific selectors using formControlName (example: input:[formControlName]).

Code:

@Directive({
  selector: '[property]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PropertyValueAccessorDirective),
      multi: true,
    }
  ]
})
export class PropertyValueAccessorDirective extends DefaultValueAccessor {}

Upvotes: 1

Related Questions