使用路由器链接动态添加元素

Add element with RouterLink dynamically

本文关键字:添加 元素 动态 链接 路由器      更新时间:2023-09-30

当我在 Angular 组件中的某个地方放置一个锚元素时,如下所示:

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

一切正常。单击链接时,Angular 路由器将导航到目标组件。

现在,我想动态添加相同的链接。在我的应用程序中的某个地方,我有一个"通知组件",它的唯一职责是显示通知。

通知组件执行以下操作:

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

其中notification.content是包含要显示的 HTML 的 NotificationComponent 类中的公共字符串变量。

notification.content变量可以包含如下内容:

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

一切正常并显示在我的屏幕上,但是当我单击动态链接时没有任何反应。

有没有办法让 Angular 路由器使用此动态添加的链接?

PS:我知道DynamicComponentLoader,但我真的需要一个更不受限制的解决方案,我可以将各种HTML发送到我的通知组件,以及各种不同的链接。

在内容已经呈现后,无法添加 routerLink,但您仍然可以获得所需的结果:

  1. 创建一个包含动态数据的 href 并为其提供一个类:

    `<a class="routerlink" href="${someDynamicUrl}">${someDynamicValue}</a>`
    
  2. 将 HostListener 添加到 app.component,该组件侦听单击并使用路由器导航

    @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}`]);
         }
       }
     }
    

    }

routerLink是一个指令。不会为使用 [innerHTML] 添加的 HTML 创建指令和组件。此 HTML 不是由 Angular 以任何方式处理的。

建议的方法是不使用[innerHTML]而是使用DynamicComponentLoader ViewContainerRef.createComponent,您将HTML包装在组件中并动态添加它。

有关示例,请参阅带有用户单击所选组件的 Angular 2 动态选项卡

从角度 9 开始,AOT 是编译角度项目的默认推荐方法。与 JIT 不同,AOT 在运行时不保存编译器的实例,这意味着您无法动态编译角度代码。可以在 angular 9 中禁用 AOT,但不建议这样做,因为您的捆绑包大小会更大,您的应用程序会变慢。

我解决这个问题的方法是在运行时使用渲染器 API 添加一个点击侦听器,防止 url 的默认行为并调用角度路由器

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;
  }
}

你可以在这里找到一个例子: https://stackblitz.com/edit/angular-dynamic-routerlink-2

显然,上面解释的方法适用于JIT和AOT,但是如果您仍在使用JIT并希望动态编译组件(这可能有助于解决其他问题(。你可以在这里找到一个例子:https://stackblitz.com/edit/angular-dynamic-routerlink-1

使用的资源 :

https://stackoverflow.com/a/35082441/6209801

https://indepth.dev/here-is-what-you-need-to-know-about-dynamic-components-in-angular

结合其他一些答案 - 我希望它作为一个指令,这样我就可以针对正在 innerHTML'd 的特定元素,但为了避免使用 querySelector(等(来保持所有内容都是 Angulular。

我还发现了上述方法的一个问题,因为如果 href 是一个完整的 URL(即 https://www.example.com/abc(,将整个内容馈送到路由器将导致导航到/https。

我还需要检查以确保我们只路由器在我们的域内的 href。

@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, '')
    );
  }
}

在上面的environment.root_url描述了我们的基本域,quotemeta是Perl-ish quotemeta函数的粗略实现,只是为了转义特殊字符。

YMMV 和我肯定错过了一些边缘情况,但这似乎工作正常。