Creating a custom Angular directive to dynamically insert an SVG element programmatically

I am working on an Angular 12 application and I want to create a custom directive for links that adds an external link icon (svg) after the link. Here is an example: https://i.sstatic.net/bOzB9.png

The usage of the directive would look like this:

<a externalLink [href]="url">View in Jira</a>

So far, I have not been able to programmatically insert an svg or an icon. One potential solution could be using `ComponentFactoryResolver` to dynamically create an icon component from our component library. Another option might be creating an svg element directly and appending it to the DOM. However, I have not successfully implemented either method yet, so any suggestions or tips are welcome! :)

I believe implementing this functionality as a directive would be cleaner and more lightweight, but I am open to considering creating it as a component if that simplifies the process. Please let me know if and how it's possible to achieve this using a directive.

This is what my directive looks like currently:

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('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 {

    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);

    let parent = this.renderer.parentNode(this.el.nativeElement);
    parent.appendChild(svg);
  }

}

Answer №1

I experimented with this concept and discovered that you can utilize a directive independently without a component by leveraging 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>

https://i.sstatic.net/eH8Yh.png

Check out the stackblitz example

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to store JSON data as an object in memory?

How do I convert the result of an HTTP request from JSON format to an object in TypeScript? Below is the code snippet: Component: import { Component } from '@angular/core'; import { DateTimeService } from './datetime.service'; import ...

Angularfire2 with Firebase - receiving notifications for added child in queried list

Is it possible to utilize the "child_added" event with queried lists? For example: this.curUserPosts = this.af.database.list('/posts', { query: { orderByChild: 'user/id', equalTo: id } }).$ref.on("child_added", (c ...

Issue in Typescript: "Implementing Partial is restricted to object types or intersection of object types with known members" error occurs when working with classes

Recently, I encountered an issue with my code that was previously working fine until I updated Typescript: class DefaultRouteConfig implements Partial<RouteConfig> { public meta = { layout: LayoutDefault }; } However, after the update, Typescript ...

The initial Angular 2 sample application is experiencing technical difficulties

I recently started an Angular2 course on Udemy and the instructor provided a seed project to kickstart the learning process. However, when I tried to run "npm install" to install all the modules in the package.json, the "node_modules" folder was not create ...

The parameter of type "Construct" cannot be assigned the argument of type "undefined"

I attempted to use code example from the AWS CDK documentation, but it did not function as I had anticipated. Using CDK version 2.62.2 with Typescript. In various parts of the code, a declaration error occurs stating that The argument of type "undefi ...

Unable to successfully import { next } from the 'express' module using Typescript

Having some trouble with this line of code: import {response, request, next} from 'express' The typescript compiler in vscode is giving me the following error: Module '"express"' has no exported member 'next'. Up ...

Encountering a fresh issue after updating to TS version 4.4.3 while accessing properties of the top "Object may be 'null'."

After upgrading my project to TypeScript 4.4.3 from 3.9.9, I encountered a change in the type declarations for the top property. My project utilizes "strictNullChecks": true, in its configuration file tsconfig.json, and is browser-based rather t ...

does not have any exported directive named 'MD_XXX_DIRECTIVES'

I am currently learning Angular2 and I have decided to incorporate angular material into my project. However, I am encountering the following errors: "has no exported member MD_XXX_DIRECTIVES" errors (e.g: MD_SIDENAV_DIRECTIVES,MD_LIST_DIRECTIVES). Her ...

Can custom types be incorporated into HTML templates in Angular 2?

Let's say we have an enumeration called CustomPages: export enum CustomPages{ page1 = 1, page2 = 2, page3 = 3 } Now, in our html template, let's try to access this enum. For instance: <a class="nav-link" [class.active]="page == Custo ...

Setting up tslint to verify for unused variables in Angular2

Have you figured out how to set up tslint for checking unused variables and imports in Angular 2? I've added these two lines in my tslint file: "no-unused-expression": true, "no-unused-variable": true, However, it doesn't seem to be working. An ...

Is the event emitter contained within the subscription?

I have an issue where I need to update my chart component after inputting values into a form field. However, I am unsure of how to accomplish this task. This is the code in my start.component.html file: <input [(ngModel)]="inputValue"/> <select ...

What is the best way to delete markers from a leaflet map?

I need to remove markers from my map. I am looking to create a function that will specifically clear a marker based on its ID. I am utilizing Leaflet for the map implementation. Here is my function: public clearMarkers(): void { for (var id in this. ...

Unexpected identifier error: Typescript interface name syntax is incorrect

I am currently learning Typescript and still navigating my way through it. I have extensively searched for a solution to the issue I am facing, but couldn't find one, hence I am seeking help here. The problem lies in a SyntaxError at the interface nam ...

Interacting with an iframe within the same domain

I'm currently working on an application in Angular 6 that requires communication with an iframe on the same origin. I'm exploring alternative methods to communicate with the iframe without relying on the global window object. Is there a more effi ...

Preventing duplicate namespace declarations in TypeScript

Let's say I have a variety of objects with the following data structure: { namespace: 'first'|'second'|'third' } Now, I need to include another object with the same data structure, but its namespace cannot be assigned ...

Is there a way to effectively transmit an observable array containing instances of Map<number, Employee> using the async pipe mechanism?

This is my Interface and Type Definition export interface EmployeeDetails { id: number; name: string; } export type EmployeesDirectory = Map<number, EmployeeDetails>; This is my Service Implementation class EmployeeServiceManager { employeesDa ...

The issue with Angular Material Dialog hiding certain elements

In my Node.js Angular project, I am trying to implement a confirm dialog which should be a simple task. Utilizing Material styling to speed up development process. However, upon running the project, the opened dialog appears to be empty: https://i.sstati ...

The data type 'string' cannot be assigned to type 'E164Number' in the react-phone-number-input component

I'm currently utilizing a library called react-phone-number-input. Within my project, I have two important files: index.tsx and useLogicRegister.ts. While working on my code, I encountered an error stating Type 'string' is not assignable to ...

Easily retrieve the value of a form control when it is changed with Form

Currently, I am in the process of reviewing Formly. In my initial attempt, I am trying to trigger a method when the content of a textarea changes. The code snippet below shows what I have tried so far. Unfortunately, it seems like the functionality is no ...

Creating a factory function through typhography

I have a dynamically generated list of functions that take an argument and return different values: actions: [ param => ({name: param, value: 2}), param => ({label: param, quantity: 4}), ] Now I am looking to create a function that will gen ...