
本文介绍如何在angular应用中替代原生textarea,构建一个支持实时json语法高亮(键/值不同颜色)且可编辑的富文本区域,解决textarea无法渲染html样式的核心限制。
本文介绍如何在angular应用中替代原生textarea,构建一个支持实时json语法高亮(键/值不同颜色)且可编辑的富文本区域,解决textarea无法渲染html样式的核心限制。
在Angular中直接对 <textarea></textarea> 内容进行HTML着色(如用 <span class="json-key"></span> 包裹键名)是不可行的——因为 <textarea></textarea> 仅接受纯文本内容,其 innerHTML 属性被忽略,设置后会自动转义为字符串字面量,导致显示 <span>...</span> 原始标签而非渲染效果。这也是你调用 document.getElementById('schema').innerHTML = coloredText 无效的根本原因。
✅ 正确方案:用可编辑的 以下是一个轻量、可靠、无外部依赖(如Monaco)的实现方案: 详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。 通过该方案,你将获得 Postman 风格的实时JSON高亮体验——键名蓝色加粗、字符串绿色、数字蓝色、布尔与null品红,同时保留完全可编辑性,且无需引入复杂编辑器依赖。<div contenteditable> 替代 <code><textarea></textarea>,配合结构化模板渲染
1. HTML 模板(使用
contenteditable + Angular结构化渲染)<!-- 替代 textarea 的可编辑容器 -->
<div class="json-editor" contenteditable="true" valid json...>
<!-- 动态渲染高亮后的JSON -->
<ng-container else rawtext><div class="json-object">
<span entry of getentries let i='index"' class="json-pair">
<span class="json-key">{{ entry.key }}:</span>
<span class="json-value"></span>
<span getentries>,</span>
</span>
</div>
</ng-container><ng-template><span class="json-raw">{{ rawInput }}</span>
</ng-template>
</div>
2. TypeScript 逻辑(安全解析 + 递归格式化)
import { Component } from '@angular/core';
@Component({
selector: 'app-json-editor',
styleUrls: ['./json-editor.component.scss']
})
export class JsonEditorComponent {
rawInput = '';
parsedJson: any = null;
// 实时响应输入(防抖建议生产环境添加)
onContentChange(event: Event): void {
const target = event.target as HTMLElement;
this.rawInput = target.innerText;
try {
// 安全解析:仅当完整输入为有效JSON时才解析(支持对象/数组根节点)
const trimmed = this.rawInput.trim();
if (trimmed) {
this.parsedJson = JSON.parse(trimmed);
} else {
this.parsedJson = null;
}
} catch (e) {
this.parsedJson = null; // 解析失败 → 回退到纯文本模式
}
}
// 将对象/数组转为键值对数组,便于 *ngFor 渲染
getEntries(obj: any): { key: string; value: any }[] {
if (Array.isArray(obj)) {
return obj.map((item, idx) => ({ key: `${idx}`, value: item }));
}
if (obj !== null && typeof obj === 'object') {
return Object.entries(obj).map(([k, v]) => ({ key: `"${k}"`, value: v }));
}
return [];
}
// 递归格式化值(处理嵌套对象、数组、字符串加引号、布尔/数字等)
formatValue(value: any): string {
if (value === null) return '<span class="json-null">null</span>';
if (typeof value === 'string') return `<span class="json-string">"${value}"</span>`;
if (typeof value === 'boolean' || typeof value === 'number') {
const cls = typeof value === 'boolean' ? 'json-boolean' : 'json-number';
return `<span class="${cls}">${value}</span>`;
}
if (Array.isArray(value)) {
return `<span class="json-array">[ ${value.map(v => this.formatValue(v)).join(', ')} ]</span>`;
}
if (typeof value === 'object') {
const entries = this.getEntries(value);
const inner = entries.map(e =>
`${e.key}: ${this.formatValue(e.value)}`
).join(', ');
return `<span class="json-object">{ ${inner} }</span>`;
}
return String(value);
}
// 可选:聚焦/失焦增强体验(如清空占位符、恢复光标等)
onFocus(): void {
if (!this.rawInput.trim() && this.parsedJson === null) {
this.rawInput = '';
}
}
onBlur(): void {
// 可在此触发验证、保存等逻辑
}
}
3. SCSS 样式(模拟 textarea 外观 + 语法高亮)
.json-editor {
width: 100%;
min-height: 200px;
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 14px;
line-height: 1.5;
white-space: pre-wrap;
overflow-wrap: break-word;
outline: none;
background-color: #fff;
&[contenteditable="true"]:empty::before {
content: attr(data-placeholder);
color: #999;
}
.json-key {
color: #007acc; /* 蓝色:key */
font-weight: bold;
}
.json-string {
color: #28a745; /* 绿色:字符串 */
}
.json-number {
color: #0066cc; /* 深蓝:数字 */
}
.json-boolean,
.json-null {
color: #e83e8c; /* 品红:true/false/null */
}
.json-object,
.json-array {
color: #495057; /* 深灰:结构符号 */
}
.json-raw {
color: #495057;
}
}
⚠️ 关键注意事项:
contenteditable 不是完美 textarea 替代品:需手动处理粘贴(建议监听 paste 事件并 event.preventDefault() 后解析+插入)、撤销重做、快捷键(Ctrl+Z/Ctrl+A)等;生产环境推荐封装为独立组件或选用成熟库(如 ngx-monaco-editor 配合正确初始化)。[innerHTML] 仅用于已知安全的JSON值(数字、字符串、布尔等),不执行脚本;若JSON可能含用户可控HTML,务必使用 DomSanitizer.bypassSecurityTrustHtml() 并严格过滤。debounceTime(300))和深度限制(避免无限递归),或改用虚拟滚动渲染。contenteditable 添加 role="textbox" 和 aria-label,确保屏幕阅读器兼容。










