Angular属性装饰器开发求助:标记组件属性遇只读属性报错
解决Angular装饰器标记属性时的只读属性错误
错误原因
你遇到的Uncaught TypeError: Cannot assign to read-only property 'Symbol(fields)' of object '[object Object]',是因为Angular编译后会冻结组件类的原型对象,直接给原型添加Symbol属性会触发只读限制,之前的实现方式直接修改了类的原型属性,导致报错。
正确实现方案
改用TypeScript内置的Reflect.metadata API存储元数据,这是官方推荐的元数据存储方式,不会触发只读属性限制。
1. 定义@OptionType装饰器
import 'reflect-metadata'; // 定义元数据唯一标识键 const OPTION_TYPE_METADATA_KEY = Symbol('OptionType'); export function OptionType(type: string) { return function (target: any, propertyKey: string) { // 先获取类上已有的元数据,无则初始化空Map const existingOptions = Reflect.getMetadata(OPTION_TYPE_METADATA_KEY, target.constructor) || new Map<string, string>(); existingOptions.set(propertyKey, type); // 将更新后的元数据绑定到类的构造函数上 Reflect.defineMetadata(OPTION_TYPE_METADATA_KEY, existingOptions, target.constructor); }; }
2. 实现getAllProperties函数
export function getAllProperties(componentInstance: any): Map<string, string> { // 从组件实例的构造函数上读取元数据 return Reflect.getMetadata(OPTION_TYPE_METADATA_KEY, componentInstance.constructor) || new Map(); }
3. 组件中使用示例
import { Component, OnInit } from '@angular/core'; import { OptionType, getAllProperties } from './option-type.decorator'; @Component({ selector: 'app-dashboard', template: `<p>Dashboard Component</p>` }) export class DashboardComponent implements OnInit { @OptionType('ColorOption') color: string = ""; @OptionType('SizeOption') size: number = 10; ngOnInit(): void { console.log(getAllProperties(this)); // 返回Map(2) {"color" => "ColorOption", "size" => "SizeOption"} } }
替代方案(依赖Angular内部API,不推荐生产环境)
如果项目禁用了Reflect,可以临时利用Angular内部组件定义扩展元数据,但该方式存在版本更新失效风险:
import { ɵComponentDef as ComponentDef } from '@angular/core'; export function OptionType(type: string) { return function (target: any, propertyKey: string) { // 延迟执行确保组件定义已初始化 setTimeout(() => { const componentDef = (target.constructor as any).ɵcmp as ComponentDef<any>; if (!componentDef.options) componentDef.options = {}; if (!componentDef.options.optionTypes) componentDef.options.optionTypes = new Map(); componentDef.options.optionTypes.set(propertyKey, type); }); }; } export function getAllProperties(componentInstance: any): Map<string, string> { const componentDef = (componentInstance.constructor as any).ɵcmp as ComponentDef<any>; return componentDef.options?.optionTypes || new Map(); }
注意:内部API无官方维护承诺,优先使用
Reflect.metadata方案。
内容的提问来源于stack exchange,提问作者Highnoon
相关产品推荐
相关产品推荐

