@@ -21,8 +21,9 @@ import {
2121} from '../state' ;
2222import { getNativeByTNode } from '../util/view_utils' ;
2323import { debugStringifyTypeForError } from '../util/stringify_utils' ;
24- import { listenToOutput } from '../view/directive_outputs' ;
24+ import { listenToDirectiveOutput } from '../view/directive_outputs' ;
2525import { listenToDomEvent , wrapListener } from '../view/listeners' ;
26+ import { setDirectiveInput } from './shared' ;
2627import { writeToDirectiveInput } from './write_to_directive_input' ;
2728
2829/**
@@ -118,20 +119,11 @@ class ControlDirectiveHostImpl implements ControlDirectiveHost {
118119 }
119120
120121 listenToCustomControlOutput ( outputName : string , callback : ( event : Event ) => void ) : void {
121- if (
122- ! hasOutput (
123- this . tView . data [ this . tNode . customControlIndex ] as DirectiveDef < unknown > ,
124- outputName ,
125- )
126- ) {
127- return ;
128- }
129-
130- listenToOutput (
122+ const directiveDef = this . tView . data [ this . tNode . customControlIndex ] as DirectiveDef < unknown > ;
123+ listenToDirectiveOutput (
131124 this . tNode ,
132125 this . lView ,
133- this . tNode . customControlIndex ,
134- outputName ,
126+ directiveDef ,
135127 outputName ,
136128 wrapListener ( this . tNode , this . lView , callback ) ,
137129 ) ;
@@ -140,11 +132,11 @@ class ControlDirectiveHostImpl implements ControlDirectiveHost {
140132 listenToCustomControlModel ( listener : ( value : unknown ) => void ) : void {
141133 const modelName =
142134 this . tNode . flags & TNodeFlags . isFormValueControl ? 'valueChange' : 'checkedChange' ;
143- listenToOutput (
135+ const directiveDef = this . tView . data [ this . tNode . customControlIndex ] as DirectiveDef < unknown > ;
136+ listenToDirectiveOutput (
144137 this . tNode ,
145138 this . lView ,
146- this . tNode . customControlIndex ,
147- modelName ,
139+ directiveDef ,
148140 modelName ,
149141 wrapListener ( this . tNode , this . lView , listener ) ,
150142 ) ;
@@ -203,21 +195,78 @@ class ControlDirectiveHostImpl implements ControlDirectiveHost {
203195 }
204196
205197 setCustomControlModelInput ( value : unknown ) : void {
206- const directive = this . lView [ this . tNode . customControlIndex ] ;
207198 const directiveDef = this . tView . data [ this . tNode . customControlIndex ] as DirectiveDef < { } > ;
208199 const modelName = this . tNode . flags & TNodeFlags . isFormValueControl ? 'value' : 'checked' ;
209- writeToDirectiveInput ( directiveDef , directive , modelName , value ) ;
200+ setDirectiveInput ( this . tNode , this . tView , this . lView , directiveDef , modelName , value ) ;
210201 }
211202
212203 customControlHasInput ( inputName : string ) : boolean {
213204 if ( this . tNode . customControlIndex === - 1 ) {
214205 return false ;
215206 }
216207 const directiveDef = this . tView . data [ this . tNode . customControlIndex ] as DirectiveDef < unknown > ;
217- return directiveDef . inputs [ inputName ] != undefined ;
208+ const presence = ( directiveDef . signalFormsInputPresence ??=
209+ this . _buildCustomControlInputCache ( directiveDef ) ) ;
210+ return presence [ inputName ] === true ;
211+ }
212+
213+ private _buildCustomControlInputCache ( directiveDef : DirectiveDef < unknown > ) : {
214+ [ key : string ] : boolean ;
215+ } {
216+ const cache : { [ key : string ] : boolean } = { } ;
217+
218+ // First, add all inputs defined directly on the custom control directive.
219+ for ( const key in directiveDef . inputs ) {
220+ cache [ key ] = true ;
221+ }
222+
223+ // Next, gather inputs exposed by host directives recursively.
224+ if ( directiveDef . hostDirectives !== null ) {
225+ const queue = [ ...directiveDef . hostDirectives ] ;
226+ while ( queue . length > 0 ) {
227+ const hostDir = queue . shift ( ) ! ;
228+ if ( typeof hostDir !== 'function' ) {
229+ // HostDirectiveDef object
230+ for ( const key in hostDir . inputs ) {
231+ cache [ hostDir . inputs [ key ] ] = true ;
232+ }
233+ const hostDirectives = getHostDirectives ( hostDir . directive ) ;
234+ if ( hostDirectives !== null ) {
235+ queue . push ( ...hostDirectives ) ;
236+ }
237+ continue ;
238+ }
239+
240+ // Factory function returning HostDirectiveConfig[]
241+ for ( const config of hostDir ( ) ) {
242+ if ( typeof config === 'function' ) {
243+ continue ;
244+ }
245+ if ( config . inputs ) {
246+ for ( let i = 0 ; i < config . inputs . length ; i += 2 ) {
247+ const exposedName = config . inputs [ i + 1 ] || config . inputs [ i ] ;
248+ cache [ exposedName ] = true ;
249+ }
250+ }
251+ const hostDirectives = getHostDirectives ( config . directive ) ;
252+ if ( hostDirectives !== null ) {
253+ queue . push ( ...hostDirectives ) ;
254+ }
255+ }
256+ }
257+ }
258+
259+ return cache ;
218260 }
219261}
220262
263+ function getHostDirectives ( directiveType : any ) : readonly any [ ] | null {
264+ if ( typeof directiveType === 'function' && 'ɵdir' in directiveType ) {
265+ return ( directiveType as any ) . ɵdir . hostDirectives ?? null ;
266+ }
267+ return null ;
268+ }
269+
221270function initializeControlFirstCreatePass ( tView : TView , tNode : TNode , lView : LView ) : void {
222271 ngDevMode && assertFirstCreatePass ( tView ) ;
223272
@@ -261,6 +310,11 @@ function initializeControlFirstCreatePass(tView: TView, tNode: TNode, lView: LVi
261310function initializeCustomControlStatus ( tView : TView , tNode : TNode ) : void {
262311 for ( let i = tNode . directiveStart ; i < tNode . directiveEnd ; i ++ ) {
263312 const directiveDef = tView . data [ i ] as DirectiveDef < unknown > ;
313+ // Host directives shouldn't be matched directly since their types are not in
314+ // `directiveToIndex`. We match them through their host component's `hostDirectiveInputs` instead.
315+ if ( tNode . directiveToIndex && ! tNode . directiveToIndex . has ( directiveDef . type ) ) {
316+ continue ;
317+ }
264318 if ( hasModelInput ( directiveDef , 'value' ) ) {
265319 tNode . flags |= TNodeFlags . isFormValueControl ;
266320 tNode . customControlIndex = i ;
@@ -272,6 +326,50 @@ function initializeCustomControlStatus(tView: TView, tNode: TNode): void {
272326 return ;
273327 }
274328 }
329+
330+ if (
331+ tNode . hostDirectiveInputs !== null &&
332+ tNode . hostDirectiveOutputs !== null &&
333+ tNode . directiveToIndex !== null
334+ ) {
335+ const checkModel = ( modelName : string , flag : TNodeFlags ) => {
336+ const inputs = tNode . hostDirectiveInputs ! [ modelName ] ;
337+ const outputs = tNode . hostDirectiveOutputs ! [ modelName + 'Change' ] ;
338+ if ( ! inputs || ! outputs ) {
339+ return false ;
340+ }
341+
342+ for ( let i = 0 ; i < inputs . length ; i += 2 ) {
343+ const inputIndex = inputs [ i ] as number ;
344+ for ( let j = 0 ; j < outputs . length ; j += 2 ) {
345+ const outputIndex = outputs [ j ] as number ;
346+ // TODO: invert control flow logic here.
347+ if ( inputIndex !== outputIndex ) {
348+ continue ;
349+ }
350+ for ( const data of tNode . directiveToIndex ! . values ( ) ) {
351+ if ( ! Array . isArray ( data ) ) {
352+ continue ;
353+ }
354+ const [ hostIndex , start , end ] = data ;
355+ if ( inputIndex >= start && inputIndex <= end ) {
356+ tNode . flags |= flag ;
357+ tNode . customControlIndex = hostIndex ;
358+ return true ;
359+ }
360+ }
361+ }
362+ }
363+ return false ;
364+ } ;
365+
366+ if ( checkModel ( 'value' , TNodeFlags . isFormValueControl ) ) {
367+ return ;
368+ }
369+ if ( checkModel ( 'checked' , TNodeFlags . isFormCheckboxControl ) ) {
370+ return ;
371+ }
372+ }
275373}
276374
277375/**
0 commit comments