Why Directives are the Go-To Choice for Select Component Options Reuse in Angular

Netanel Basal
Netanel Basal
Published in
2 min readJan 16, 2023

--

Our application has various select components that are frequently used throughout the application, such as “select user,” “select wallet,” and “select blockchain” components.

One approach to managing these components would be to individually query the options and pass them to the select component each time they are needed; however, this approach isn’t DRY.

An alternative solution is to create a wrapper component for each select component that encapsulates its logic. For example, we could create a UsersSelect component that is reusable throughout the application:


@Component({
selector: 'app-users-select',
standalone: true,
imports: [SelectComponent, CommonModule],
template: `
<app-select
placeholder="Select user"
[options]="users$ | async"
[allowClearSelected]="allowClearSelected"
></app-select>
`
})
export class UsersSelectComponent implements ControlValueAccessor {
@Input() allowClearSelected: boolean;

users$ = inject(UsersService).getUsers().pipe(
map((users) => {
return users.map((user) => {
return {
id: user.id,
label: user.label,
};
});
})
)

// ... ControlValueAccessor...
}

Although that approach works, it’s not optimal. First, each component would require a new control value accessor. Moreover, to allow consumers to set inputs and listen for outputs, the select component must proxy its API to the wrapped component.

To address these issues, we can utilize directives. Rather than creating a separate component for each select options, we can create a reusable directive that can be used to add options to the select component:

import { Directive, inject } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';

@UntilDestroy()
@Directive({
selector: '[usersSelectOptions]',
standalone: true,
})
export class UsersSelectOptionsDirective {
private select = inject(SelectComponent);
private users$ = inject(UserService).getUsers();

ngOnInit() {
this.select.placeholder = 'Select user';

this.users$.pipe(untilDestroyed(this)).subscribe((users) => {
this.select.options = users.map((user) => {
return {
id: user.id,
label: user.name,
};
});
});
}
}

We obtain a reference to the SelectComponent using DI. In the ngOnInit lifecycle hook, the select.placeholder property is set to “Select user”.

We fetch the users and sets the select options. Now, we can use it in our select component:

<app-select
usersSelectOptions <=====
formControlName="userId"
[allowClearSelected]="false"
></app-select>

Follow me on Medium or Twitter to read more about Angular and JS!

--

--

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.