In the component code I have a field called taggy containing a full anchor tag. At first, in my HTML, I rendered it to the screen, which, of course didn't work out because I only saw contents of taggy in the verbatim version. The text said literally <a href="blopp/1337">hazaa<a>.
One googling away, I discovered my mistake. I'm supposed to use innerHTML. It works but causes the browser to reload, since we're using a href and not routerLink. Naturally, I changed the value of taggy so it says routerLink instead of href. However, when I do so, it works kind of but not quite. In my HTML I have the following.
{{taggy}}
<div [innerHTML]=taggy></div>
<div innerHTML=taggy></div>
None of those produce a working link. The closest is the middle one, as it creates a div and inside it an anchor. However, there's no attributes on the anchor tag and it only contains the text that the user's supposed to read. The routing information is gone.
What to do?
I've found a suggestion but it doesn't feel as a good way to go (even author indicates that it's not the best approach). There's also this but it's not relevant in my case as I'm passing the string to another component.
As you've discovered, you can't have a dynamic template in Angular because the templates are bundled with components as javascript. If you want to learn more, check out this issue on github. The upshot of it is: dynamic templates would break AOT compilation.
You have also discovered that you can set the innerHTML
of an object to arbitrary HTML, but routerLink
won't work in that context. Why? Because routerlink
is an Angular directive, not an HTML attribute. Since we are just setting the innerHTML
, it's not a compiled template.
So, what's the solution? Let's back up and think about what routerLink
is doing in the first place. As it turns out, not much. Take a look at the source. Specifically, this is what it's doing when an element with the routerLink
directive is clicked:
@HostListener('click')
onClick(): boolean {
const extras = {
skipLocationChange: attrBoolValue(this.skipLocationChange),
replaceUrl: attrBoolValue(this.replaceUrl),
};
this.router.navigateByUrl(this.urlTree, extras);
return true;
}
It's just using HostListener
to monitor clicks, and routing accordingly. We can do something similar in our component. I've called it FooterComponent
since you mentioned that this was a footer with dynamic HTML:
import { Component, Input, HostListener } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-footer',
template: '<div [innerHTML]="footerHtml"></div>'
})
export class FooterComponent {
@Input() footerHtml: string = "";
constructor(private router: Router) { }
// Watch for clicks in our component
@HostListener("click", ['$event'])
onClick(event: MouseEvent) {
// If we don't have an anchor tag, we don't need to do anything.
if (event.target instanceof HTMLAnchorElement === false) {
return;
}
// Prevent page from reloading
event.preventDefault();
let target = <HTMLAnchorElement>event.target;
// Navigate to the path in the link
this.router.navigate([target.pathname]);
}
}
As you can see, it takes in input called footerHtml
, which is of type string. To use it, we will add this somewhere:
<app-footer [footerHtml]="footerHtml"></app-footer>
Where the footerHtml
property in our component is defined as follows:
footerHtml = '<p>This is the footer with a <a href="/admin">link!</a></p>';
When the element is clicked, we check to make sure the user clicked on a hyperlink. If they did, we navigate using the router and prevent the default behavior. Here's a working StackBlitz example. Hope this helps!