如何在jQuery动态添加的HTML中使用Angular指令(如routerLink)?
我在Angular应用里用jQuery的GridStack插件,代码如下:
ngOnInit() { var options = { float: true }; $('.grid-stack').gridstack(options); this.gridHr = $('.grid-stack-hr').data('gridstack'); this.items.filter(i => i.category == 'hr').map( item => { this.gridHr.addWidget($(`<div class="start-grid-stack-item"> <div class="grid-stack-item-content" style="background:${ item.color }"> <a class="link" [routerLink]="['employee']"> <img style="display: ${ item.icon ? 'inline-block': 'none' }" src="assets/gridstack/${ item.icon }"> <p>${ item.title }</p> </a> </div> </div>`), item.x, item.y, item.width, item.height); }); }
但发现jQuery添加的HTML里没法用[routerLink]="['employee']"这类Angular构造,请问这可行吗?能不能给个示例代码展示怎么在jQuery添加的代码里用Angular特性?
嘿,直接这么写肯定行不通的!原因很简单:Angular只会在应用初始化阶段编译组件模板里的内容,你用jQuery动态插入的纯HTML字符串,Angular的编译器根本不会搭理它,所以像[routerLink]这种指令、插值表达式啥的自然就失效了。
不过也不是完全没办法,给你两种实用的解决方案:
方案1:手动绑定Angular Router导航(适合快速改造现有代码)
既然routerLink指令没法生效,那我们可以不用它,转而手动用Angular的Router服务来处理导航。
首先在组件里注入Router:
import { Router } from '@angular/router'; constructor(private router: Router) {}
然后修改你的ngOnInit代码,把routerLink换成普通的点击事件绑定:
ngOnInit() { const options = { float: true }; $('.grid-stack').gridstack(options); this.gridHr = $('.grid-stack-hr').data('gridstack'); this.items.filter(i => i.category == 'hr').forEach(item => { // 构造HTML时去掉routerLink,换成自定义class方便绑定事件 const $widget = $(` <div class="start-grid-stack-item"> <div class="grid-stack-item-content" style="background:${ item.color }"> <a class="link employee-nav-link" href="javascript:void(0)"> <img style="display: ${ item.icon ? 'inline-block': 'none' }" src="assets/gridstack/${ item.icon }"> <p>${ item.title }</p> </a> </div> </div> `); // 把widget添加到GridStack this.gridHr.addWidget($widget, item.x, item.y, item.width, item.height); // 手动绑定点击事件,用Angular Router做导航 $widget.find('.employee-nav-link').on('click', () => { this.router.navigate(['employee']); }); }); }
方案2:用Angular动态组件(更符合Angular生态的做法)
如果想完全利用Angular的模板特性(比如指令、数据绑定),最好的方式是把每个GridStack的widget做成Angular组件,然后动态创建组件实例再挂载到GridStack里。
步骤1:创建GridWidget组件
先写一个专门的组件来承载widget内容:
<!-- grid-widget.component.html --> <div class="grid-stack-item-content" [style.background]="item.color"> <a class="link" [routerLink]="['employee']"> <img *ngIf="item.icon" style="display: inline-block" [src]="'assets/gridstack/' + item.icon"> <p>{{ item.title }}</p> </a> </div>
// grid-widget.component.ts import { Component, Input } from '@angular/core'; @Component({ selector: 'app-grid-widget', templateUrl: './grid-widget.component.html', styleUrls: ['./grid-widget.component.css'] }) export class GridWidgetComponent { @Input() item: any; // 接收父组件传递的item数据 }
步骤2:在父组件动态创建组件并挂载
在使用GridStack的父组件里,注入动态组件需要的依赖,然后修改逻辑:
import { ComponentFactoryResolver, ViewContainerRef } from '@angular/core'; import { GridWidgetComponent } from './grid-widget/grid-widget.component'; constructor( private componentFactoryResolver: ComponentFactoryResolver, private viewContainerRef: ViewContainerRef ) {} ngOnInit() { const options = { float: true }; $('.grid-stack').gridstack(options); this.gridHr = $('.grid-stack-hr').data('gridstack'); this.items.filter(i => i.category == 'hr').forEach(item => { // 1. 创建组件工厂 const componentFactory = this.componentFactoryResolver.resolveComponentFactory(GridWidgetComponent); // 2. 创建组件实例 const componentRef = this.viewContainerRef.createComponent(componentFactory); // 3. 给组件传递数据 componentRef.instance.item = item; // 4. 获取组件的原生DOM元素 const widgetElement = componentRef.location.nativeElement; // 5. 包装成GridStack需要的容器,添加到GridStack const $widget = $(`<div class="start-grid-stack-item"></div>`).append(widgetElement); this.gridHr.addWidget($widget, item.x, item.y, item.width, item.height); }); }
注意:如果你的Angular版本低于9,需要把
GridWidgetComponent添加到模块的entryComponents数组里,新版本Angular已经不需要这个配置了。
总结
如果只是临时改造,方案1足够快速;如果想长期维护,推荐方案2,完全遵循Angular的开发模式,避免jQuery和Angular混用带来的各种坑。
内容的提问来源于stack exchange,提问作者Bmaed Riasbet

