Angular混入类中使用@Input/input()输入属性的可行解决方案问询
嘿,我最近碰到个特别挠头的问题——在Angular里尝试给混入类添加@Input()属性或者input()信号时,编译器完全不买账,模板里绑定这个属性的时候直接提示“属性不存在”。查了下Angular官方的说法,这个场景目前居然是不支持的!
我特别喜欢混入这种复用逻辑的方式,想把输入属性的逻辑抽成混入给多个组件用,比如我写了这么个混入:
export function propertyMixin<T extends new (...args: any[]) => any>(Base: T) { @Directive() abstract class PropertyMixin extends Base { name = input('Default'); } return PropertyMixin; }
然后组件继承这个混入:
@Component({ selector: 'app-name', template: `<h1>Hello from {{ name() }}!</h1>`, }) export class NameComponent extends propertyMixin(class {}) {}
结果在父组件模板里绑[name]="'123'"的时候,编译器直接报错说name属性不存在。而且直接在组件里覆盖name = input('Default')也不行——这等于重新创建了一个新属性,混入里的那个就完全失效了,根本达不到复用的目的。
有没有什么优雅的 workaround 能解决这个问题啊?我翻了好长时间的资料,试了几种方法,先分享给大家,也想问问有没有更好的思路:
方案1:用declare关键字给TypeScript补全类型
这是最简单的方案,完全不影响运行时,只是告诉TypeScript“这个属性确实存在,是混入给我加的”:
@Component({...}) export class NameComponent extends propertyMixin(class {}) { // 用declare声明属性的类型,不会创建新属性 declare readonly name: ReturnType<typeof input>; }
这样TypeScript编译器就不会再报错,而且混入里的name信号会正常工作,模板绑定也能识别到。
方案2:把混入改成配置化,组件主动复用逻辑
如果不想用类型断言,可以把混入改成提供配置或者逻辑方法,让组件自己初始化输入属性,这样编译器能直接识别到组件的属性:
// 修改混入,提供创建输入属性的方法 export function propertyMixin() { return { getDefaultNameInput: () => input('Default'), // 还可以加其他复用逻辑 }; } @Component({...}) export class NameComponent { private mixinLogic = propertyMixin(); // 组件自己初始化,复用混入的配置 name = this.mixinLogic.getDefaultNameInput(); }
这个方案完全符合Angular当前的规范,没有任何黑科技,缺点是每个组件都要手动调用混入的方法,不过如果混入里有复杂的逻辑(比如输入属性的验证、默认值计算),还是能很好地复用的。
方案3:手动添加Angular的输入属性元数据
Angular的编译器是通过组件的静态元数据来识别输入属性的,所以我们可以手动给组件添加这个元数据,让Angular识别到混入里的属性:
export class NameComponent extends propertyMixin(class {}) {} // 手动给组件添加输入属性的元数据 // 注意:这里用了Angular的私有API,版本升级可能会失效 if (NameComponent.ɵcmp) { NameComponent.ɵcmp.inputs = [...(NameComponent.ɵcmp.inputs || []), 'name: name']; }
这个方案能让Angular完全“看到”这个输入属性,连模板的类型检查都能通过,但缺点是用了私有API,Angular版本升级时可能会失效,所以只适合内部项目或者临时方案,不建议在公共库中使用。
以上几个方案我都试过,方案1是我目前用得最多的,简单又安全。有没有大佬有更优雅的解决方案啊?毕竟混入的复用性真的很香,不想因为输入属性的问题就放弃它😭
内容来源于stack exchange

