I have an Angular 12 application, where I'd like to add a custom directive for Elements that adds an external link icon (svg). The icon should be added after the link, like this:
The directive should be used like this: <a externalLink [href]="url">View in Jira</a>
I've not been able to insert an svg or an icon programmatically. We have a component library that has an icon (similarly to MatIcon
) so a possible solution might be to use ComponentFactoryResolver
to dynamically create an icon component, or another possible solution might be to create an svg element directly and add that.. I haven't gotten either to work yet, so I'm open for suggestions and tips! :)
I'd like to realise this as a directive, because I think it should be possible, and then it's just neater and more lightweight to use. But of course creating this as a component would be easier, because it doesn't require any programmatic messing with the dom.. please let me know if/how this would be possible to do using a directive :)
My directive so far looks like this (a lot of trial and error):
import { ComponentFactory, ComponentFactoryResolver, Directive, ElementRef, HostBinding, OnInit, Renderer2, TemplateRef, ViewContainerRef, } from '@angular/core';
import { CustomIcon } from '@company/components-lib/custom-icon';
@Directive({
selector: 'a[externalLink]',
})
export class ExternalLinkDirective implements OnInit {
// @HostBinding('id') readonly elementClass = 'external-link';
/* enforce external links to open in a new tab */
@HostBinding('attr.target') readonly target = '_blank';
svgContent = '';
constructor(
private el: ElementRef,
private renderer: Renderer2,
private componentFactory: ComponentFactory<unknown>,
private readonly templateRef: TemplateRef<unknown>,
private readonly viewContainer: ViewContainerRef,
private readonly componentFactoryResolver: ComponentFactoryResolver
) {
}
ngOnInit(): void {
/* tried to add the actual svg to the innerHTML of the svg comoponent */
this.svgContent =
`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">`
`<path d="M18.75 11.8869V18.75H5.25V5.25H12.1131L9.86306 3H3.75C3.33577 3 3 3.33581 3 3.75V20.25C3 20.6642 3.33577 21 3.75 21H20.25C20.6642 21 21 20.6642 21 20.25V14.1369L18.75 11.8869V11.8869Z" fill="#3ad4dd" />`
`<path d="M13.0449 3L15.9617 5.9168L9.87854 12L11.9999 14.1213L18.0831 8.03812L20.9999 10.955V3H13.0449Z" fill="#3ad4dd" />`
`</svg>`;
let svg = this.renderer.createElement('svg');
this.renderer.setAttribute(svg, 'innerHTML', this.svgContent);
console.log(this.svgContent);
/* tried to add the component using ComponentFactoryResolver, since ComponentFactory is marked as deprecated in Angular 14 https://angular.io/api/core/ComponentFactory */
// let icon = this.componentFactoryResolver.resolveComponentFactory(CustomIcon);
// let icon = this.renderer.createElement('custom-icon');
// icon.setAttribute('class', 'external-link');
let parent = this.renderer.parentNode(this.el.nativeElement);
// parent.appendChild(icon);
parent.appendChild(svg);
// let iconElement = document.createElement('custom-icon', { name: 'external-link' });
}
}
CodePudding user response:
I played around with it a little and you can use a directive without a component by using elementRef
and renderer
@Directive({ selector: '[external]' })
export class ExternalLinkDirective implements OnInit {
@Input() size: string = '16';
@Input() color: string = '#3ad4dd';
@Input() viewBox = '0 0 24 24';
constructor(
private elementRef: ElementRef,
private render: Renderer2
) {}
ngOnInit() {
const svg = this.render.createElement('svg', 'svg');
this.render.setAttribute(svg, 'width', this.size);
this.render.setAttribute(svg, 'height', this.size);
this.render.setAttribute(svg, 'viewBox', this.viewBox);
this.render.setAttribute(svg, 'fill', 'none');
const p1 = this.render.createElement('path', 'svg');
const p2 = this.render.createElement('path', 'svg');
this.render.setAttribute(
p1,
'd',
'M18.75 11.8869V18.75H5.25V5.25H12.1131L9.86306 3H3.75C3.33577 3 3 3.33581 3 3.75V20.25C3 20.6642 3.33577 21 3.75 21H20.25C20.6642 21 21 20.6642 21 20.25V14.1369L18.75 11.8869V11.8869Z'
);
this.render.setAttribute(
p2,
'd',
'M13.0449 3L15.9617 5.9168L9.87854 12L11.9999 14.1213L18.0831 8.03812L20.9999 10.955V3H13.0449Z'
);
this.render.setAttribute(p1, 'fill', this.color);
this.render.setAttribute(p2, 'fill', this.color);
this.render.appendChild(svg, p1);
this.render.appendChild(svg, p2);
this.render.appendChild(this.elementRef.nativeElement, svg);
}
}
<!-- app.component.html -->
<a href="https://google.com" external color="red">google</a>
<a href="https://linkedin.com" external color="green">LinkedIn</a>
<a href="https://ebay.com" external color="#ffcc00">E-Bay</a>