This post is about a Component created in the context of our application development.There is a demo here, and you can find the full source code here.
Lately, the need arose to create a tooltip directive. This brought up a lot of questions we hadn’t had to face before, such as how to create markup wrapping around rendered content, or rather“what is Angular 2’s transclude?”
Turns out, using TemplateRef is very useful for this, but the road to understanding it wasn’t easy. After seeing it used in a similar fashion by Ben Nadel, I decided to take a stab at it.
TemplateRef is used when using <template> elements, or perhaps most commonly when using *-prefixed directives such as NgFor of NgIf. For *-prefixed directives (or directives in <template>elements, TemplateRef can be injected straight into the constructor of the class. For other components, however, they can be queried via something like the ContentChild decorator.
Initially, I had thought to create two directives: a TooltipDirective to be placed on the parent element, plus a TooltipTemplate directive to be placed in a template, that would then inject itself into the parent. It proved too complex, though, and after finding what could be done with the ContentChild query the implementation became much simpler.
The end result looks like this (simplified for clarity):
@Directive({
selector:
"[tooltip]"
})
export
class
TooltipDirective
implements
OnInit {
@Input(
"tooltip"
)
private
tooltipOptions: any;
@ContentChild(
"tooltipTemplate"
)
private
tooltipTemplate: TemplateRef <Object>;
private
tooltip: ComponentRef<Tooltip>;
private
tooltipId: string;
constructor (
private
viewContainer: ViewContainerRef,
public elementRef: ElementRef,
private
componentResolver: ComponentResolver,
private
position: PositionService ) {
this
.tooltipId = _.uniqueId(
"tooltip"
);
}
ngOnInit () {
// Attach relevant events
}
private
showTooltip () {
if
(
this
.tooltipTemplate) {
this
.componentResolver.resolveComponent(Tooltip)
.then(factory => {
this
.tooltip =
this
.viewContainer.createComponent(factory);
this
.tooltip.instance[
"content"
] =
this
.tooltipTemplate;
this
.tooltip.instance[
"parentEl"
] =
this
.elementRef;
this
.tooltip.instance[
"tooltipOptions"
] =
this
.options;
});
}
}
private
hideTooltip () {
this
.tooltip.destroy();
this
.tooltip = undefined;
}
private
get options (): TooltipOptions {
return
_.defaults({},
this
.tooltipOptions || {}, defaultTooltipOptions);
}
}
@Component({
selector:
"tooltip"
,
template:
`<div
class
=
"inner"
>
<template [ngTemplateOutlet]=
"content"
></template>
</div>
<div
class
=
"arrow"
></div>`
})
class
Tooltip
implements
AfterViewInit {
@Input()
private
content: TemplateRef <Object>;
@Input()
private
parentEl: ElementRef;
@Input()
private
tooltipOptions: TooltipOptions;
constructor (
private
positionService: PositionService,
public elementRef: ElementRef)
{}
private
position() {
// top and left calculated and set
}
ngAfterViewInit(): void {
this
.position();
}
}
|
The TooltipDirective requires a <template #tooltipTemplate> element, that gets rendered through a Tooltip Component, created and injected with the templateRef containing our content. Essentially, “transcluding” it. The Tooltip component’s role is only to wrap the content with some light markup, and position itself when inserted into the page.
A lot of the actual positioning (not shown here, but in the source code) is done directly to the rendered elements, though – I faced some issued when using the host properties object, that I believe were reintroduced in the latest RC.
All in all, it was a great learning experience, and Angular 2’s <template> surely beats Angular.js’transclude. Slowly but surely Angular 2 get’s more and more demystified to me, but it is hard work getting there.
原文链接: https://stacksandfoundations.com/2016/07/13/using-templateref-to-create-a-tooltippopover-directive/