Angular @ViewChild: How to read multiple tokens (ElementRef, ViewContainerRef, component instance, ….) from the same element
Caveats: it won’t work with AOT
When working with angular2 you often use @ViewChild decorator to get access to specific instance from the containing element.
@ViewChild decorator has the following signature:
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;And when angular reads value it uses getQueryValue function:
export function getQueryValue(
view: ViewData, nodeDef: NodeDef, queryValueType: QueryValueType): any {
if (queryValueType != null) {
// a match
let value: any;
switch (queryValueType) {
case QueryValueType.RenderElement:
value = asElementData(view, nodeDef.index).renderElement;
break;
case QueryValueType.ElementRef:
value = new ElementRef(asElementData(view, nodeDef.index).renderElement);
break;
case QueryValueType.TemplateRef:
value = asElementData(view, nodeDef.index).template;
break;
case QueryValueType.ViewContainerRef:
value = asElementData(view, nodeDef.index).viewContainer;
break;
case QueryValueType.Provider:
value = asProviderData(view, nodeDef.index).instance;
break;
}
return value;
}
}So we can specify selector and read token and get desired instance.
For example, let’s say we have directive and component as:
@Directive({
selector: '[dir]'
})
export class SomeDirective {}
export class SomeService {}
@Component({
selector: 'some-component',
template: `Some component`,
providers: [SomeService]
})
export class SomeComponent {}and we use it in parent component like:
<some-component #someComp dir>And we really want to get component instance and also the corresponding ElementRef, applied SomeDirective and even SomeService that has been provided within providers metadata of SomeComponent. So we can write:
@ViewChild(SomeComponent) someComponent;
// or the same
@ViewChild('someComp') someComponent;@ViewChild(SomeComponent, { read: ViewContainerRef }) vcRef;
@ViewChild(SomeComponent, { read: SomeDirective }) someDir;
@ViewChild(SomeComponent, { read: ElementRef }) elRef;
@ViewChild(SomeComponent, { read: SomeService }) someService;
But it would be great if we could write it something like this:
@ViewChild(SomeComponent, {
read: [ElementRef, SomeComponent, SomeDirective, SomeService]})
instances;and get array of all instances
I can provide my solution for that:
multi-viewchild.decorator.ts
import { ViewChild } from '@angular/core';
export function MultiViewChild(selector, opts: { read: any[] }) {
const tokens = opts.read;
const decs = tokens.map(x => ViewChild(selector, { read: x }));
return function(target: any, name: string) {
decs.forEach((d, i) => d(target, `${name}_${i}`)); Object.defineProperty(target, name, {
get: function() {
return decs.map((x, i) => this[`${name}_${i}`], this);
}
});
};
}
Then i can use it like:
@MultiViewChild(SomeComponent,
{
read:
[
ElementRef,
SomeComponent,
SomeDirective,
SomeService,
ViewContainerRef
]
})
instances;It will give us array of instances in the same order we’ve passed types to read property.
Here is the plunker that demonstrates the solution.
That’s all.
