2

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!';
}

2 Answers 2

2

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>
Sign up to request clarification or add additional context in comments.

3 Comments

@Brian I added a clarification with a further example that may be useful
Ahh...that's exactly what i was trying to accomplish. I was hoping this could give me a more flexible approach than inheritance, but I'm not a fan of the injection approach either 😟
The entire Angular ecosystem is built on top of Dependency Injection, and often this pattern offers the best trade-offs in this framework. A nice thing I love about DI is that for example here your CompositionTestDirective is available in CompositionTestComponent and in all its children and you don't have to propagate Input or use a Service!
1

Things to notice.

We have defined the inputs for the directive and not for the component so dataProperty does not exist on the component but on the directive, you can see this, when you log the value in the directive on ngOnInit

I replaced the dataProperty with a dummy property to get rid of the errors since it does not exist on the component!

Note: the error after you comment is coming from the HTML of CompositionTestComponent not from the parent element!

FULL CODE:

import { Component, Input } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { Directive } from '@angular/core';
import 'zone.js';

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

  ngOnInit() {
    console.log(this.dataProperty);
  }
}

@Component({
  selector: 'app-composition-test',
  standalone: true,
  template: `
  <h1> {{qwerty}} </h1> <!-- changed here! -->
  `,
  hostDirectives: [
    {
      directive: CompositionTestDirective,
      inputs: ['dataProperty'],
    },
  ],
})
export class CompositionTestComponent {
  qwerty = '123'; // <- changed here!
  // @Input() dataProperty: string = '';
}

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

bootstrapApplication(App);

Stackblitz Demo

1 Comment

Thank you! That clears it up. I was thinking about this as inheritance, which it obviously is not!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.