1

I currently have an text control input component that acts as the input field for my form. I am reusing this for each piece of input data I need e.g. Name, Email, Password etc.

I have setup the component to take required, minLength and maxLength (which currently don't display an error and is part of my problem), and would also like them to use the inbuilt Angular directives for validation: pristine, dirty, touched etc which will then display the validation message in the parent form component.

I cannot work out how to apply these directives to the instance of the child component as it doesn't seem to reach the input field itself and I need to apply different directives to the different instances E.g. I need Name not to contain a number, but I do need Password to contain a number, therefore using different directives.

text-control.component.html

<input #textControlInput readonly="{{getReadOnly()}}" className="{{getCSSClasses()}}" [id]="id" [name]="name"
      [value]="modelValueDisplay" [type]="type" [minLength]="minLength" [maxLength]="maxLength" [size]="size"
      [placeholder]="placeholder" [required]="required" (input)="onChange()" (change)="onChange()"  />

text-control.component.ts

import {
  Component,
  EventEmitter,
  Input,
  Output,
  ViewChild,
  ElementRef,
} from '@angular/core';

@Component({
  selector: 'app-text-control',
  templateUrl: './text-control.component.html',
  styleUrls: ['./text-control.component.scss'],
})
export class TextControlComponent  {
  @Input() className: string;
  @Input() disabled = false;
  @Input() form: string;
  @Input() id: string;
  @Input() minLength: number;
  @Input() maxLength: number;
  @Input() name: string;
  @Input() pattern: string;
  @Input() placeholder: string;
  @Input() suffix: string;
  @Input() size = 25;
  @Input() type = 'text';
  @Input() value: string;
  @Input() public required = false;

  @ViewChild('textControlInput') textControlInput: ElementRef;

  public label: string;
  touched = false;

  constructor() {}

  ngOnInit() {}

  ngAfterViewInit() {}

  getCSSClasses() {
      return 'TextControl' + (this.className ? ' ' + this.className : '')
        + (this.multiLine ? ' multiLine' : '') + (this.disabled ? ' disabled' : '') ;
  }

}

parent.component.html

<app-text-control id="{{idPrefix}}firstName" name="{{idPrefix}firstName" [type]="'text'" [readOnly]="readOnly"
          className="g-mb-1" [size]="15" [(ngModel)]="loginDetails.firstName" [placeholder]="'First Name'"
          required="true" minLength="3" maxLength="100" #firstName=ngModel>
        </app-text-control>

parent.component.ts

import {
  Component,
  OnInit,
  Input,
  Output,
  EventEmitter,
  ViewChild,
} from '@angular/core';

@Component({
  selector: 'app-form-login-details',
  templateUrl: './form-login-details.component.html',
  styleUrls: ['./form-login-details.component.scss'],
})
export class FormLoginDetailsComponent implements OnInit {
  @Input() readOnly: boolean;
  @Input() idPrefix = '';
  @Input() loginDetails = new LoginDetails();
  @Output() loginDetailsChange = new EventEmitter<LoginDetails>();
  @Output() selectChange = new EventEmitter<any>();

  constructor() {}

  ngOnInit() {}


1 Answer 1

2

Template driven forms do not work like that. First of all you need to add viewProviders to your child component, so that it knows that this is a form field :)

viewProviders: [{ provide: ControlContainer, useExisting: NgForm }]

Then... you cannot add the [(ngModel)] on the child tag. NgModel just work on (form)fields, inputs and such. So that has to be added to the child component. Same goes for #firstName=ngModel. We don't even actually need this, if we pass a model and name to child... which is good, because you cannot create template references dynamically.

So simplified example of your code (which you should strive to achieve, for easier debugging, lots of noise in your code):

<form #f="ngForm">
  <app-text-control [required]="true" [name]="name" [model]="null"></app-text-control>
</form>

If you want some initial value for your input, add that to model. I have now just passed null.

Then the child component (including template):

@Component({
  selector: 'app-text-control',
  template: `
    <input [(ngModel)]="model" [name]="name" [required]="required"/>
  `,
  viewProviders: [{ provide: ControlContainer, useExisting: NgForm }]
})
export class TextControlComponent {
  @Input() model: string;
  @Input() name: string;
  @Input() public required = false;
}

In this example you can see as only validator I passed from parent to child is required. You can though pass as many as you have :)

Here's a STACKBLITZ for your reference.

Lastly... off-topic but important! ;)

If your forms become more complicated... like child components for example, I really suggest you look into reactive forms :) You'd just need to pass a form control to the child and eeeeverything else would be taken care of automatically! The form control would include value, validations etc, everything in one package! Just a short off-topic suggestion ;)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.