@@ -1047,9 +1047,6 @@ fn link_directive(
10471047 let standalone = get_bool_property ( meta, "isStandalone" ) . unwrap_or ( true ) ;
10481048 parts. push ( format ! ( "standalone: {standalone}" ) ) ;
10491049
1050- if let Some ( host_directives) = get_property_source ( meta, "hostDirectives" , source) {
1051- parts. push ( format ! ( "hostDirectives: {host_directives}" ) ) ;
1052- }
10531050 if let Some ( features) = build_features ( meta, source, ns) {
10541051 parts. push ( format ! ( "features: {features}" ) ) ;
10551052 }
@@ -1283,11 +1280,12 @@ fn build_queries(
12831280/// Build the features array from component metadata.
12841281///
12851282/// Examines boolean flags and providers to build the features array:
1283+ /// - `providers: [...]` → `ns.ɵɵProvidersFeature([...])`
1284+ /// - `hostDirectives: [...]` → `ns.ɵɵHostDirectivesFeature([...])`
12861285/// - `usesInheritance: true` → `ns.ɵɵInheritDefinitionFeature`
12871286/// - `usesOnChanges: true` → `ns.ɵɵNgOnChangesFeature`
1288- /// - `providers: [...]` → `ns.ɵɵProvidersFeature([...])`
1289- /// Order is important: ProvidersFeature → InheritDefinitionFeature → NgOnChangesFeature
1290- /// (see definition.rs line 990 and packages/compiler/src/render3/view/compiler.ts:119-161)
1287+ /// Order is important: ProvidersFeature → HostDirectivesFeature → InheritDefinitionFeature → NgOnChangesFeature
1288+ /// (see packages/compiler/src/render3/view/compiler.ts:119-161)
12911289fn build_features ( meta : & ObjectExpression < ' _ > , source : & str , ns : & str ) -> Option < String > {
12921290 let mut features: Vec < String > = Vec :: new ( ) ;
12931291
@@ -1307,12 +1305,17 @@ fn build_features(meta: &ObjectExpression<'_>, source: &str, ns: &str) -> Option
13071305 ( None , None ) => { }
13081306 }
13091307
1310- // 2. InheritDefinitionFeature
1308+ // 2. HostDirectivesFeature — must come before InheritDefinitionFeature
1309+ if let Some ( host_directives) = get_property_source ( meta, "hostDirectives" , source) {
1310+ features. push ( format ! ( "{ns}.\u{0275} \u{0275} HostDirectivesFeature({host_directives})" ) ) ;
1311+ }
1312+
1313+ // 3. InheritDefinitionFeature
13111314 if get_bool_property ( meta, "usesInheritance" ) == Some ( true ) {
13121315 features. push ( format ! ( "{ns}.\u{0275} \u{0275} InheritDefinitionFeature" ) ) ;
13131316 }
13141317
1315- // 3 . NgOnChangesFeature
1318+ // 4 . NgOnChangesFeature
13161319 if get_bool_property ( meta, "usesOnChanges" ) == Some ( true ) {
13171320 features. push ( format ! ( "{ns}.\u{0275} \u{0275} NgOnChangesFeature" ) ) ;
13181321 }
@@ -1435,11 +1438,6 @@ fn link_component(
14351438 let standalone = get_bool_property ( meta, "isStandalone" ) . unwrap_or ( true ) ;
14361439 parts. push ( format ! ( "standalone: {standalone}" ) ) ;
14371440
1438- // 11b. hostDirectives (Directive Composition API)
1439- if let Some ( host_directives) = get_property_source ( meta, "hostDirectives" , source) {
1440- parts. push ( format ! ( "hostDirectives: {host_directives}" ) ) ;
1441- }
1442-
14431441 // 12. features
14441442 if let Some ( features) = build_features ( meta, source, ns) {
14451443 parts. push ( format ! ( "features: {features}" ) ) ;
@@ -2177,4 +2175,114 @@ MyDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
21772175 result. code
21782176 ) ;
21792177 }
2178+
2179+ /// Issue #71: hostDirectives must be converted to ɵɵHostDirectivesFeature in features array
2180+ /// instead of being emitted as a direct property.
2181+ #[ test]
2182+ fn test_link_directive_with_host_directives ( ) {
2183+ let allocator = Allocator :: default ( ) ;
2184+ let code = r#"
2185+ import * as i0 from "@angular/core";
2186+ class BrnContextMenuTrigger {
2187+ }
2188+ BrnContextMenuTrigger.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.0", ngImport: i0, type: BrnContextMenuTrigger, selector: "[brnCtxMenuTriggerFor]", isStandalone: true, hostDirectives: [{ directive: CdkContextMenuTrigger }] });
2189+ "# ;
2190+ let result = link ( & allocator, code, "test.mjs" ) ;
2191+ assert ! ( result. linked) ;
2192+ // Must have HostDirectivesFeature in the features array
2193+ assert ! (
2194+ result. code. contains( "HostDirectivesFeature" ) ,
2195+ "Should have HostDirectivesFeature in features array, got:\n {}" ,
2196+ result. code
2197+ ) ;
2198+ // Must NOT have hostDirectives as a direct property
2199+ assert ! (
2200+ !result. code. contains( "hostDirectives:" ) ,
2201+ "Should NOT have hostDirectives as a direct property, got:\n {}" ,
2202+ result. code
2203+ ) ;
2204+ }
2205+
2206+ /// Issue #71: hostDirectives with input/output mappings on a directive
2207+ #[ test]
2208+ fn test_link_directive_with_host_directives_mappings ( ) {
2209+ let allocator = Allocator :: default ( ) ;
2210+ let code = r#"
2211+ import * as i0 from "@angular/core";
2212+ class UnityTooltipTrigger {
2213+ }
2214+ UnityTooltipTrigger.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.0", ngImport: i0, type: UnityTooltipTrigger, selector: "[uTooltip]", isStandalone: true, hostDirectives: [{ directive: BrnTooltipTrigger, inputs: ["brnTooltipTrigger", "uTooltip"], outputs: ["onHide", "tooltipHidden"] }] });
2215+ "# ;
2216+ let result = link ( & allocator, code, "test.mjs" ) ;
2217+ assert ! ( result. linked) ;
2218+ assert ! (
2219+ result. code. contains( "HostDirectivesFeature" ) ,
2220+ "Should have HostDirectivesFeature, got:\n {}" ,
2221+ result. code
2222+ ) ;
2223+ assert ! (
2224+ !result. code. contains( "hostDirectives:" ) ,
2225+ "Should NOT have hostDirectives as a direct property, got:\n {}" ,
2226+ result. code
2227+ ) ;
2228+ }
2229+
2230+ /// Issue #71: hostDirectives on a component must go to HostDirectivesFeature
2231+ #[ test]
2232+ fn test_link_component_with_host_directives ( ) {
2233+ let allocator = Allocator :: default ( ) ;
2234+ let code = r#"
2235+ import * as i0 from "@angular/core";
2236+ class BrnMenu {
2237+ }
2238+ BrnMenu.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.0", ngImport: i0, type: BrnMenu, selector: "[brnMenu]", isStandalone: true, hostDirectives: [{ directive: CdkMenu }], template: "<ng-content></ng-content>" });
2239+ "# ;
2240+ let result = link ( & allocator, code, "test.mjs" ) ;
2241+ assert ! ( result. linked) ;
2242+ assert ! (
2243+ result. code. contains( "HostDirectivesFeature" ) ,
2244+ "Should have HostDirectivesFeature in features array, got:\n {}" ,
2245+ result. code
2246+ ) ;
2247+ assert ! (
2248+ !result. code. contains( "hostDirectives:" ) ,
2249+ "Should NOT have hostDirectives as a direct property, got:\n {}" ,
2250+ result. code
2251+ ) ;
2252+ }
2253+
2254+ /// Issue #71: Feature ordering — HostDirectivesFeature must come after ProvidersFeature
2255+ /// and before InheritDefinitionFeature
2256+ #[ test]
2257+ fn test_features_order_with_host_directives ( ) {
2258+ let allocator = Allocator :: default ( ) ;
2259+ let code = r#"
2260+ import * as i0 from "@angular/core";
2261+ class MyComp {
2262+ }
2263+ MyComp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.0", ngImport: i0, type: MyComp, selector: "my-comp", providers: [SomeProvider], hostDirectives: [{ directive: SomeDirective }], usesInheritance: true, usesOnChanges: true, template: "<div></div>" });
2264+ "# ;
2265+ let result = link ( & allocator, code, "test.mjs" ) ;
2266+ assert ! ( result. linked) ;
2267+ let code = & result. code ;
2268+ let providers_pos = code. find ( "ProvidersFeature" ) . expect ( "should have ProvidersFeature" ) ;
2269+ let host_dir_pos =
2270+ code. find ( "HostDirectivesFeature" ) . expect ( "should have HostDirectivesFeature" ) ;
2271+ let inherit_pos =
2272+ code. find ( "InheritDefinitionFeature" ) . expect ( "should have InheritDefinitionFeature" ) ;
2273+ let on_changes_pos =
2274+ code. find ( "NgOnChangesFeature" ) . expect ( "should have NgOnChangesFeature" ) ;
2275+ assert ! (
2276+ providers_pos < host_dir_pos,
2277+ "ProvidersFeature must come before HostDirectivesFeature"
2278+ ) ;
2279+ assert ! (
2280+ host_dir_pos < inherit_pos,
2281+ "HostDirectivesFeature must come before InheritDefinitionFeature"
2282+ ) ;
2283+ assert ! (
2284+ inherit_pos < on_changes_pos,
2285+ "InheritDefinitionFeature must come before NgOnChangesFeature"
2286+ ) ;
2287+ }
21802288}
0 commit comments