是否只能通过手动调用ngOnChanges来为像NgxMaskDirective这样的宿主指令配置动态输入?
我在用Angular 21构建一个可复用的掩码指令,它把 NgxMaskDirective 作为宿主指令来包装。目标是为常见的巴西文档和格式化模式(CPF、CNPJ、电话号码、邮编等)提供命名掩码,同时仍允许将原始掩码字符串作为回退传入。
Here's what I have so far:
export type NamedMask = 'cnpj' | 'cpf' | 'phone' | 'cnpj-cpf' | 'zip-code';
const MASKS: Record<NamedMask, string> = {
cnpj: '00.000.000/0000-00', // Brazilian company registry number
cpf: '000.000.000-00', // Brazilian individual taxpayer number
phone: '(00) 0000-0000||(00) 00000-0000', // 8 or 9-digit phone numbers
'cnpj-cpf': '000.000.000-00||00.000.000/0000-00', // accepts both CPF and CNPJ
'zip-code': '00000-000', // Brazilian ZIP code (CEP)
};
@Directive({
selector: '[mascara]',
hostDirectives: [
{
directive: NgxMaskDirective,
inputs: ['patterns', 'prefix', 'suffix'],
},
],
})
export class MaskDirective {
readonly mascara = input.required<NamedMask | string>();
private readonly maskDir = inject(NgxMaskDirective);
private readonly resolvedMask = computed(() =>
MASKS[this.mascara() as NamedMask] ?? this.mascara()
);
constructor() {
effect(() => {
this.maskDir.ngOnChanges({
mask: new SimpleChange(undefined, this.resolvedMask(), true),
});
});
}
}
问题: NgxMaskDirective 将 mascara、dropSpecialCharacters 和 clearIfNotMatch 公开为标准的 @Input() 属性,但由于我使用的是 hostDirectives,因此只能转发在 inputs 数组中明确列出的输入。这三个没有被列出,因为我需要拦截并转换它们(特别是 mascara,它把友好名称映射到实际的模式字符串)。
唯一可以在解析完毕后将值推送到宿主指令的办法,是用一个 SimpleChange 对象手动调用 ngOnChanges(),这感觉像是在滥用内部API。
我的问题:
- 在这个场景中,对注入的宿主指令手动调用
ngOnChanges()是预期/推荐的做法吗,还是被视为投机取巧? - 是否存在更干净的Angular 19 API(基于signals的,或其他)可以在不经过
ngOnChanges()的情况下,将解析/转换后的值传递给宿主指令的输入? - 在这里是否更好完全避免使用
hostDirectives,改为以编程方式应用NgxMaskDirective,还是直接复刻其逻辑?
解决方案
与其把 mascara 作为宿主指令的输入进行转发。
我们可以直接设置 _maskInternal(私有且内部的属性)(WORKAROUND!)来配置指令使用的掩码。
@Directive({
selector: '[mascara]',
hostDirectives: [
{
directive: NgxMaskDirective,
inputs: ['patterns', 'prefix', 'suffix'],
},
],
})
export class MaskDirective {
readonly mascara = input.required<NamedMask | string>();
private readonly maskDir = inject(NgxMaskDirective);
private readonly resolvedMask = computed(
() => MASKS[this.mascara() as NamedMask] ?? this.mascara()
);
constructor() {
effect(() => {
this.maskDir['_maskValue'].set(this.resolvedMask()); // set the internal private property
this.maskDir._maskService.maskExpression = this.maskDir['_maskValue'](); // set the service property also.
});
}
}
你的代码也做了同样的事情,但这并不使用事件钩子。
完整代码:
import {
Component,
computed,
Directive,
effect,
inject,
input,
SimpleChange,
} from '@angular/core';
import { NgxMaskDirective } from 'ngx-mask';
export type NamedMask = 'cnpj' | 'cpf' | 'phone' | 'cnpj-cpf' | 'zip-code';
const MASKS: Record<NamedMask, string> = {
cnpj: '00.000.000/0000-00', // Brazilian company registry number
cpf: '000.000.000-00', // Brazilian individual taxpayer number
phone: '(00) 0000-0000||(00) 00000-0000', // 8 or 9-digit phone numbers
'cnpj-cpf': '000.000.000-00||00.000.000/0000-00', // accepts both CPF and CNPJ
'zip-code': '00000-000', // Brazilian ZIP code (CEP)
};
@Directive({
selector: '[mascara]',
hostDirectives: [
{
directive: NgxMaskDirective,
inputs: ['patterns', 'prefix', 'suffix'],
},
],
})
export class MaskDirective {
readonly mascara = input.required<NamedMask | string>();
private readonly maskDir = inject(NgxMaskDirective);
private readonly resolvedMask = computed(
() => MASKS[this.mascara() as NamedMask] ?? this.mascara()
);
constructor() {
effect(() => {
this.maskDir['_maskValue'].set(this.resolvedMask()); // set the internal private property
this.maskDir._maskService.maskExpression = this.maskDir['_maskValue'](); // set the service property also.
});
}
}
@Component({
selector: 'app-root',
imports: [MaskDirective],
template: `
<div>
<label>Number (00) 0000 00</label>
<input
type="text"
prefix="+963 "
mask="00 0000 000"
mascara="cnpj"
/>
</div>
`,
})
export class App {}
Stackblitz Demo
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。