Question Details

No question body available.

Tags

angular angular-directive angular-signals angular21

Answers (2)

April 19, 2026 Score: 2 Rep: 67,050 Quality: Medium Completeness: 80%

Instead of using mascara as a host directive input, which is forwarded.

We can directly set the maskInternal (private and internal property) (WORKAROUND!) to configure the mask used by the directive.

@Directive({
  selector: '[mascara]',
  hostDirectives: [
    {
      directive: NgxMaskDirective,
      inputs: ['patterns', 'prefix', 'suffix'],
    },
  ],
})
export class MaskDirective {
  readonly mascara = input.required();

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. }); } }

Your code also does the same thing, but this does not use the event hook.

Full Code:

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 = { 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();

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: `
Number (00) 0000 00

`, }) export class App {}

Stackblitz Demo

April 20, 2026 Score: 2 Rep: 615 Quality: Low Completeness: 80%

Is manually calling ngOnChanges() on an injected host directive the intended/recommended approach in this scenario, or is it considered a hack?

This relies on the inner workings of NgxMaskDirective, so yes I would consider it unstable, but also the best solution if you want to use the shorter mask names in the template like you described.

Is there a cleaner Angular 19 API (signals-based or otherwise) to pass resolved/transformed values into a host directive's inputs without going through ngOnChanges()?

There is an ongoing issue for what you are trying to accomplish and I don't believe that there currently is a clean solution to this problem that does not rely on an internal API.

Would it be better to avoid hostDirectives entirely here and instead apply NgxMaskDirective programmatically, or directly replicate its logic?

Unfortunately you can't create a directive programmatically. If it's only the mask, I would suggest simply using the original directive like this:

@Component({
  selector: 'my-component',
  imports: [NgxMaskDirective],
  template: `

`, }) export class MyComponent{ protected masks = MASKS; }