Search code examples
htmlangularangular-ng-ifangular-standalone-componentsangular-inheritance

How can I deal with CommonModule references in a standalone directive?


I'm trying to migrate an old library of Angular components to standalone components.

A number of the components have a lot in common, so I use a superclass for all of the common behavior. At least as I was accustomed to doing things, the superclass of a @Component shouldn't be another @Component, but rather a @Directive.

The start of the superclass looks like this:

// @dynamic
@Directive()
export abstract class DigitSequenceEditorDirective<T> implements
    AfterViewInit, ControlValueAccessor, OnInit, OnDestroy, Validator {
// ...

And one the subclass components starts like this:

// @dynamic
@Component({
  selector: 'tbw-time-editor',
  animations: [BACKGROUND_ANIMATIONS],
  templateUrl: '../digit-sequence-editor/digit-sequence-editor.directive.html',
  styleUrls: ['../digit-sequence-editor/digit-sequence-editor.directive.scss', './time-editor.component.scss'],
  providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TimeEditorComponent), multi: true },
              { provide: NG_VALIDATORS, useExisting: forwardRef(() => TimeEditorComponent), multi: true }]
})
export class TimeEditorComponent extends DigitSequenceEditorDirective<number> implements OnInit {
// ...

The problem now, porting this to Angular 19 and trying to use a standalone design, is that the HTML for the directive contains some CommonModule features like [ngClass] and [ngStyle]. (There are also uses of *ngIf and the like, but those I can get rid of using @if, @for, etc.)

A non-functional recommendation by Google AI for using CommonModule features in a directive looks like this:

import { Directive, Input } from '@angular/core';
    import { NgIf } from '@angular/common';
    
    @Directive({
      selector: '[appMyStandalone]',
      standalone: true,
      imports: [NgIf] // Can't be done! No `imports` field! (I'd have NgClass and NgStyle here if this worked)
    })
    export class MyStandaloneDirective {
      @Input() appMyStandalone: boolean = false;
    }

If I can't use imports with a directive, how do I resolve the red squiggly line stuff below?

enter image description here


Solution

  • The directive never contains the imports used in HTML, it always exists on the parent component.

    The content of the decorator is never inherited in angular, only the class properties and methods.

    The reason we use @Directive({ ... }) is so that you can use angular features like model, input, etc.

    So add the common module imports in the child component, the parent directive decorator contents are never used anyway.

    // @dynamic
    @Component({
      selector: 'tbw-time-editor',
      animations: [BACKGROUND_ANIMATIONS],
      templateUrl: '../digit-sequence-editor/digit-sequence-editor.directive.html',
      styleUrls: [
        '../digit-sequence-editor/digit-sequence-editor.directive.scss', 
        './time-editor.component.scss'
      ],
      providers: [
        { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TimeEditorComponent), multi: true },
        { provide: NG_VALIDATORS, useExisting: forwardRef(() => TimeEditorComponent), multi: true }
      ],
      imports: [
        ...
        NgIf
        ...
      ],
    })
    export class TimeEditorComponent extends DigitSequenceEditorDirective<number> implements OnInit {
    // ...