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

Angular 5可编辑Div中动态插入可点击自定义Span组件的问题

解决Angular 5中在contenteditable Div任意位置插入动态组件的问题

咱们先把核心问题掰明白:你现在用ViewContainerRef只能在预定义的<ng-container>里生成组件,但contenteditable的myMessage div是原生DOM环境,直接把Angular组件塞进去行不通,得换个思路——把组件动态挂载到div里的指定DOM节点上,同时保留组件的事件和样式。

下面是具体的分步解决方案:

1. 先处理光标位置,插入占位DOM节点

要在用户光标所在位置插入组件,首先得获取myMessage div里的光标位置,然后插入一个空的<span>作为占位符,后续把Angular组件挂载到这个占位符上。

先在Message.ts里添加一个获取光标位置并插入占位元素的函数:

getCursorInsertPosition(): HTMLElement {
  const editableDiv = document.getElementById('myMessage');
  let range, placeholder;

  if (window.getSelection) {
    const sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
      range = sel.getRangeAt(0);
      // 创建占位span,作为组件的挂载容器
      placeholder = document.createElement('span');
      range.insertNode(placeholder);
      // 调整光标到占位符后面,不影响后续输入
      range.setStartAfter(placeholder);
      range.setEndAfter(placeholder);
      sel.removeAllRanges();
      sel.addRange(range);
    }
  } else if ((document as any).selection && (document as any).selection.createRange) {
    // 兼容IE(如果需要的话)
    range = (document as any).selection.createRange();
    placeholder = document.createElement('span');
    range.pasteHTML(placeholder.outerHTML);
  }

  return placeholder;
}

2. 修改动态组件创建逻辑,挂载到占位节点上

原来的createSpanPhone是用target.createComponent把组件放到ng-container里,现在要改成把组件挂载到刚才创建的占位DOM节点上。需要用到ComponentFactoryResolver和Injector,同时要把组件的宿主视图关联到占位节点。

修改Message.ts的createSpanPhone方法:

import { ComponentFactoryResolver, ComponentRef, Injector } from '@angular/core';
// 记得注入Injector
constructor(private cfr: ComponentFactoryResolver, private injector: Injector) {}

// 定义数组存储动态组件,方便后续获取数据
dynamicComponents: ComponentRef<any>[] = [];

createSpanPhone(spanIDNumber, phoneDescription, phoneValue ){
  // 1. 获取光标位置的占位节点
  const placeholder = this.getCursorInsertPosition();
  if (!placeholder) return;

  // 2. 创建PhoneComponent的工厂
  const phoneComponentFactory = this.cfr.resolveComponentFactory(PhoneComponent);
  
  // 3. 创建组件实例,把占位节点作为宿主元素
  const componentRef: ComponentRef<PhoneComponent> = phoneComponentFactory.create(
    this.injector, 
    [], 
    placeholder
  );
  
  // 4. 把组件实例关联到当前视图的变更检测树,确保事件和数据绑定生效
  componentRef.changeDetectorRef.detectChanges();

  // 5. 保存组件引用,方便后续获取数据
  this.dynamicComponents.push(componentRef);

  // 6. 给组件传递动态数据
  componentRef.instance.phoneDesc = phoneDescription;
  componentRef.instance.phoneVal = phoneValue;
}

3. 调整PhoneComponent,支持输入数据绑定

原来的PhoneComponent里的文本是硬编码的,现在改成绑定输入属性,方便动态传入数据:

import { Component, Input } from '@angular/core';
import { faPhone } from '@fortawesome/free-solid-svg-icons';

@Component({
 selector: 'my-phone',
 template: `
 <span contenteditable="false" (click)="test()" class="BAN_Tags_IN_Text">
   <fa-icon [icon]="faPhone" class="faSpanIcon"></fa-icon>
   <span class="phoneDesc" data-attr="EN">{{phoneDesc}}</span>
   <span class="phoneVal"><b>{{phoneVal}}</b></span>
 </span>
 `
})
export class PhoneComponent {
 faPhone = faPhone;
 @Input() phoneDesc: string;
 @Input() phoneVal: string;

 constructor(){}

 test(){
   console.log("Hiiii, clicked phone tag!");
   // 这里可以打开修改对话框,修改后直接更新phoneDesc和phoneVal即可
 }
}

4. 保存时收集所有组件的数据

当用户要保存推送内容时,遍历之前保存的dynamicComponents数组,提取每个组件的核心数据:

saveMessage() {
  const messageData = {
    content: document.getElementById('myMessage').innerHTML,
    tags: this.dynamicComponents.map(comp => {
      if (comp.instance instanceof PhoneComponent) {
        return {
          type: 'phone',
          description: comp.instance.phoneDesc,
          value: comp.instance.phoneVal
        };
      }
      // 同理处理位置、网址等其他组件
      return null;
    }).filter(tag => tag !== null)
  };

  // 把messageData发送到后端或者处理移动端推送逻辑
  console.log('保存的数据:', messageData);
}

关键注意事项

  • 因为contenteditable的特性,用户可能会删除插入的span组件,所以要监听myMessage的input事件,及时从dynamicComponents数组中移除被删除的组件引用(可以给每个组件的宿主节点加唯一ID,然后对比DOM节点是否存在)。
  • Angular 5里需要把动态组件(比如PhoneComponent)添加到模块的entryComponents数组中,否则会报错:
@NgModule({
  declarations: [PhoneComponent],
  entryComponents: [PhoneComponent] // 必须手动声明
})
export class YourModule {}
  • 组件挂载后,如果需要更新数据,直接修改组件实例的属性即可,Angular的变更检测会自动更新视图。

这样调整后,就能在myMessage div的任意光标位置插入带样式、图标和点击事件的PhoneComponent,并且能在保存时准确获取对应的标签数据啦。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 15:02:49