1

My end goal is to have user created HTML (from a database) with ability to navigate within an Angular SPA. The simple way to display HTML from a backend is to use the DomSanitizer then bind to innerHTML of some element. The problem arises when an anchor tag <a href="/path/to/diffrent/page/in/app"> in the user created content reboots the app. I have thought of 3 solutions and they all have problems.

Note: I'd like to fully utilize AOT and build-optimizer.

  1. (dynamic component with JIT compiler)
    The user created content will have angular syntax like <a [routerLink]="/path/to/diffrent/page/in/app">. Then use the example here of loading the runtime compiler with JitCompilerFactory and custom decorators to preserve the component and module metadata to JIT compile components with the user created content as templates. However with build-optimizer this requires restoring the ctorParameters. With this you don't get all the size benefits of AOT because you still need to load the JIT compiler and as stated here - " And it seems to be risky as compiler is subject to internal changes. It works for @angular/compiler@4.4.5 but may not work for other releases."

  2. (DOM manipulation)
    Leave proper HTML in user created content <a href="/path/to/diffrent/page/in/app">. Then after binding to innerHTML get an ElementRef, then find every anchor tag and override the click event. This kind of DOM manipluation seems very discouraged in angular.

  3. (custom window event)
    The user created content will look like <a onclick="window.someGlobalFunc("/path/to/diffrent/page/in/app")">. Then listen to a window event in angular somehow. I like this idea even less as it is completely outside of angular.

edit add:

  1. (listen to the container "click" event and filter if it came from a anchor)
    See @cyrix 's answer

What is the correct way to achieve this functionality?

edit update:
I accepted an answer as the "best" option, but if anyone finds or they release an "Angular" way to do this please add an answer.

Wilhelmina Lohan
  • 2,803
  • 2
  • 29
  • 58
  • The only way to get Angular "stuff" working in dynamically added content is to compile a component at runtime and add that component dynamically. You can always use imperative methods (JavaScript) to access and modify the DOM and add/remove event handlers. – Günter Zöchbauer Oct 16 '17 at 18:00
  • @GünterZöchbauer yes, that is my solution 1. But this requires "hacks" that are subject to change to work with AOT and build-optimizer. – Wilhelmina Lohan Oct 16 '17 at 18:03
  • Found this [SO](https://stackoverflow.com/questions/37676726/angular-2-innerhtml-click-binding#answer-37676847) where [@GünterZöchbauer](https://stackoverflow.com/users/217408/g%c3%bcnter-z%c3%b6chbauer) links to this, [Angular 2 dynamic tabs with user-click chosen components](https://stackoverflow.com/questions/36325212/angular-2-dynamic-tabs-with-user-click-chosen-components/36325468#36325468) which lead to finding this as a possible solution. [Insert component into html dynamically](https://stackoverflow.com/questions/45984171/insert-component-into-html-dynamically#answer-45985727) – ttugates Oct 16 '17 at 18:25
  • @ttugates the "less Angulary way" mentioned [here](https://stackoverflow.com/a/37676847/6656422) is my solution #2. The ["Angular 2 dynamic tabs with user-click chosen components"](https://stackoverflow.com/questions/36325212/angular-2-dynamic-tabs-with-user-click-chosen-components/36325468#36325468) uses ComponentFactoryResolver which requires the component to already exist (be compiled). The ngComponentOutlet solution [here](https://stackoverflow.com/questions/45984171/insert-component-into-html-dynamically#answer-45985727) only addresses how to add the component to the view. – Wilhelmina Lohan Oct 16 '17 at 18:37

1 Answers1

1

I had the same problem and solved it by adding a click-listener to the container of the dynamic content by creating a custom directive and checking on each click if the target is an anchor element. If it is a anchor element prevent the default behavior and take the href-attribute, use it as url for router.navigateByUrl.

private onClick(e: any) {
    let link = this.checkForParentLink(e.target as HTMLElement);
    if(link && link.getAttribute('href')) {
      let href = link.getAttribute('href');
      let isMailOrPhone = href.startsWith('mailto:') || href.startsWith('tel:');
      if(isMailOrPhone) return;
      e.preventDefault();
      if(link.getAttribute('target') === '_blank') {     
        let win = window.open(href, '_blank');
        win && win.focus();
      } else {
        this.router.navigateByUrl(href);
      }
    }
  }

  private checkForParentLink(element: HTMLElement): HTMLLinkElement {
    if(!element) return;
    if(element.tagName === 'A') {
      return element as HTMLLinkElement;
    // this.element.nativeElement is in this case the container element
    }
    if(element.parentNode === this.element.nativeElement) {
      return null;
    }
    return this.checkForParentLink(element.parentNode as HTMLElement);
  }
cyr_x
  • 13,987
  • 2
  • 32
  • 46