Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hostDirectives deep input mapping works implicitly and not explicitly #55218

Open
kbrilla opened this issue Apr 4, 2024 · 3 comments
Open

hostDirectives deep input mapping works implicitly and not explicitly #55218

kbrilla opened this issue Apr 4, 2024 · 3 comments
Labels
area: compiler Issues related to `ngc`, Angular's template compiler
Milestone

Comments

@kbrilla
Copy link

kbrilla commented Apr 4, 2024

Which @angular/* package(s) are the source of the bug?

compiler

Is this a regression?

No

Description

Docs says that You can nest hostDirectives and that:

When you apply hostDirectives to your component, the inputs and outputs from the host directives are not included in your component's API by default. You can explicitly include inputs and outputs in your component's API by expanding the entry in hostDirectives.

https://angular.dev/guide/directives/directive-composition-api#including-inputs-and-outputs

So I would expect this to work:

@Directive({
  selector: '[level2]',
  standalone: true,
  hostDirectives: [{ directive: Level1Directive, inputs: ['background'] }],
})
export class Level2Directive {}

@Directive({
  selector: '[level3]',
  standalone: true,
  hostDirectives: [{ directive: Level2Directive, inputs: ['background']}],
})
export class Level3Directive {}

but actually I get error saying:
TS-992017: Directive Level2Directive does not have an input with a public name of background.

What more this actually works:

@Directive({
  selector: '[level3]',
  standalone: true,
  hostDirectives: [{ directive: Level2Directive }],
})
export class Level3Directive {}

and exposes input of Level2Directive implicitly

What I expect is even deeply hosted directives should only allow for explicitly defined input and outputs, with ability to alias those inputs and outputs which is especially important when multiple directives are hosted with input names that overlap.

I also think that deeply realiasing should be possible as well:

@Directive({
  selector: '[level2]',
  standalone: true,
  hostDirectives: [{ directive: Level1Directive, inputs: ['background : bg'] }],
})
export class Level2Directive {}

@Directive({
  selector: '[level3]',
  standalone: true,
  hostDirectives: [{ directive: Level2Directive, inputs: ['bg : bckg'']}],
})
export class Level3Directive {}

and aliasing overlaping inputs form difrent hosted directives should be possible:

@Directive({
  selector: '[level2]',
  standalone: true,
  hostDirectives: [{ directive: Level1Directive, inputs: ['background : bg'] }],
})
export class Level2Directive {}

@Directive({
  selector: '[level2]',
  standalone: true,
  hostDirectives: [{ directive: OtherLevel1Directive, inputs: ['background : bg'] }],
})
export class OtherLevel2Directive {}

@Directive({
  selector: '[level3]',
  standalone: true,
  hostDirectives: [
    { directive: Level2Directive, inputs: ['bg : bckg'']},
    { directive: OtherLevel2Directive, inputs: ['bg : bckg2'']},
  ],
})
export class Level3Directive {}

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/~/github.com/kbrilla/angular-host-directives-composition-deep-input-mapping

Please provide the exception or error you saw

`TS-992017: Directive Level2Directive does not have an input with a public name of background.`

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 17.3.3
Node: 18.18.0
Package Manager: npm 10.2.3
OS: linux x64

Angular: 17.3.2
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1703.3
@angular-devkit/build-angular   17.3.3
@angular-devkit/core            17.3.3
@angular-devkit/schematics      17.3.3
@angular/cli                    17.3.3
@schematics/angular             17.3.3
rxjs                            7.8.1
typescript                      5.3.3
zone.js                         0.14.4

Anything else?

Also, error from browser is missleading:

Error: The directive 'Level3Directive2' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available.

Altenative

If this is by design end expected behaviour please add that to the docs.

@thePunderWoman thePunderWoman added the area: compiler Issues related to `ngc`, Angular's template compiler label Apr 4, 2024
@ngbot ngbot bot added this to the needsTriage milestone Apr 4, 2024
@crisbeto
Copy link
Member

crisbeto commented Apr 5, 2024

To understand the behavior, it's worth keeping in mind that host directives are all resolved to a flat array during matching, instead of being treated as a tree. E.g. in your example Angular would match [Level3, Level2, Level1], rather than [Level3, [Level2, [Level1]]]. In this context forwarding inputs multiple levels doesn't make sense, because there is no hierarchy.

As for this example:

What more this actually works:

@directive({
selector: '[level3]',
standalone: true,
hostDirectives: [{ directive: Level2Directive }],
})
export class Level3Directive {}
and exposes input of Level2Directive implicitly

Unless I'm missing something, that works because background is exposed from Level1Directive which was brought in by Level2Directive.

@kbrilla
Copy link
Author

kbrilla commented Apr 5, 2024

Say I use an directive from some lib:

  1. It has it's own inputs - well I can reuse it as hosted directive, reassigning input names, and even add diffrent hosted directive from some other lib that have the same input names but change them so they won't colllide.
  2. It has hosted directive and only reexports it, hard luck - now we cannot reassigning input names and use if with diffrent hosted directive - solution - do not use it or ask library author to change the way he implemented the directive - simply do not use hosted directives.

So basicly You need to look into implementation details of directives to knwon what can be done with them.

I would expect that hosted directive inputs/outputs are always resolved in the same way, so it would be transparent for user of lib whatever hosted directive was used or not to create some input.

Also TS-992017: Directive Level2Directive does not have an input with a public name of background. is misleading as You can actually set input background on Level2Directive directive in html tempalte so it kinda does have that input.

You see confusion - sometimes input exist sometimes it does not.

If needed I can provide more examples in stackblitz of all the cases I mentioned in my posts.

@crisbeto
Copy link
Member

crisbeto commented Apr 5, 2024

We can definitely do a better job at documenting the behavior, but I think it's consistent. The directives aren't really "mixed in", but rather they just get applied one after the other. The Directive Level2Directive does not have an input with a public name of background. error is correct, because the background input in that case writes to an instance of Level1Directive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: compiler Issues related to `ngc`, Angular's template compiler
Projects
None yet
Development

No branches or pull requests

3 participants