'How to fill select options from a directive?
Here is a simple select:
<select [(ngModel)]="..." name="..." id="..." required ...>
<option *ngFor="let o of options" [ngValue]="o.value">{{o.label}}</option>
</select>
Options is initialised like this:
class MyComponent() {
options;
constructor(someService: MyService) {
this.options = someService.getAllOptions();
}
}
So far, so good. Everything works fine. However the problem is that I need this select with exactly the same options at various locations. So there are many components which all have this options
-Property and load it from my service. That's a lot of code repetition I would like to avoid.
Obviously a component is an option, so that I can just write <mySelect ...>
, but the downside is, that I need to pass thru many other variables such as id
, class
, name
, required
and possibly more attributes. So I'd prefer a directive-solution, so I can write <select [(ngModel)]="..." name="..." ... myDirective>
and myDirective
should just add the options as needed. How can I do that?
Solution 1:[1]
In a directive you have easy access to the HTML element using an ElementRef
-Parameter and thus adding the element options is no problem. The key is, that you need to register the options with the SelectControlValueAccessor
. Usually an <option>
-Element is recognized by angular at compile time and an NgSelectOption
is created which registers itself in the constructor. Since you created that option element dynamically you need to do this step manually:
@Directive({
selector: '[myDirective]'
})
export class MyDirective {
constructor(someService: MyService,
element: ElementRef<HTMLSelectElement>,
renderer: Renderer2,
@Optional() @Host() select: SelectControlValueAccessor) {
someService.getAllOptions().forEach(co => {
const option = document.createElement('option');
option.text = co.displayName;
option.value = co.id;
element.nativeElement.add(option);
new NgSelectOption(new ElementRef(option), renderer, select);
});
}
}
Solution 2:[2]
Answering an old question, but I think it's worth the share.
Improving on the existing answer, the Angular way is not to interact directly with the global document
, but instead, use its built-in Renderer API.
@Directive({
selector: '[myDirective]'
})
export class MyDirective {
constructor(
someService: MyService,
element: ElementRef<HTMLSelectElement>,
renderer: Renderer2,
@Optional() @Host() select: SelectControlValueAccessor
) {
// remove previous options
this.element
.nativeElement
?.childNodes
.forEach((node) => this.renderer.removeChild(this.element.nativeElement, node));
someService.getAllOptions().forEach(co => {
// prepare the option element
const option = this.renderer.createElement('option');
const text = this.renderer.createText(co.displayName);
this.renderer.setValue(option, `${co.id}`);
// add the option text under option, and option element under select
this.renderer.appendChild(option, text);
this.renderer.appendChild(this.element.nativeElement, option);
// make the directive play nice with Angular forms
const ngOption = new NgSelectOption(new ElementRef(option), renderer, select);
ngOption.ngValue = value;
});
}
}
Advantage of this is that it's easier to port your code to run in environments that don't have DOM access, such as when you do server-side rendering, or build for native mobile, or run as a service worker.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | yankee |
Solution 2 | Christian |