Search code examples
angularangular-directiveangular-dynamic-componentsangular-input

Angular directive @Input ignored when added to component via directive composition


I think I'm missing something very basic here, but I've been staring at this for too long. According the docs and everything else I've seen, an Angular directive's @Input() will be accessible on a component as long as it's explicitly declared on the hostDirectives.inputs of that component. That's not the behavior I'm seeing. Instead, I get the dreaded Property [propertyName] does not exist on type [componentName] error.

Comment out line #26 on my Stackblitz to see it.

@Directive({
  standalone: true,
})
export class CompositionTestDirective {
  @Input() dataProperty?: string;
}

@Component({
  selector: 'app-composition-test',
  standalone: true,
  template: `<h1> {{dataProperty}} </h1>`,
  hostDirectives: [
    {
      directive: CompositionTestDirective,
      inputs: ['dataProperty'],
    },
  ],
})
export class CompositionTestComponent {
  /* This shouldn't be necessary right? Comment this out and...KABOOM! */
  @Input() dataProperty: string = '';
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CompositionTestComponent],
  template: `
    <app-composition-test [dataProperty]="property"/>
  `,
})
export class App {
  property = 'It works!';
}

Solution

  • You can se that the error reference to the missing property in the HTML template of CompositionTestComponent, so it's not a matter of Input exposed.
    In fact, when you omitted dataProperty you were trying to reference the component's dataProperty in the template, but it that didn't exist (causing the error). On the other hand, while you declared the Input dataProperty in the component, you weren't using the Directive because from the parent component you were assigning dataProperty.

    I'm afraid that in order to access dataProperty you have to access CompositionTestDirective via dependency injection.
    Note that in the constructor you can't be sure it's valued, if you want to reassign its value to a CompositionTestComponent property you have to use the OnInit hook.
    It's not very elegant, but I'm afraid it's the best result achievable.

    Here's your StackBlitz modified:

    export class CompositionTestComponent {
      testDirective = inject(CompositionTestDirective);
      dataProperty?: string;
    
      ngOnInit() {
        this.dataProperty = this.testDirective.dataProperty;
      }
    }
    
      <h1> from component: {{dataProperty}} </h1>
      <h1> from directive: {{testDirective.dataProperty}} </h1>