@@ -120,6 +120,23 @@ export function compileComponent(
120120 addDependencyToComponent ( outputCtx , summary , pipeSet , pipeExps ) ;
121121 }
122122
123+ const directiveExps : o . Expression [ ] = [ ] ;
124+ const directiveMap = new Set < string > ( ) ;
125+ /**
126+ * This function gets called every time a directive dependency needs to be added to the template.
127+ * Its job is to remove duplicates from the list. (Only have single dependency no matter how many
128+ * times the dependency is used.)
129+ */
130+ function addDirectiveDependency ( ast : DirectiveAst ) {
131+ const importExpr = outputCtx . importExpr ( ast . directive . type . reference ) as o . ExternalExpr ;
132+ const uniqueKey = importExpr . value . moduleName + ':' + importExpr . value . name ;
133+
134+ if ( ! directiveMap . has ( uniqueKey ) ) {
135+ directiveMap . add ( uniqueKey ) ;
136+ directiveExps . push ( importExpr ) ;
137+ }
138+ }
139+
123140 const field = ( key : string , value : o . Expression | null ) => {
124141 if ( value ) {
125142 definitionMapValues . push ( { key, value, quoted : false } ) ;
@@ -162,10 +179,13 @@ export function compileComponent(
162179 new TemplateDefinitionBuilder (
163180 outputCtx , outputCtx . constantPool , reflector , CONTEXT_NAME , ROOT_SCOPE . nestedScope ( ) , 0 ,
164181 component . template ! . ngContentSelectors , templateTypeName , templateName , pipeMap ,
165- component . viewQueries , addPipeDependency )
182+ component . viewQueries , addDirectiveDependency , addPipeDependency )
166183 . buildTemplateFunction ( template , [ ] ) ;
167184
168185 field ( 'template' , templateFunctionExpression ) ;
186+ if ( directiveExps . length ) {
187+ field ( 'directives' , o . literalArr ( directiveExps ) ) ;
188+ }
169189
170190 // e.g. `pipes: [MyPipe]`
171191 if ( pipeExps . length ) {
@@ -373,6 +393,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
373393 private bindingScope : BindingScope , private level = 0 , private ngContentSelectors : string [ ] ,
374394 private contextName : string | null , private templateName : string | null ,
375395 private pipes : Map < string , CompilePipeSummary > , private viewQueries : CompileQueryMetadata [ ] ,
396+ private addDirectiveDependency : ( ast : DirectiveAst ) => void ,
376397 private addPipeDependency : ( summary : CompilePipeSummary ) => void ) {
377398 this . _valueConverter = new ValueConverter (
378399 outputCtx , ( ) => this . allocateDataSlot ( ) , ( name , localName , slot , value ) => {
@@ -504,23 +525,9 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
504525 this . instruction ( this . _creationMode , ast . sourceSpan , R3 . projection , ...parameters ) ;
505526 }
506527
507- private _computeDirectivesArray ( directives : DirectiveAst [ ] ) {
508- const directiveExpressions : o . Expression [ ] =
509- directives . filter ( directive => ! directive . directive . isComponent ) . map ( directive => {
510- this . allocateDataSlot ( ) ; // Allocate space for the directive
511- return this . typeReference ( directive . directive . type . reference ) ;
512- } ) ;
513- return directiveExpressions . length ?
514- this . constantPool . getConstLiteral (
515- o . literalArr ( directiveExpressions ) , /* forceShared */ true ) :
516- o . literal ( null , o . INFERRED_TYPE ) ;
517- ;
518- }
519-
520528 // TemplateAstVisitor
521529 visitElement ( element : ElementAst ) {
522530 const elementIndex = this . allocateDataSlot ( ) ;
523- let componentIndex : number | undefined = undefined ;
524531 const referenceDataSlots = new Map < string , number > ( ) ;
525532 const wasInI18nSection = this . _inI18nSection ;
526533
@@ -563,13 +570,11 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
563570 const nullNode = o . literal ( null , o . INFERRED_TYPE ) ;
564571 const parameters : o . Expression [ ] = [ o . literal ( elementIndex ) ] ;
565572
566- // Add component type or element tag
567573 if ( component ) {
568- parameters . push ( this . typeReference ( component . directive . type . reference ) ) ;
569- componentIndex = this . allocateDataSlot ( ) ;
570- } else {
571- parameters . push ( o . literal ( element . name ) ) ;
574+ this . addDirectiveDependency ( component ) ;
572575 }
576+ element . directives . forEach ( this . addDirectiveDependency ) ;
577+ parameters . push ( o . literal ( element . name ) ) ;
573578
574579 // Add the attributes
575580 const i18nMessages : o . Statement [ ] = [ ] ;
@@ -598,10 +603,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
598603
599604 parameters . push ( attrArg ) ;
600605
601- // Add directives array
602- const directivesArray = this . _computeDirectivesArray ( element . directives ) ;
603- parameters . push ( directivesArray ) ;
604-
605606 if ( element . references && element . references . length > 0 ) {
606607 const references =
607608 flatten ( element . references . map ( reference => {
@@ -621,16 +622,12 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
621622 parameters . push ( nullNode ) ;
622623 }
623624
624- // Remove trailing null nodes as they are implied.
625- while ( o . isNull ( parameters [ parameters . length - 1 ] ) ) {
626- parameters . pop ( ) ;
627- }
628-
629625 // Generate the instruction create element instruction
630626 if ( i18nMessages . length > 0 ) {
631627 this . _creationMode . push ( ...i18nMessages ) ;
632628 }
633- this . instruction ( this . _creationMode , element . sourceSpan , R3 . createElement , ...parameters ) ;
629+ this . instruction (
630+ this . _creationMode , element . sourceSpan , R3 . createElement , ...trimTrailingNulls ( parameters ) ) ;
634631
635632 const implicit = o . variable ( this . contextParameter ) ;
636633
@@ -725,28 +722,41 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
725722 contextName ? `${ contextName } _Template_${ templateIndex } ` : `Template_${ templateIndex } ` ;
726723 const templateContext = `ctx${ this . level } ` ;
727724
728- const directivesArray = this . _computeDirectivesArray ( ast . directives ) ;
725+ const parameters : o . Expression [ ] = [ o . variable ( templateName ) , o . literal ( null , o . INFERRED_TYPE ) ] ;
726+ const attributeNames : o . Expression [ ] = [ ] ;
727+ ast . directives . forEach ( ( directiveAst : DirectiveAst ) => {
728+ this . addDirectiveDependency ( directiveAst ) ;
729+ CssSelector . parse ( directiveAst . directive . selector ! ) . forEach ( selector => {
730+ selector . attrs . forEach ( ( value ) => {
731+ // Convert '' (falsy) strings into `null`. This is needed because we want
732+ // to communicate to runtime that these attributes are present for
733+ // selector matching, but should not actually be added to the DOM.
734+ // attributeNames.push(o.literal(value ? value : null));
735+
736+ // TODO(misko): make the above comment true, for now just write to DOM because
737+ // the runtime selectors have not been updated.
738+ attributeNames . push ( o . literal ( value ) ) ;
739+ } ) ;
740+ } ) ;
741+ } ) ;
742+ if ( attributeNames . length ) {
743+ parameters . push (
744+ this . constantPool . getConstLiteral ( o . literalArr ( attributeNames ) , /* forcedShared */ true ) ) ;
745+ }
729746
730747 // e.g. C(1, C1Template)
731748 this . instruction (
732749 this . _creationMode , ast . sourceSpan , R3 . containerCreate , o . literal ( templateIndex ) ,
733- directivesArray , o . variable ( templateName ) ) ;
734-
735- // e.g. Cr(1)
736- this . instruction (
737- this . _refreshMode , ast . sourceSpan , R3 . containerRefreshStart , o . literal ( templateIndex ) ) ;
750+ ...trimTrailingNulls ( parameters ) ) ;
738751
739752 // Generate directives
740753 this . _visitDirectives ( ast . directives , o . variable ( this . contextParameter ) , templateIndex ) ;
741754
742- // e.g. cr();
743- this . instruction ( this . _refreshMode , ast . sourceSpan , R3 . containerRefreshEnd ) ;
744-
745755 // Create the template function
746756 const templateVisitor = new TemplateDefinitionBuilder (
747757 this . outputCtx , this . constantPool , this . reflector , templateContext ,
748758 this . bindingScope . nestedScope ( ) , this . level + 1 , this . ngContentSelectors , contextName ,
749- templateName , this . pipes , [ ] , this . addPipeDependency ) ;
759+ templateName , this . pipes , [ ] , this . addDirectiveDependency , this . addPipeDependency ) ;
750760 const templateFunctionExpr = templateVisitor . buildTemplateFunction ( ast . children , ast . variables ) ;
751761 this . _postfix . push ( templateFunctionExpr . toDeclStmt ( templateName , null ) ) ;
752762 }
@@ -811,8 +821,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
811821 statements . push ( o . importExpr ( reference , null , span ) . callFn ( params , span ) . toStmt ( ) ) ;
812822 }
813823
814- private typeReference ( type : any ) : o . Expression { return this . outputCtx . importExpr ( type ) ; }
815-
816824 private definitionOf ( type : any , kind : DefinitionKind ) : o . Expression {
817825 return this . constantPool . getDefinition ( type , kind , this . outputCtx ) ;
818826 }
@@ -923,6 +931,16 @@ export function createFactory(
923931 type . reference . name ? `${ type . reference . name } _Factory` : null ) ;
924932}
925933
934+ /**
935+ * Remove trailing null nodes as they are implied.
936+ */
937+ function trimTrailingNulls ( parameters : o . Expression [ ] ) : o . Expression [ ] {
938+ while ( o . isNull ( parameters [ parameters . length - 1 ] ) ) {
939+ parameters . pop ( ) ;
940+ }
941+ return parameters ;
942+ }
943+
926944type HostBindings = {
927945 [ key : string ] : string
928946} ;
0 commit comments