Home > Net >  How can a template-driven form access a control inside a component?
How can a template-driven form access a control inside a component?

Time:07-02

I have a <form> that uses the Template Driven approach.

The form contains a mix of native controls, like <input> and <select>, and also wrapper components, like <text-control> and <checkbox-control> that contains the native <input> element inside of it.

How can the form access the <text-control>'s native element to read its error and touched state?

Also, if I want to place a validator directive on that wrapper component and have it pipe-down to the native <input>, how can I approach this?

I tried to use ngModel on the wrapper component, but it doesn't work, since ngModel is hooking to the wrapper component, not the underlying <input> element.

Another approach was to use <ng-content>, but the underlying native element takes a lot of attributes, so much that it'll be a pain to copy-n-paste it everywhere.

Example:

<checkbox-control>
  <!-- Now, it's easy for the form to access the control. -->
  <!-- But I've too many attributes and properties that go here. -->
  <input type="checkbox">
</checkbox-control>

PS: I am not looking to use ElementRef to access the native element, I just want the <form> to be aware of the native elements' error state, so that I can tell whether the form is valid or not.

Example:

<form #editor="ngForm">
  <input type="text" validateText />

  <select validateSelect>
    <option value="1"></option>
    <option value="2"></option>
  </select>

  <!-- Does not work. -->
  <checkbox-control validateCheckbox></checkbox-control>
</form>

Thanks in advance.

CodePudding user response:

You can use element's local reference in template approach. Please refer to this code I just wrote to explain :

https://stackblitz.com/edit/angular-ivy-7no9ok?file=src/app/app.component.html

CodePudding user response:

I found a reasonable solution that works without writing much code, and without editing too many components.

Step 1: Create your validator

@Directive({ selector: '[assertNoSpecialChars]' })
export class SpecialCharacterValidator implements Validator {
  // The directive will have a reference for the name of the form's control.
  @Input() assertNoSpecialChars: string = '';

  validate(group: FormGroup): ValidationErrors | null {
    const control = group.controls[this.assertNoSpecialChars];

    // For simplicity, let's say we don't want the dollar sign in our input.
    if (control.value.includes('$')) {
      return { invalid: true };
    } else {
      return null;
    }
  }
}

Step 2: Apply the directive on your form

<form #f="ngForm" [assertNoSpecialChars]="'username'">
  <text-control></text-control>
</form>

Step 3: Bind the input event of your component to the hosting form component

<form #f="ngForm" [assertNoSpecialChars]="'username'">
  <text-control (input)="updateFormManually($event, 'username')"></text-control>
</form>

Step 4: Get a reference for your NgForm, and implement the update mechanism

@Component({})
export class FormComponent implements AfterViewInit {
  // Grab a reference for your NgForm.
  @ViewChild('f', { static: true }) f!: NgForm;

  // Create your form control.
  username: FormControl = new FormControl();

  // Register your control to the NgForm.
  ngAfterViewInit(): void {
    this.f.form.addControl('username', this.username);
  }

  // Update the control manually.
  updateFormManually(event: any, controlName: string): void {
    this.f.form.controls[controlName].setValue(event.target.value);
  }
}

Now, the form's validity state, and error messages will be correctly bound.

  • Related