0

I'm working on writing a component intended to simplify/homogenize the way our forms look and interact. The code looks something like this:

Example Usage

...
<my-form-input labelKey = "email" controlName="emailAddress" [form]="userForm">
    <input myInput class="form-control" type="email" formControlName="emailAddress" />
</my-form-input>
...

You can see that "emailAddress" is passed to MyFormInputComponent as the controlName and is passed a second time to the FormControlName directive on the <input> element. I'd like to only pass this once so that my end user doesn't have to do this.

Is there a good way I can go about this, or is this just a constraint I should accept (if yes, an explanation of why this constraint exists would be welcome)? Code is shown below.

I've tried two approaches:

  1. Setting a @HostBinding("attr.formControlName") annotation in the MyInput component. I can manipulate an attribute called formcontrolname on the element this way, but it doesn't trigger the directive that Angular Forms needs to properly register the control with the group.
  2. Ask the user to supply formControlName to the <input> element and read the value off of this for the rest of the component. This might work, but I'd have to access the DOM directly through an ElementRef, which is not recommended. The recommended route for interacting with DOM -- Renderer -- doesn't seem to expose any ability to read attributes either.

my-form-input.component.ts

@Component({
    selector: 'my-form-input',
    templateUrl: './my-form-input.component.html',
    styleUrls: ['./my-form-input.component.scss']
})
export class MyFormInputComponent implements OnInit, AfterContentInit {
    @Input()
    labelKey: string;

    @Input()
    controlName: string;

    @Input()
    form: FormGroup;

    @ContentChild(MyInputDirective)
    input: MyInputDirective;

    ngAfterContentInit(): void {        
        this.initInput();
    }

    /**
     * Updates the input we project into the template
     */
    private initInput() {
        this.input.updatePlaceholder(this.labelKey);
        // I'd like to somehow edit the FormControlName directive applied to the input here
    }
}

my-form-input.component.html

<label>{{ labelKey | translate }}</label>
<ng-content></ng-content>
<my-input-error [control]="form.controls[controlName]" [name]="labelKey | translate" />

my-input.directive.ts

@Directive({
    selector: '[myInput]'
})
export class myInputDirective implements OnInit {
    private placeholderKey = ""; 

    @HostBinding("placeholder")
    private placeholder: string;

    updatePlaceholder(placeholderKey: string) {
        this.placeholderKey = placeholderKey;
        this.placeholder = this.translateService.instant(this.placeholderKey);
    }

    constructor(private translateService: TranslateService) {
    }
}

my-form-error.component.ts

// Not shown since not relevant.
Sammaron
  • 196
  • 1
  • 3
  • 14

1 Answers1

0

I'm still not sure the exact explanation, but some reasoning alludes to where I might have strayed.

I assumed that my component owned the elements that it was projecting, but I actually think this isn't true. Your opportunity to set attributes/directives is in the template. This means you are better off including any elements that you want to own/control into the template rather then just projecting them.

In this case, that leads you to making separate components for specific form controls (like <input>, <textarea>, etc). This is what I've ended up doing. This is better from a design endpoint anyway -- one component to wrap all possible form-controls was never going to happen. Either project a form control that duplicates some properties, like I do in the question, or create specific components. Or both (just make your most common form controls into components, wrap the one-off problems in your projecting component).

Here are some blogs that helped me find my way:

  1. https://medium.com/@vadimkorr/implementing-nested-custom-controls-in-angular-5-c115c68e6b88
  2. https://blog.angularindepth.com/never-again-be-confused-when-implementing-controlvalueaccessor-in-angular-forms-93b9eee9ee83
Sammaron
  • 196
  • 1
  • 3
  • 14