Skip to content

fix(directive): emit a dynamic host attribute value as written, like ngtsc - #639

Open
edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/transform-compile-errors
Open

edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/transform-compile-errors

Conversation

@edbzn

@edbzn edbzn commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #627 (case 1). Cases 2 and 3 in the issue are left unchanged on purpose. The issue explains why.

Problem

ngtsc's evaluateHostExpressionBindings (compiler-cli/src/ngtsc/annotations/directive/src/shared.ts) turns a host value it can't evaluate (DynamicValue) into WrappedNodeExpr(value.node). parseHostBindings emits that expression for a plain attribute (hostAttrs: ["test", test]). oxc stored host attribute values as strings and reported an error. A value computed from an import got the "imported from another module" error instead, with or without resolveImportedValues.

Fix

  • evaluate_host_metadata returns a HostValue for each entry: a string, or the expression ngtsc would emit. For a plain attribute, a dynamic value (an unknown name, a call, a function expression, or a value computed from an import) becomes its expression. The expression comes from Prop::origin, in the form it can be written where the metadata is compiled.
    • Imports count as dynamic here because ngtsc's local compilation mode doesn't read the other file either. Full mode would fold an imported string constant to a literal. The runtime attribute is the same.
  • The component HostMetadata.attributes holds OutputExpressions, like the directive's R3HostMetadata.attributes already does. Full emit and partial emit (host: { attributes: { role: make() } }) both use them.
  • These cases still report errors:
    • [prop], (event), class and style keep ngtsc's "... binding must be string" errors, or the import error.
    • An attribute whose expression can't be written where the metadata is compiled is still an error. That covers a value that uses the parameters of a helper the metadata calls, and an export of another module reached through { ...ns }.

Checks

  • host_metadata_evaluation_test:
    • dynamic_value_of_a_plain_attribute now asserts the emitted hostAttrs. It covers make(), an unknown name, a spread const, an import, and the ngtsc spec input. It used to assert the error.
    • New: dynamic_value_of_a_plain_attribute_partial and dynamic_value_of_a_plain_attribute_out_of_scope.
    • host_that_depends_on_an_import no longer expects an error for { role: SHARED }.
  • cross_file_values_test::dynamic_host_attribute_from_an_unreadable_import ports ngtsc_spec.ts "should accept dynamic host attribute bindings" with resolveImportedValues.
  • cargo test -p oxc_angular_compiler --all-features: 3519 passed, 0 failed, 1 ignored.
  • cargo fmt is clean. cargo clippy -p oxc_angular_compiler --all-features reports no warnings on the changed lines.

🤖 Generated with Claude Code

…ngtsc

ngtsc wraps a host value it can't evaluate in a WrappedNodeExpr and emits it
in hostAttrs for a plain attribute; oxc rejected it. Values computed from an
import are treated the same way, as ngtsc's local compilation mode does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dynamic host attribute values are rejected (and two local-compilation-only transform cases)

1 participant