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>
修复方案
核心问题分析
- Angular不会编译
[innerHTML]插入的HTML内容,所以模板语法(click)="toggleInstructions()"完全无效。 - 在
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容器,在内容渲染完成后绑定原生事件:
- 先在组件中声明ViewChild:
import { ViewChild, ElementRef } from '@angular/core'; // ... @ViewChild('workoutBodyEl') workoutBodyEl!: ElementRef;
- 新增事件绑定方法,替换原有的
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
相关产品推荐
相关产品推荐

