Search code examples
htmlangulartypescriptinnerhtmlangular-router

Angular/Typescript Text with routerLink


Updated Question for more Clarity:

Need to display some texts and links as innerHTML(data from service/DB) in the Angular HTML and when user clicks, it should go to Typescript and programmatically navigates by router.navigate

Also, How to add DomSanitizer from @ViewChild/ElementRef

Added all example in below code

Here is the updated stackblitz code

As shown in screenshot from angular.io some texts and some links

enter image description here


Solution

  • Sorry, I didn't realize you answered my comment. Angular routing is not secondary, if you don't use Angular modules you'll end up with just an HTML/CSS/Typescript application. you need at least the RouterModule for Angular to be able to use routing and hence, do what it's supposed to with the DOM.

    First:

    • You are not importing RouterModule

      solution:

      imports: [ 
        BrowserModule, 
        FormsModule, 
        RouterModule.forRoot([])  // this one
      ]
      

    Second:

    • You can't bind Angular events through innerHTML property

      fix:

    Make use of @ViewChild directive to change your innerHTML property and manually bind to the click event, so change in your app.component.html from

    <div id="box" [innerHTML]="shouldbedivcontent" ></div>
    

    to

    <div #box id="box"></div>
    

    Now, in your app.component.ts, add a property to hold a reference to that "box" element so you can later make some changes to the dom with it:

      @ViewChild('box')  container: ElementRef;
    

    Implement AfterViewInit, that hook is where you will be able to actually handle your container, if you try using it for example in OnInit you'd get undefined because that component's html is not in the dom yet.

    export class AppComponent  implements AfterViewInit {
    

    and

    ngAfterViewInit() {
        this.container.nativeElement.innerHTML = this.shouldbedivcontent;
        this.container.nativeElement.addEventListener('click', 
          () => this.goto('bar')
        );
    }
    

    change shouldbedivcontent property from:

    '1) this is a click 
     <a (click)="goto("bar")">Click</a><br> 
     2)this is with routerlink 
     <a routerLink="" (click)="goto("bar")">Click</a><br> 
     3)This only works with href 
     <a href="goto/bar" title="bar">bar</a>&nbsp; and test'
    

    to

    '1) this is a click 
     <a id="link_1">Click</a><br> 
     2)this is with routerlink 
     <a [routerLink]="" (click)="goto(\'bar\')">Click</a><br> 
     3)This only works with href 
     <a href="goto/bar" title="bar">bar</a>&nbsp; and test'
    

    And even so you'd still not get the default anchor style unless you apply some styling yourself.

    Third

    You are not HTML sanitizing, which could be dangerous. read more here

    MY SUGGESTION:

    Seems like a lot to do for you and a lot to read for someone else working alongside you for something you could easily do like in the example below!

    Move your html to your app.component.html:

    <div id="box">
      1) this is a click 
      <a (click)="goto('bar')">Click</a><br> 
      2)this is with routerlink 
      <a routerLink="" (click)="goto('bar')">Click</a><br> 
      3)This only works with href 
      <a href="goto/bar" title="bar">bar</a>&nbsp; and test
    </div>
    
    <p>Below is actual content</p>
    

    You'll notice that everything works now, except the anchor without routerLink or href, because that's not a link.

    EDIT:

    Looking at the new stackblitz, i suggest a change of approach, binding to innerHTML is ok when working with plain text or even some simple html but not a great choice to bind events or routing logic.

    Angular's Renderer2 provides with a bunch of methods to dyncamically add elements to the DOM. With that on the table, you just need a little effort to take that simple html you get from your backend and turn it into something like (paste this property in your code to test it along the rest of the code provided below):

    public jsonHTML = [
      {
        tagName: '',
        text: 'some text with click ',
        attributes: {
        }
      },
      {
        tagName: 'a',
        text: 'bar',
        attributes: {
          value: 'bar' // goto parameter
        }
      },
      {
        tagName: '',
        text: ' some more text with click ',
        attributes: {
        }
      },
      {
        tagName: 'a',
        text: 'foo',
        attributes: {
          value: 'foo' // goto parameter
        }
      }
    ]
    

    Once you have it, it's way easier to create all of those elements dynamically:

    this is for the code in your Q1:

    Inject Renderer2 with private r2: Renderer2

    And replace the Q1 related code in AfterViewInit hook to:

    const parent = this.r2.createElement('div');  // container div to our stuff
    
    this.jsonHTML.forEach((element) => {
      const attributes = Object.keys(element.attributes);
      const el = element.tagName && this.r2.createElement(element.tagName);
      const text = this.r2.createText(element.text);
    
      if (!el) {  // when there's no tag to create we just create text directly into the div.
        this.r2.appendChild(
          parent,
          text
        );
      } else { // otherwise we create it inside <a></a>
        this.r2.appendChild(
          el,
          text
        );
    
        this.r2.appendChild(
          parent,
          el
        );
      }
      
      if (attributes.length > 0) {
        attributes.forEach((name) => {
          if (el) {
            this.r2.setAttribute(el, name, element.attributes[name]); // just the value attribute for now
    
           if (name === 'value') { 
              this.r2.listen(el, 'click', () => {
                this.goto(element.attributes[name]); // event binding with property "value" as parameter to navigate to
              })
            }
          } else {
            throw new Error('no html tag specified as element...');
          }
        })
      }
    })
    
    this.r2.appendChild(this.container.nativeElement, parent); // div added to the DOM
    

    No html sanitizer needed and no need to use routerLink either just inject Router and navigate to the route you want! Make improvements to the code t make it fit your needs, it should be at least a good starting point

    Good Luck!