6

I am using Angular 6. I just want to create a dynamic route by using the innerHTML. I tried the following code but it is not working. In the component file, I wrote the following code inside the constructor:

this.anchor = "<a routerLink='login'>Dynamic Login</a>"

In the template file, I wrote the following code:

<div [innerHtml]="anchor"></div>
Yuri
  • 4,254
  • 1
  • 29
  • 46

7 Answers7

15

There is one hacky solution that I used when encountered same problem as you did since I didn't find anything better. In template I had span where I was setting innerHtml that html was coming from server and it could have a tag as well as any other formatting html tags. So the solution is to add click event:

<span [innerHtml]="customHtml" (click)="processLinks($event)"></span>

And in our ts file process them:

customHtml: string = 'Hey <a href="/help/">this is</a> a link';

// Don't forget to import and inject the router
constructor(private router: Router) {}

// ...

processLinks(e) {
  const element: HTMLElement = e.target;
  if (element.nodeName === 'A') {
    e.preventDefault();
    const link = element.getAttribute('href');
    this.router.navigate([link]);
  }
}

Notice that you can do anything with the link here, if you need you can add some conditions or transform links before navigating to them.

Hope it helps. I know it's not the best solution, there must be something better. But that's what I came up with in a rush.

Alex Chebotarsky
  • 481
  • 5
  • 19
2
this.anchor = "<a routerLink='login'>Dynamic Login</a>"
<div [innerHtml]="anchor"></div>

Above codes won't cause Angular to process anything Angular-specific in anchor.

Angular replaces Angular specific markup at build time with generated code. Markup added at runtime won't be processed by Angular.

To add HTML that contains Angular-specific markup (property or value binding, components, directives, pipes, ...) it is required to add the dynamic module and compile components at runtime. This answer provides more details How can I use/create a dynamic template to compile dynamic Component with Angular 2.0?

Alex Chebotarsky
  • 481
  • 5
  • 19
shhdharmen
  • 1,413
  • 10
  • 20
0

@Alex Chebotarsky solution is good and prooves the concept.

I can show you how I've used it as a pipe for long description where you encounter more links.

  1. I've created a pipe to replace some words with <a [routerLink]="build/your/${url},{$here}">{initialWord}

docs.pipe.ts

@Pipe({ name: 'docs' })
export class DocsPipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}
  transform(value: string, item: IStack): SafeHtml {
    value = value.replace(DocsKeywords.language, `<a class="doc-type" [routerLink]="/tabs/search,${item.area[0]},${item.id}">${DocsKeywords.language}</a>`);
    value = value.replace(DocsKeywords.frontend, `<a class="doc-type" [routerLink]="/tabs/search,${item.area[0]},${item.id}">${DocsKeywords.frontend}</a>`);
    value = value.replace(DocsKeywords.framework, `<a class="doc-type" [routerLink]="/tabs/search,${item.area[0]},${item.id}">${DocsKeywords.framework}</a>`);

    return this.sanitizer.bypassSecurityTrustHtml(value);
  }
}

Afterwards my rendered HTML & DOM looks like this: enter image description here enter image description here

  1. Then in your component where you use it

some-component.html

<p class="description" [innerHtml]="item.longDescription | docs: item">

some-component.ts

  @HostListener('click', ['$event'])
  onClick(e) {
    if (e.target.classList.contains("doc-type")) {
      const urlSegments = e.target.getAttribute('[routerlink]').split(',');
      this.router.navigate(urlSegments);
    };
  }

Sorin Veștemean
  • 1,772
  • 19
  • 17
0

I got the same problem, but I solved it.

@Directive({
  selector: "[linkify]",
})

// * Apply Angular Routing behavior, PreventDefault behavior
export class CustomLinkDirective {
  @Input()
  appStyle: boolean = true;
  constructor(
    private router: Router,
    private ref: ElementRef,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {}

  @HostListener("click", ["$event"])
  onClick(e: any) {
    e.preventDefault();
    const href = e.target.getAttribute("href");
    href && this.router.navigate([href]);
  }

  ngAfterViewInit() {
    if (isPlatformBrowser(this.platformId)) {
      this.ref.nativeElement.querySelectorAll("a").forEach((a: HTMLElement) => {
        const href = a.getAttribute("href");
        href &&
          this.appStyle &&
          a.classList.add("text-indigo-600", "hover:text-indigo-500");
      });
    }
  }
}

use it

 <p linkify
    class="mt-3 text-lg text-gray-500 include-link"
    [innerHtml]="apiSectionText"
  ></p>
k-coding
  • 11
  • 2
0

This is my solution. I created a custom directive. It scans dom tree in innerHTML then make decision

import {Directive, ElementRef, AfterViewInit} from '@angular/core';
import {Router} from '@angular/router';

@Directive({
    selector: '[appDynamicRouterLink]'
})

/**
 * This directive is used to dynamically add routerLink to a tag.
 * @author Hakkı Konu
 */
export class DynamicRouterLinkDirective implements AfterViewInit {

    constructor(private elementRef: ElementRef, private router: Router) {
    }

    ngAfterViewInit() {
        const links = this.elementRef.nativeElement.querySelectorAll('a');
        console.log('links', links);
        links.forEach(link => {
            const routerLink = link.getAttribute('routerLink');
            if (routerLink) {
                const urlTree = this.router.createUrlTree([routerLink]);
                link.setAttribute('href', urlTree.toString());
                link.addEventListener('click', event => {
                    event.preventDefault();
                    this.router.navigateByUrl(urlTree);
                });
            } else {
                const href = link.getAttribute('href');
                if (href && href.startsWith('/')) {
                    const urlTree = this.router.createUrlTree([href]);
                    link.setAttribute('href', urlTree.toString());
                    link.addEventListener('click', event => {
                        event.preventDefault();
                        this.router.navigateByUrl(urlTree);
                    });
                }
            }
        });
    }
}

--

usage:

<div [innerHTML]="content" appDynamicRouterLink></div>
hakki
  • 6,181
  • 6
  • 62
  • 106
0

My decision for subj with innerHtml, i18n translate and routerLink below: component html:

<p 
     (click)="routerlinkClicked($event)" 
     [innerHTML]="signupSuccessText"
></p>

component.ts:

public signupSuccessText: SafeHtml = this.sanitizer.bypassSecurityTrustHtml(this.translate.instant('signup.successReport.body.text'))

constructor(
    private sanitizer:                  DomSanitizer,
    private router:                     Router,
    public  translate:                  TranslateService
) { }

public routerlinkClicked(event: Event): void {
    const target: HTMLElement = event.target as HTMLElement;
    const attributes: NamedNodeMap = target.attributes;
    const redirectUrl: string = attributes && attributes['routerlink'] ? attributes['routerlink'].value : '';
    if (redirectUrl) {
        this.router.navigate([redirectUrl]);
    }
}

finally translate i18n en.json:

"signup.successReport.body.text": "You have almost finished. <br /> Please, check your email and follow <br /> the verification link to complete you registration.<br /><br /> After you finish, come back and <a routerLink='/login' class='reset__link' data-test-id='loginLink'>log in</a>.",

cheers!

Mentor-kh
  • 99
  • 1
  • 5
-2

I had the same issue. I just replaced the <a routerLink="/something">Something</a>

to

<a href="#/something">Something</a>

And it works just fine

baliman
  • 588
  • 2
  • 8
  • 27