【问题标题】:Angular 2 innerHTML (click) bindingAngular 2 innerHTML(点击)绑定
【发布时间】:2026-01-06 05:25:02
【问题描述】:

我有这么大的 html 菜单,因此我决定绑定以能够制作多个子菜单下拉菜单并避免 html 代码重复。父>子(也是父)>子...

对于上下文:在 ng2_msList/msList.components.ts 中,ColumnsManagements.ts 被导入为 this.ColumnsManagementInstance。 innerHTML 菜单正确显示,在 ng2_msList/pages/list.html 中:

<!-- COLUMNS DROPDOWN BUTTON -->
<ul [innerHTML]="msList.ColumnsManagementInstance.columnMenu" class="dropdown-menu" role="menu"> </ul>

使用(在我的代码的一个非常简化的版本中):(感谢Stack Q)

setHtmlColumnsMenu() {
     var self = this;
     var htmlcolumnsmenu = '';
     [...]
     htmlcolumnsmenu += this.createColumnsList(this.getNoneRelationalColumns(true));

     // which return something like a lot of html content and sometime in it : 
     // <a href="javascript:;" (click)="msList.ColumnsManagementInstance.toogleColumn(column)">
     [...]
     return htmlcolumnsmenu;
}

BUT (click)="msList.ColumnsManagementInstance.toogleColumn(column)" (以前在 html 内容中)不再起作用。它在视图中作为标记中的简单文本写入(在未显示的 innerHtml 之前)。

我无法找到让它再次工作的方法。我测试了多种调用函数的方法,或者我在Ang Doc Sectionhere 等网络链接中找到的方法。 这些示例很容易调用在同一文件/上下文中设置的函数(单击)=“MyAction()”,但在我的上下文中,我无法正确调用它。

应用架构可能不像 Angular2 点击调用所期望的那样。

【问题讨论】:

    标签: angular


    【解决方案1】:

    这是设计使然。 Angular 不会以任何方式处理由[innerHTML]="..." 添加的 HTML(清理除外)。它只是将它传递给浏览器,仅此而已。

    如果您想动态添加包含绑定的 HTML,您需要将其包装在 Angular2 组件中,那么您可以使用例如 ViewContainerRef.createComponent() 来添加它

    有关完整示例,请参阅Angular 2 dynamic tabs with user-click chosen components

    一种不那么 Angulary 的方法是注入 ElementRef,使用

    访问添加的 HTML
    elementRef.nativeElement.querySelector('a').addEventListener(...)
    

    【讨论】:

    • 我们需要听到的并不总是我们想听到的:/ 谢谢你,这当然是我需要的,即使这意味着我错了 ;) 接受!
    • 由于某种原因,您的解决方案在我的演示中不起作用ng-run.com/edit/N0eb6SerRhXl4v8CrXTa
    • 我只在玩手机。不要从绑定中调用函数。每次更改检测运行时都会执行它们,因为 Angular 没有其他方法来检查结果是否更改。将结果分配给一个字段并改为绑定到该字段。不确定这与您的问题有关。
    • 打字稿中更详细的是: ....addEventListener('click', (evt: Event) => this.myMethod(params));
    【解决方案2】:

    可能为时已晚,但我希望这会对某人有所帮助。

    由于您想要点击绑定(可能还有其他绑定),最好使用[innerHTML]="..."跳过创建一个内部组件,您可以通过该组件向其传递数据@Input() 注释。

    具体来说,您有一个名为 BaseComponent 的组件,您可以在其中将一些 HTML 代码设置为变量 htmlData

    let htmlData  = '...<button (click)="yourMethodThatWontBeCalled()">Action</button>...'
    

    然后在 BaseComponent 的 html 文件中绑定它,如下所示:

    ...<div [innerHTML]="htmlData"></div>...
    

    而不是这样做,您创建 InnerComponent.ts

    @Component({
     selector: 'inner-component',
     templateUrl: './inner-component.html',
     styleUrls: ['./inner-component.scss']
    })
    export class InnerComponent {
       @Input()
       inputData: any;
    
       methodThatWillBeCalled(){
        //do you logic on this.inputData
       }
    }
    

    InnerComponent的Html文件:

    ...<button (click)="methodThatWillBeCalled()">Action</button>...
    

    现在在 BaseComponent 的 Html 文件中:

    ...<inner-component [inputData]="PUT_HERE_YOUR_DATA"></inner-component>
    

    【讨论】:

    • 添加子组件并输入经过清理或简单的html后,这对我不起作用。
    • 我们可以在不创建 InnerComponent 的 html 文件的情况下使用它吗?例如:我存储了 html 数据。
    • @Mitchapp 这解决了我的问题,谢谢。
    【解决方案3】:

    我知道这个问题有两种解决方案:

    1) 使用Renderer2 在渲染时添加点击事件

    在此解决方案中,Renderer2 用于向元素添加单击事件侦听器,并按其类型 HTMLAnchorElement 过滤目标。

    public ngOnInit() {
      // Solution for catching click events on anchors using Renderer2:
      this.removeEventListener = this.renderer.listen(this.elementRef.nativeElement, 'click', (event) => {
        if (event.target instanceof HTMLAnchorElement) {
          // Your custom anchor click event handler
          this.handleAnchorClick(event);
        }
      });
    }
    

    StackBlitz with a demo

    2) 在视图初始化后使用ElementRefquerySelectorAll

    在此解决方案中,使用querySelectorAllElementRef 中找到所有锚元素,并添加自定义点击处理程序。这必须在视图初始化之后完成,以便渲染 DOM 并且我们可以查询渲染的元素。

    public ngAfterViewInit() {
      // Solution for catching click events on anchors using querySelectorAll:
      this.anchors = this.elementRef.nativeElement.querySelectorAll('a');
      this.anchors.forEach((anchor: HTMLAnchorElement) => {
        anchor.addEventListener('click', this.handleAnchorClick)
      })
    }
    

    StackBlitz with a demo


    注意 请留下您喜欢哪种解决方案的评论以及为什么,我实际上很难选择我更喜欢哪一种。

    【讨论】:

      【解决方案4】:

      我能够为来自 API 到 innerHtml 的 html 设置点击侦听器。

      1. 需要将 API 中的 html 包装在 div 中以设置为不显示。

        <div id='wholeHtml' style='display:none;'> <h2>Foo Bar Inc.</h2>... <button class="buttonToClick">Click me</button> </div>

      2. html 所在的模板,也需要包裹在 targetable div 中。请注意“safeHtml”管道告诉 Angular 这个来源是可信的(这是我管理的一个 API)。

        <div id="parentDiv"> <div [innerHTML]="apiData | safeHtml"></div> </div>

      3. 对应的组件使用ElementRef和Renderer2从api中复制隐藏的div(被阻止接受点击监听),粘贴到父div中,设置新粘贴的内容显示,应用点击监听。

        import { Component, OnInit, ElementRef, Renderer2 } from '@angular/core'; ...

        export class ExampleComponent implements OnInit { constructor(private elRef: ElementRef, private renderer: Renderer2) {} ngOnInit() {} ngAfterViewInit() { if(document.getElementById('wholeHtml')){ let allContent = this.elRef.nativeElement.querySelector('#wholeHtml'); let parentContainer = this.elRef.nativeElement.querySelector('#parentDiv'); this.renderer.appendChild(parentContainer, allContent); this.renderer.removeStyle(allContent, "display"); let clickButtons = this.elRef.nativeElement.querySelectorAll('.buttonToClick'); for(var i = 0; i < clickButtons.length; i++){ this.renderer.listen(clickButtons[i], 'click', function($event){ functionToCall($event.target); }); }; } } ngOnDestroy() { this.renderer.destroy(); } }

      我使用的是 Angular 6,因为它物有所值。

      【讨论】:

        【解决方案5】:

        在 CSS 中:

        .dropdown-menu {
          pointer-events: none;
        }
        

        它将解决innerHTML(点击)绑定的问题。

        【讨论】:

          【解决方案6】:

          使用 Ihor 的解决方案:

          使用[innerHtlm]将标签包裹在另一个标签中,在子标​​签上设置pointer-events: none,在父标签上设置click()事件:

          <div (click)="doSomething()">
            <div style="pointer-events:none" [innerHtlm]="someValue">
          </div>
          

          【讨论】:

            【解决方案7】:

            非常hacky,对不起代码纯粹主义者

            首先,如果你想阻止 Angular 清理你的 innerHTML,在这里使用自定义管道会有所帮助

            import {DomSanitizer} from '@angular/platform-browser';
            import {PipeTransform, Pipe} from "@angular/core";
            
                @Pipe({ name: 'rawHtml'})
                export class RawHtmlPipe implements PipeTransform  {
                  constructor(private sanitized: DomSanitizer) {}
                  transform(value) {
                    return this.sanitized.bypassSecurityTrustHtml(value);
                  }
                }
            

            您可以像这样在代码中使用它(假设我们绑定到一个名为 comment 的对象,该对象具有属性 commentText)

            <p [innerHTML]="comment.commentText | rawHtml"></p>
            

            下一步是添加点击处理程序

            我们无法将点击处理程序添加到标记中,因为标记在渲染后不再与角度上下文相关联。

            而是给内部html的父元素添加一个handler,使用html的属性如

             <p (click)="commentContentClicked($event, comment)" [innerHTML]="comment.commentText | rawHtml"></p>
            

            从这里我们可以处理点击事件,也可以访问绑定的对象

            假设内部 HTML 看起来像这样:

             <b class='56508ee4-b3fb-40c7-a946-c0eb799b6b75'>Clicked User</b> nice test
            

            我们希望使用 id (56508ee4-b3fb-40c7-a946-c0eb799b6b75) 作为参数来处理用户点击事件

            commentContentClicked(event, comment){
                console.log('comment', comment);
                console.log('source element', event.srcElement);
              }   
            

            event.srcElement 将显示我们在 innerHTML 中单击了哪个元素

            然后我们只需从类中提取 id 并继续工作流

            event.srcElement.className
            

            【讨论】: