Dynamically insert an element using RouterLink

In my Angular component, I have successfully added an anchor element like this:

<a [routerLink]="['/LoggedIn/Profile']">Static Link</a>

Clicking the link navigates to the desired component without any issues.

Now, I want to incorporate a similar dynamic link. Within my app, there is a "notification component" dedicated to displaying notifications.

The notification component looks something like this:

<div [innerHTML]="notification.content"></div>

Here, notification.content is a public string variable within the NotificationComponent class that holds the HTML content to be displayed.

The notification.content can include:

<div>Click on this <a [routerLink]="['/LoggedIn/Profile']">Dynamic Link</a> please</div>

While the dynamic link appears correctly on the screen, it does not function when clicked.

Is there a way to make the Angular router work with dynamically generated links like this?

PS: I am aware of DynamicComponentLoader, but I require a more flexible solution where various types of HTML content, including different kinds of links, can be sent to my notification component.

Answer №1

It is not possible to add routerLink after the content has been rendered, but you can still achieve the desired outcome:

  1. Create an anchor with dynamic data and assign it a class:

    `<a class="routerlink" href="${someDynamicUrl}">${someDynamicValue}</a>`
    
  2. Implement a HostListener in app.component that listens for clicks and uses the router for navigation

    @HostListener('document:click', ['$event'])
    public handleClick(event: Event): void {
     if (event.target instanceof HTMLAnchorElement) {
       const element = event.target as HTMLAnchorElement;
       if (element.className === 'routerlink') {
         event.preventDefault();
         const route = element?.getAttribute('href');
         if (route) {
           this.router.navigate([`/${route}`]);
         }
       }
     }
    }

Answer №2

routerLink is considered a directive. When working with Angular, directives and Components are not designed for HTML content that is injected using [innerHTML]. This means that the added HTML will not be processed by Angular in any way.

The recommended approach is to avoid using [innerHTML] altogether, and instead utilize DynamicComponentLoaderViewContainerRef.createComponent. This method involves wrapping the HTML within a component and dynamically adding it to the view.

For an illustration of this concept, you can refer to Angular 2 dynamic tabs with user-click chosen components.

Answer №3

Starting from angular 9, AOT has become the default and recommended way to compile angular projects. Unlike JIT, AOT does not maintain a compiler instance at runtime, making it impossible to dynamically compile angular code. While it is possible to disable AOT in angular 9, it is not advisable as it will result in a larger bundle size and slower application performance.

To address this issue, I implement a solution by adding a click listener at runtime using the renderer API, preventing the default behavior of URLs and invoking the angular router.

import { Directive, ElementRef, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import { Router } from '@angular/router';

@Directive({
  selector: '[hrefToRouterLink]'
})
export class HrefToRouterLinkDirective implements OnInit, OnDestroy {
  private _listeners: { destroy: () => void }[] = [];

  constructor(private _router: Router, 
  private _el: ElementRef, 
  private _renderer: Renderer2) {
  }

  ngOnInit() {
    // TODO how to guarantee this directive running after all other directives without setTimeout?
    setTimeout(() => {
      const links = this._el.nativeElement.querySelectorAll('a');
      links.forEach(link => {
        this._renderer.setAttribute(link, 'routerLink', link?.getAttribute('href'));
        const destroyListener = this._renderer.listen(link, 'click',
          (event) => {
            event.preventDefault();
            event.stopPropagation();
            this._router.navigateByUrl(link?.getAttribute('href'));
          });
        this._listeners.push({ destroy: destroyListener });
      });
    }, 0);
  }

  ngOnDestroy(): void {
    this._listeners?.forEach(listener => listener.destroy());
    this._listeners = null;
  }

}

You can view an example here : https://stackblitz.com/edit/angular-dynamic-routerlink-2

The method described above works for both JIT & AOT compilation. However, if you are still utilizing JIT and need to dynamically compile components (which may help resolve other issues), you can find an example here: https://stackblitz.com/edit/angular-dynamic-routerlink-1

References used :

Answer №4

After considering various answers, I decided to create a Directive for targeting specific elements that are being innerHTML'd in Angular. I wanted to avoid using querySelector and similar methods to keep everything Angulary.

One issue I encountered with the previous approaches was that if the href is a full URL (e.g., https://www.example.com/abc), navigating to /https would occur when feeding the whole URL to the router.

I also needed to implement checks to ensure that only hrefs within our domain were routed.

@Directive({
  selector: '[hrefToRouterLink]'
})
export class HrefToRouterLinkDirective {
  constructor(private _router: Router){}

  private _baseHref = quotemeta(environment.root_url.replace(`^https?://`, ''));
  private _hrefRe: RegExp = new RegExp(`^(https?:)?(\\/+)?(www\\.)?${this._baseHref}`, `i`);

  @HostListener('click', ['$event'])
  onClick(e) {
    // Is it a link?
    if (!(e.target instanceof HTMLAnchorElement)) 
      return;

    let href: string = e.target?.getAttribute('href')
      .replace(/(^\s+|\s+$)/gs, '');

    // Is this a URL in our site?
    if (!this._hrefRe.test(href))
      return;
      
    // If we're here, it's a link to our site, stop normal navigation
    e.preventDefault();
    e.stopPropagation();

    // Feed the router.
    this._router.navigateByUrl(
      href.replace(this._hrefRe, '')
    );
  }
}

The use of environment.root_url in the code above specifies our base domain, and quotemeta serves as an implementation of a Perl-esque quotemeta function for escaping special characters.

Your mileage may vary, and there could be some edge cases I've overlooked, but so far, this solution appears to be functioning effectively.

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

The Bootstrap modal causes the scrollbar to vanish upon closure

Despite trying numerous solutions and hacks on this issue from StackOverflow, none of them seem to work for me. Currently, I am utilizing a modal for the login process in Meteor (e.g. using Facebook login service) which triggers a callback upon successful ...

Discovering the perfect CSS unit for this specific number

I have an input field of type text <input type="text"> Currently, I am utilizing JavaScript's ClientRect to retrieve caret details. The ClientRect object structure is as follows: [object ClientRect] { [functions]: , __proto__: { } ...

TypeORM findManyToOne queries results in excessive and redundant query execution

I am currently working with a database table design structured as follows: Table Appointments: id| start_time| patientId |.. and other fields | And another table known as the Patient table: id| name | last_name | .. along with other fields | In my app ...

Using synthetic events in place of the values stored in useState

As I am relatively new to React, please bear with me as I attempt to explain the current issue. I am in the process of developing an application that retrieves all COD customer information and allows users to search through it. Additionally, it includes a ...

npm allows for multiple entry points for modules

I'm working on an NPM package and I'm curious about how to allow users to register multiple entry points. This way, they have the option to import either the entire library or just specific parts that they need. For instance, importing the compl ...

How can I use Angular 6 to design an interactive user interface with drag-and-drop functionality?

I am looking to make a dynamic form using Angular 7 with drag and drop functionality. The controls I need for building the form are: Check Boxes Multiple Headings Sub Headings Table + Formatted Tables + Unformulated Checkbox + text Text Fields + formatte ...

Encountering a 400 BAD REQUEST error while attempting to make an AJAX post request in

I'm struggling to grasp the concept of AJAX and I'm perplexed as to why this code snippet isn't functioning properly. When making an AJAX call, I keep encountering a 400 BAD REQUEST error, but the cause eludes me. Below is the AJAX functio ...

Angular 2 doesn't reflect changes in component variables in the view until mouseover happens

Since updating from angular2-alpha to the latest version, I've noticed that when a boolean value changes in my *ngIf directive, it doesn't reflect in the view until certain actions are taken. Here is the specific component code: declare var CKE ...

Managing numerous inquiries from a single customer within a succession of brief intervals

After creating a web application with a dashboard to showcase different reports and graphs based on user selections, I encountered an issue. Users can interact with the reports using checkboxes and radio buttons. Every time a checkbox or radio button is s ...

What role does the sequence play in matrix transitions that involve rotating and translating simultaneously?

Attempting to animate a matrix3d with both rotation and translation concurrently has yielded unexpected results for me. It seems that changing the order of applying rotation and translation produces vastly different outcomes. http://jsfiddle.net/wetlip/2n ...

Jquery code failing to trigger any response

Recently, I quickly created a jQuery script to dynamically populate a paragraph element in order to easily switch between player and server interaction options. However, I am currently facing an issue where my script does not populate as expected. I have a ...

You can activate the ability to click on the main tag by setting routerlink as the child component in Vue

Within my HTML code, I have utilized a RouterLink within an li tag to create a basic dropdown menu. Currently, when options are displayed in the dropdown menu, I am only able to click on the text itself to navigate to the next page. However, I would like t ...

Choose the OK button on the modal during the testing phase

I am having difficulty selecting an element on my webpage using testcafe because I am not very familiar with selectors. I am open to choosing it in the JSX or HTML, but I am struggling with both methods. Once I click the "LayerAddingPopUpButton", a modal a ...

retrieve the name of the button that was clicked

I am trying to access the button name in the onNextStep function, but e.target.value is not working. The button I have with an event attached is shown below: <button className="pull-right btn btn-success" onClick={this.onNextStep}>Next</button> ...

Determine the timing and add it to an array

I am currently working on an application that reads values from external devices and then writes these values to a database. The values I deal with include acceleration, gyroscope, magnetometer, and pressure. For acceleration, gyroscope, and magnetometer ...

Activate the stripe button after successful bootstrap validation

My goal was to implement BootstrapValidator for validation on a couple of fields and enable the Stripe button only when both fields are valid. Currently, the button is enabled once any of the fields pass validation. The challenge lies in ensuring that the ...

Troubleshooting: Issue with JQuery click functionality within Django's AJAX implementation

I've been tackling the Tango with Django exercises to get a better grip on Django. I'm almost finished, but I'm hitting a snag with the Ajax segment. The Ajax function for auto-adding a page isn't triggering. I can't seem to figur ...

Is requestAnimationFrame necessary for rendering in three.js?

I am currently working on the example provided in Chapter 2 of the WebGL Up and Running book. My goal is to display a static texture-mapped cube. The initial code snippet is not functioning as expected: var camera = null, renderer = null, scene = null ...

Struggling with modifying class in HTML using JavaScript

I've been attempting to replicate a JavaScript code I came across on the internet in order to create a functioning dropdown menu. The concept is quite straightforward - the div class starts as xxx-closed and upon clicking, with the help of JavaScript, ...

Display SVG at full size without any distortion

How can I make the SVG image 100% by 100% without scaling? I want the image to be centered on the page both horizontally and vertically. The challenge is that I also want to display the areas outside of the SVG artboard, so using CSS to center it won&apos ...