升级至Angular 17后Angular库中属性装饰器失效问题求助
升级Angular 15到17后属性装饰器失效问题
将Angular 15升级至Angular 17后,Angular库中的@Adapter属性装饰器无法正常工作。已知TypeScript 5的装饰器机制在修改属性值上有变化,已在tsconfig中关闭useDefineForClassFields并开启experimentalDecorators,但组件中被@Adapter装饰的user$和account$在ngOnInit中仍为undefined,无法完成赋值。该装饰器用于将父应用的数据连接到子组件,是适配器逻辑的核心。
相关代码与配置
Angular库组件代码(@some/some-module)
export class CreditsComponent implements OnInit { @Adapter('user$') public user$: Observable<any>; @Adapter('account$') public account$: Observable<any>; public ngOnInit() { // 此处user$为undefined this.user$ .pipe(takeUntil(this.destroy$)) .subscribe(res => this.user = res); // 此处account$为undefined this.account$ .pipe(takeUntil(this.destroy$)) .subscribe(res => this.account = res); } }
@Adapter装饰器实现(Angular库@some/some-module)
export function Adapter(field: string) { return function (target: Object, propertyKey: string) { DecoratorService.adapter$.pipe( filter((res: AdapterService) => { return !!res.getOptions()[field]; }), take(1) ).subscribe( res => { Object.defineProperty(target, propertyKey, { value: res.getOptions()[field], // 无法修改属性值 }); } ); } }
主应用初始化适配器代码
import { AdapterService } from '@some/some-module'; export class AppComponent { public constructor( private adapterService: AdapterService ) {} public initAdapter() { this.adapterService.setOptions('user$', this.currentAccountService.user$); this.adapterService.setOptions('accountSub', this.currentAccountService.accountSub); this.adapterService.setOptions('account$', this.currentAccountService.account$); this.adapterService.setOptions('accountsList$', this.accounts$); this.adapterService.setOptions('switchModalSub', this.modalSwitcherService.switchModalSub); this.adapterService.setOptions('switchModal$', this.modalSwitcherService.switchModal$); this.adapterService.setOptions('depositsProduct$', this.depositsProduct$); this.adapterService.setOptions('deposits$', this.deposits$); this.adapterService.setOptions('depositsSettings$', this.config$); this.adapterService.setOptions('eventSwitcherService', this.eventSwitcherService); } }
主应用tsconfig配置
/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { "types": [ "jasmine", "node" ], "baseUrl": "./", "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": false, "noPropertyAccessFromIndexSignature": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, "downlevelIteration": true, "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, "target": "ES2022", "module": "es2020", "strictNullChecks": false, "noImplicitAny": false, "resolveJsonModule": true, "esModuleInterop": true, "lib": [ "es2020", "dom" ], "paths": { "@business/*": [ "src/app/*" ], "@business-test/*": [ "src/test-data/mock/*" ], "file-saver-es": [ "@types/file-saver" ] }, "useDefineForClassFields": false }, "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true } }
Angular库tsconfig配置(@some/some-module)
/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { "resolveJsonModule": true, "esModuleInterop": true, "baseUrl": "./", "outDir": "./dist/out-tsc", "types": [], "paths": { "dictionary-employees": [ "dist/dictionary-employees" ], "foreign-economic-activity": [ "dist/foreign-economic-activity" ], "trading-module": [ "dist/trading-module" ], "credits-module": [ "dist/credits-module" ], "@abank/shared-module": [ "dist/shared-module" ] }, "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, "downlevelIteration": true, "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, "target": "es2022", "module": "es2022", "noImplicitAny": true, "strictNullChecks": false, "lib": [ "es2022", "dom" ], "useDefineForClassFields": false }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true } }
解决方案
1. 调整装饰器的属性定义权限
当前装饰器中使用Object.defineProperty时未设置可写权限,在TypeScript 5+环境下,类属性默认可能被标记为不可写,导致赋值失败。修改装饰器:
export function Adapter(field: string) { return function (target: Object, propertyKey: string) { DecoratorService.adapter$.pipe( filter((res: AdapterService) => !!res.getOptions()[field]), take(1) ).subscribe(res => { Object.defineProperty(target, propertyKey, { value: res.getOptions()[field], writable: true, // 添加可写配置 enumerable: true, configurable: true }); }); } }
2. 提前初始化属性避免undefined
由于DecoratorService.adapter$是异步流,可能在ngOnInit执行时还未完成赋值。可以先给属性初始化一个空Observable,后续再替换为目标数据源:
export function Adapter(field: string) { return function (target: Object, propertyKey: string) { // 先初始化空Observable,避免ngOnInit时出现undefined const initial$ = new Observable<any>(); Object.defineProperty(target, propertyKey, { value: initial$, writable: true, enumerable: true, configurable: true }); // 订阅适配器数据,更新属性值 DecoratorService.adapter$.pipe( filter((res: AdapterService) => !!res.getOptions()[field]), take(1) ).subscribe(res => { (target as any)[propertyKey] = res.getOptions()[field]; }); } }
3. 统一库与主应用的编译配置
确保Angular库的tsconfig.json中experimentalDecorators和useDefineForClassFields的配置与主应用完全一致,并且库在构建时正确应用了这些配置。如果使用Angular CLI构建库,检查angular.json中的构建配置是否继承了tsconfig的设置。
4. 改用构造函数注入替代属性装饰器(可选)
如果上述方案仍不生效,可以调整适配器逻辑,直接在组件构造函数中通过AdapterService获取数据:
export class CreditsComponent implements OnInit { public user$: Observable<any>; public account$: Observable<any>; constructor(private adapterService: AdapterService) {} public ngOnInit() { this.user$ = this.adapterService.getOptions()['user$']; this.account$ = this.adapterService.getOptions()['account$']; this.user$?.pipe(takeUntil(this.destroy$)).subscribe(res => this.user = res); this.account$?.pipe(takeUntil(this.destroy$)).subscribe(res => this.account = res); } }
内容的提问来源于stack exchange,提问作者Kostiantyn Okhotnyk
相关产品推荐
相关产品推荐

