Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2+ pass directives to a custom component

I created a custom component which have it' own @input(), @output and so on. This component has a <input /> field where a user can enter some value.

E.g: <my-component ...></my-component>

I reference it in my html and it works flawlessly. I also created several directives which validate form input data via simple regexps. I can use them in plain input inside a form like:

<input type="text" validator1 validator2 validator3 />

Is there a way to pass one or more of these directive (but also none of them) to my custom component without hardcoding them in the source of the component?

Some kind of ...params to evaluate?

Thanks in advance for all your help

Valerio

like image 528
Valerio Avatar asked Jan 04 '18 10:01

Valerio


1 Answers

The pattern you're looking for is definitely possible, but not achievable with directives in a sense you're trying to. This is due the fact that Angular is compiled, meaning you cannot not "hard-code" a directive (at least not without doing weird stuff that's not recommended to do in production).

Your component can accept an input named validators, which should be an array of functions (or instances of a class, if you need it), and then use that to validate.

For example, you can have the following three super-simple validators:

export const required   = value => value != null && value != ''
export const minLength3 = value => value == null || value.length > 3
export const maxLength9 = value => value == null || value.length < 9

Your my-component accepts an array of these validators. For simplicity sake, a validator is actually a predicate of a string. In other words, it is a function with the same signature as three functions above: (value: string) => boolean. We initialize this input as an empty array, effectively making this the default value in case nothing is passed down to it.

@Input() validators: ((value: string) => boolean)[] = []

In the consumer component's template (the component using my-component), we now use the component by passing down an array of validators to it.

<my-component [validators]="[required, maxLength9]"></my-component>

Of course, to use them, we have to either DI them or simply instantiate them as members of the component class. To use it with DI, validators would have to be classes (at least as far as versions 5.x.x and below go).

import {required, maxLength9} from '../validators'
export class ConsumerComponent {
  public required = requierd
  public maxLength9 = maxLength9
}

The my-component component should, of course, make use of these validators. For example, the following function can be run on each change or input or blur event, depending on when do you want to run the validators.

public validate(value: string): boolean {
  let valid: boolean = true
  this.validators.forEach(validator => {
    const result = validator(value)
    valid = valid || result
  })
}

You now have better dynamic control of which validators you want to run on the field. You can also change these dynamically during run-time of the application, of course. This comes at the following cost: no tree-shaking of unused validators. Angular compiler can no longer determine which validators are you using, which means that all of them have to be imported in the final bundle of your app, even though some of them might never be used.


You might be interested in reactive forms in Angular. You can read about reactive forms in official documentation, or take a look at Todd Motto's article on reactive forms in Angular, or Reactive Forms in Angular by Pascal Precht on thoughtram.

like image 142
Lazar Ljubenović Avatar answered Sep 24 '22 03:09

Lazar Ljubenović