Home > database >  Adding an svg programmatically using an angular custom directive
Adding an svg programmatically using an angular custom directive

Time:07-14

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: enter image description here

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>

Result

Here's a stackblitz

  • Related