You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular中嵌入HTML的点击事件绑定失效,如何修复?

问题描述

处理服务器返回的嵌入HTML内容时,已添加自定义元素及点击方法,但点击事件无法生效,以下是组件代码与模板代码,求修复方案:

组件代码

async ionViewWillEnter() {
    this.unlockOrientation();   
    const state = history.state;

    await this.userService.checkForCrucialData();

    if (state && state.workout) {
        this.workout = state.workout;
        this.featuredImage = this.workout._embedded['wp:featuredmedia'][0].source_url;
        this.workoutTitle = this.workout.title.rendered;
        this.workoutBody = this.sanitized.bypassSecurityTrustHtml(this.correctFileData(this.workout.content.rendered));
        this.changeDetector.detectChanges();
        this.localReferrer = state.referrer;

        setTimeout(() => {
            console.log('FOUND', this.playerContainers);
            console.log('FOUND2', this.playerContainer);
            console.log('FOUND3', this.workoutBodyEl);
            const iframes = document.querySelectorAll('iframe');
            this.vimeoPlayers = [];
            console.log('FOUND4', iframes);
            iframes.forEach((iframe) => {
                iframe.setAttribute('webkit-playsinline', 'webkit-playsinline');
                iframe.setAttribute('playsinline', 'playsinline');
                // this.vimeoPlayers.push(new Player(iframe));
            });
            console.log(this.vimeoPlayers);
        }, 10000);
    }
}

correctFileData(response:string) :string {
    let el = document.createElement('html');
    el.innerHTML = response;
    var instructions = el.getElementsByClassName('col-12-instructions');
    for ( var i =0; i<instructions.length ; i++) {
        instructions[i].insertAdjacentHTML('beforebegin', '<div class = "instructions-container" (click)="toggleInstructions()" style="display: flex; justify-content: space-between;margin: 6px 0 0 0 "><h3 class="heading" style="padding: 10px 10px 30px 0; margin: 0">TIPS</h3><img   src="../../../assets/imgs/mbm_chevron2.png" style="height: 1.5rem; width: 1.5rem;margin: 6px 0 0 0 "></div>')
    }

    var instructionsC = el.getElementsByClassName('instructions-container');
    for ( var i =0; i<instructionsC.length ; i++) {
        instructionsC[i].addEventListener('click',  this.toggleInstructions.bind(this))
    }

    // !this.showInstructions && instructions.remove()
    return el.outerHTML;
}

toggleInstructions(){
    this.showInstructions = !this.showInstructions;
    this.workoutBody = this.sanitized.bypassSecurityTrustHtml(this.correctFileData(this.workout.content.rendered));

    console.log('this.showInstructions', this.showInstructions);
}

模板代码

<ion-header class="ion-no-border">
  <ion-toolbar >
      <ion-title></ion-title>

       <ion-buttons slot="start">
            <ion-back-button text=""></ion-back-button>
        </ion-buttons>

  <ion-button  class="ion-padding-end no-padding" routerLink="/profile-home" routerDirection="back" slot="end" >
    <ion-icon  name="close" color="light" ></ion-icon>
  </ion-button>
  </ion-toolbar>
</ion-header>

<ion-content fullscreen="true" class="gradient card-background-page">
    <div class="header-container ion-padding-top bgimg-top" *ngIf="featuredImage"
            [ngStyle]="{'background': 'linear-gradient(rgba(0, 0, 0, 0.74), rgba(0, 0, 0, 0.74)),url(' + featuredImage + ')', 'background-size': 'cover'}">
            <ion-button fill="clear" color="white" class="back-arrow bring-to-front" (click)="goBack()"></ion-button>

            <div class="lesson-title">
                <h1 class="text-primary text-white" [innerHTML]="workoutTitle"></h1>
            </div>
        </div>


    <div *ngIf="workout">


        <div class="content-container" class="ion-padding">
            <div class="workout-text-container">
                <!-- div class="lead-text" style="font-size: 1rem; padding-top: 20px">Follow exercises in order below.<br/>Tap 'Workout Complete' to log the session.<br/><br/></div -->
        <div class = "tip-container" (click)="toggleTips()" style="display: flex; justify-content: space-between;margin: 6px 0 0 0 " *ngIf="localReferrer === 'gym'">
            <h3 class="heading" style="padding: 10px 10px 30px 0; margin: 0">TIPS</h3>
            <img   src="../../../assets/imgs/mbm_chevron2.png" style="height: 1.5rem; width: 1.5rem;margin: 6px 0 0 0 " [ngClass] = "{'pointing-up-icon': showTips}"></div>
        <div style="font-size: .8rem" *ngIf="localReferrer === 'gym' && showTips === true">
         'Reps' - Number of complete movements<br/>
   'Sets' - A set of reps<br/>
   'Rep Max' - Max weight for that many reps<br/>
   'Rest' - Rest time between sets</div>


        <!-- div style="font-size: .8rem" *ngIf="localReferrer === 'home'">
         'Reps' - Number of complete movements<br/>
   'Rest' - Rest time between sets</div -->
            <div #workoutBodyEl [innerHTML]="workoutBody" class="workout-body">
                    
            </div>

           
            </div>
            <ion-row>
                <ion-col>
                    <ion-button class="primary" (click)="toggleInstructions()" expand="block" fill="outline"
                        color="tertiary">SHOW INSTRUCTIONS</ion-button>
                </ion-col>
            </ion-row>
            <ion-row>
                <ion-col>
                    <ion-button class="primary" (click)="uploadTrainingData()" expand="block" fill="outline"
                        color="tertiary">WORKOUT COMPLETE</ion-button>
                </ion-col>
            </ion-row>
        </div>
    </div>
</ion-content>

修复方案

核心问题分析

  1. Angular不会编译[innerHTML]插入的HTML内容,所以模板语法(click)="toggleInstructions()"完全无效。
  2. 在correctFileData中提前给DOM元素绑定的事件,会在元素被转为HTML字符串后丢失,因为字符串无法保留事件引用。

步骤1:修改correctFileData方法

移除无效的Angular语法,仅保留元素结构,并根据showInstructions状态控制内容显示:

correctFileData(response:string) :string {
    let el = document.createElement('html');
    el.innerHTML = response;
    const instructions = el.getElementsByClassName('col-12-instructions');
    for (let i = 0; i < instructions.length ; i++) {
        // 只添加基础元素结构,去掉Angular的(click)语法
        instructions[i].insertAdjacentHTML('beforebegin', `<div class="instructions-container" style="display: flex; justify-content: space-between;margin: 6px 0 0 0 ">
            <h3 class="heading" style="padding: 10px 10px 30px 0; margin: 0">TIPS</h3>
            <img src="../../../assets/imgs/mbm_chevron2.png" style="height: 1.5rem; width: 1.5rem;margin: 6px 0 0 0 ">
        </div>`);
        // 根据状态控制指令内容的显示/隐藏
        instructions[i].style.display = this.showInstructions ? 'block' : 'none';
    }
    return el.outerHTML;
}

步骤2:通过ViewChild绑定事件

利用Angular的ViewChild获取动态HTML容器,在内容渲染完成后绑定原生事件:

  1. 先在组件中声明ViewChild:
import { ViewChild, ElementRef } from '@angular/core';

// ...

@ViewChild('workoutBodyEl') workoutBodyEl!: ElementRef;
  1. 新增事件绑定方法,替换原有的setTimeout逻辑:
async ionViewWillEnter() {
    this.unlockOrientation();   
    const state = history.state;

    await this.userService.checkForCrucialData();

    if (state && state.workout) {
        this.workout = state.workout;
        this.featuredImage = this.workout._embedded['wp:featuredmedia'][0].source_url;
        this.workoutTitle = this.workout.title.rendered;
        this.workoutBody = this.sanitized.bypassSecurityTrustHtml(this.correctFileData(this.workout.content.rendered));
        this.localReferrer = state.referrer;
        
        // 触发变更检测后绑定事件
        this.changeDetector.detectChanges();
        this.bindInstructionsEvents();
    }
}

// 绑定指令容器的点击事件
bindInstructionsEvents() {
    if (!this.workoutBodyEl?.nativeElement) return;
    
    // 绑定指令容器点击事件
    const containers = this.workoutBodyEl.nativeElement.querySelectorAll('.instructions-container');
    containers.forEach((container: HTMLElement) => {
        // 先移除旧事件,避免重复绑定
        container.removeEventListener('click', this.handleInstructionsClick);
        container.addEventListener('click', this.handleInstructionsClick.bind(this));
    });

    // 处理iframe的playsinline属性
    const iframes = this.workoutBodyEl.nativeElement.querySelectorAll('iframe');
    this.vimeoPlayers = [];
    iframes.forEach((iframe: HTMLIFrameElement) => {
        iframe.setAttribute('webkit-playsinline', 'webkit-playsinline');
        iframe.setAttribute('playsinline', 'playsinline');
        // this.vimeoPlayers.push(new Player(iframe));
    });
}

// 指令容器的点击处理逻辑
handleInstructionsClick() {
    this.showInstructions = !this.showInstructions;
    // 更新HTML内容
    this.workoutBody = this.sanitized.bypassSecurityTrustHtml(this.correctFileData(this.workout.content.rendered));
    // 重新检测变更并绑定事件
    this.changeDetector.detectChanges();
    this.bindInstructionsEvents();
}

步骤3:优化原toggleInstructions方法

保留原按钮的点击逻辑,直接调用新的处理方法即可:

toggleInstructions(){
    this.handleInstructionsClick();
}

内容的提问来源于stack exchange,提问作者warraich

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 03:20:35