@@ -40,19 +40,12 @@ function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefin
4040 case BindingType . Attribute :
4141 return R3 . elementAttribute ;
4242 case BindingType . Class :
43- return R3 . elementClassNamed ;
43+ return R3 . elementClassProp ;
4444 default :
4545 return undefined ;
4646 }
4747}
4848
49- // `className` is used below instead of `class` because the interception
50- // code (where this map is used) deals with DOM element property values
51- // (like elm.propName) and not component bindining properties (like [propName]).
52- const SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP : { [ index : string ] : o . ExternalReference } = {
53- 'className' : R3 . elementClass
54- } ;
55-
5649export class TemplateDefinitionBuilder implements t . Visitor < void > , LocalResolver {
5750 private _dataIndex = 0 ;
5851 private _bindingContext = 0 ;
@@ -310,33 +303,59 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
310303 const i18nMessages : o . Statement [ ] = [ ] ;
311304 const attributes : o . Expression [ ] = [ ] ;
312305 const initialStyleDeclarations : o . Expression [ ] = [ ] ;
306+ const initialClassDeclarations : o . Expression [ ] = [ ] ;
313307
314308 const styleInputs : t . BoundAttribute [ ] = [ ] ;
309+ const classInputs : t . BoundAttribute [ ] = [ ] ;
315310 const allOtherInputs : t . BoundAttribute [ ] = [ ] ;
316311
317312 element . inputs . forEach ( ( input : t . BoundAttribute ) => {
318- // [attr.style] should not be treated as a styling-based
319- // binding since it is intended to write directly to the attr
320- // and therefore will skip all style resolution that is present
321- // with style="", [style]="" and [style.prop]="" assignments
322- if ( input . name == 'style' && input . type == BindingType . Property ) {
323- // this should always go first in the compilation (for [style])
324- styleInputs . splice ( 0 , 0 , input ) ;
325- } else if ( input . type == BindingType . Style ) {
326- styleInputs . push ( input ) ;
327- } else {
328- allOtherInputs . push ( input ) ;
313+ switch ( input . type ) {
314+ // [attr.style] or [attr.class] should not be treated as styling-based
315+ // bindings since they are intended to be written directly to the attr
316+ // and therefore will skip all style/class resolution that is present
317+ // with style="", [style]="" and [style.prop]="", class="",
318+ // [class.prop]="". [class]="" assignments
319+ case BindingType . Property :
320+ if ( input . name == 'style' ) {
321+ // this should always go first in the compilation (for [style])
322+ styleInputs . splice ( 0 , 0 , input ) ;
323+ } else if ( isClassBinding ( input ) ) {
324+ // this should always go first in the compilation (for [class])
325+ classInputs . splice ( 0 , 0 , input ) ;
326+ } else {
327+ allOtherInputs . push ( input ) ;
328+ }
329+ break ;
330+ case BindingType . Style :
331+ styleInputs . push ( input ) ;
332+ break ;
333+ case BindingType . Class :
334+ classInputs . push ( input ) ;
335+ break ;
336+ default :
337+ allOtherInputs . push ( input ) ;
338+ break ;
329339 }
330340 } ) ;
331341
332342 let currStyleIndex = 0 ;
343+ let currClassIndex = 0 ;
333344 let staticStylesMap : { [ key : string ] : any } | null = null ;
345+ let staticClassesMap : { [ key : string ] : boolean } | null = null ;
334346 const stylesIndexMap : { [ key : string ] : number } = { } ;
347+ const classesIndexMap : { [ key : string ] : number } = { } ;
335348 Object . getOwnPropertyNames ( outputAttrs ) . forEach ( name => {
336349 const value = outputAttrs [ name ] ;
337350 if ( name == 'style' ) {
338351 staticStylesMap = parseStyle ( value ) ;
339352 Object . keys ( staticStylesMap ) . forEach ( prop => { stylesIndexMap [ prop ] = currStyleIndex ++ ; } ) ;
353+ } else if ( name == 'class' ) {
354+ staticClassesMap = { } ;
355+ value . split ( / \s + / g) . forEach ( className => {
356+ classesIndexMap [ className ] = currClassIndex ++ ;
357+ staticClassesMap ! [ className ] = true ;
358+ } ) ;
340359 } else {
341360 attributes . push ( o . literal ( name ) ) ;
342361 if ( attrI18nMetas . hasOwnProperty ( name ) ) {
@@ -357,14 +376,22 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
357376 }
358377 }
359378
379+ for ( let i = 0 ; i < classInputs . length ; i ++ ) {
380+ const input = classInputs [ i ] ;
381+ const isMapBasedClassBinding = i === 0 && isClassBinding ( input ) ;
382+ if ( ! isMapBasedClassBinding && ! stylesIndexMap . hasOwnProperty ( input . name ) ) {
383+ classesIndexMap [ input . name ] = currClassIndex ++ ;
384+ }
385+ }
386+
360387 // this will build the instructions so that they fall into the following syntax
361388 // => [prop1, prop2, prop3, 0, prop1, value1, prop2, value2]
362389 Object . keys ( stylesIndexMap ) . forEach ( prop => {
363390 initialStyleDeclarations . push ( o . literal ( prop ) ) ;
364391 } ) ;
365392
366393 if ( staticStylesMap ) {
367- initialStyleDeclarations . push ( o . literal ( core . InitialStylingFlags . INITIAL_STYLES ) ) ;
394+ initialStyleDeclarations . push ( o . literal ( core . InitialStylingFlags . VALUES_MODE ) ) ;
368395
369396 Object . keys ( staticStylesMap ) . forEach ( prop => {
370397 initialStyleDeclarations . push ( o . literal ( prop ) ) ;
@@ -373,6 +400,22 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
373400 } ) ;
374401 }
375402
403+ Object . keys ( classesIndexMap ) . forEach ( prop => {
404+ initialClassDeclarations . push ( o . literal ( prop ) ) ;
405+ } ) ;
406+
407+ if ( staticClassesMap ) {
408+ initialClassDeclarations . push ( o . literal ( core . InitialStylingFlags . VALUES_MODE ) ) ;
409+
410+ Object . keys ( staticClassesMap ) . forEach ( className => {
411+ initialClassDeclarations . push ( o . literal ( className ) ) ;
412+ initialClassDeclarations . push ( o . literal ( true ) ) ;
413+ } ) ;
414+ }
415+
416+ const hasStylingInstructions = initialStyleDeclarations . length || styleInputs . length ||
417+ initialClassDeclarations . length || classInputs . length ;
418+
376419 const attrArg : o . Expression = attributes . length > 0 ?
377420 this . constantPool . getConstLiteral ( o . literalArr ( attributes ) , true ) :
378421 o . TYPED_NULL_EXPR ;
@@ -411,10 +454,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
411454
412455 const implicit = o . variable ( CONTEXT_NAME ) ;
413456
414- const elementStyleIndex =
415- ( initialStyleDeclarations . length || styleInputs . length ) ? this . allocateDataSlot ( ) : 0 ;
416457 const createSelfClosingInstruction =
417- elementStyleIndex === 0 && element . children . length === 0 && element . outputs . length === 0 ;
458+ ! hasStylingInstructions && element . children . length === 0 && element . outputs . length === 0 ;
418459
419460 if ( createSelfClosingInstruction ) {
420461 this . instruction (
@@ -429,16 +470,30 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
429470 ...trimTrailingNulls ( parameters ) ) ;
430471
431472 // initial styling for static style="..." attributes
432- if ( elementStyleIndex > 0 ) {
433- let paramsList : ( o . Expression ) [ ] = [ o . literal ( elementStyleIndex ) ] ;
473+ if ( hasStylingInstructions ) {
474+ const paramsList : ( o . Expression ) [ ] = [ ] ;
475+
434476 if ( initialStyleDeclarations . length ) {
435- // the template compiler handles initial styling (e.g. style="foo") values
477+ // the template compiler handles initial style (e.g. style="foo") values
436478 // in a special command called `elementStyle` so that the initial styles
437479 // can be processed during runtime. These initial styles values are bound to
438480 // a constant because the inital style values do not change (since they're static).
439481 paramsList . push (
440482 this . constantPool . getConstLiteral ( o . literalArr ( initialStyleDeclarations ) , true ) ) ;
483+ } else if ( initialClassDeclarations . length ) {
484+ // no point in having an extra `null` value unless there are follow-up params
485+ paramsList . push ( o . NULL_EXPR ) ;
486+ }
487+
488+ if ( initialClassDeclarations . length ) {
489+ // the template compiler handles initial class styling (e.g. class="foo") values
490+ // in a special command called `elementClass` so that the initial class
491+ // can be processed during runtime. These initial class values are bound to
492+ // a constant because the inital class values do not change (since they're static).
493+ paramsList . push (
494+ this . constantPool . getConstLiteral ( o . literalArr ( initialClassDeclarations ) , true ) ) ;
441495 }
496+
442497 this . _creationCode . push ( o . importExpr ( R3 . elementStyling ) . callFn ( paramsList ) . toStmt ( ) ) ;
443498 }
444499
@@ -465,25 +520,64 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
465520 } ) ;
466521 }
467522
468- if ( styleInputs . length && elementStyleIndex > 0 ) {
469- const indexLiteral = o . literal ( elementStyleIndex ) ;
470- styleInputs . forEach ( ( input , i ) => {
471- const isMapBasedStyleBinding = i == 0 && input . name == 'style' ;
472- const convertedBinding = this . convertPropertyBinding ( implicit , input . value , true ) ;
473- if ( isMapBasedStyleBinding ) {
474- this . instruction (
475- this . _bindingCode , input . sourceSpan , R3 . elementStyle , indexLiteral , convertedBinding ) ;
476- } else {
523+ if ( ( styleInputs . length || classInputs . length ) && hasStylingInstructions ) {
524+ const indexLiteral = o . literal ( elementIndex ) ;
525+
526+ const firstStyle = styleInputs [ 0 ] ;
527+ const mapBasedStyleInput = firstStyle && firstStyle . name == 'style' ? firstStyle : null ;
528+
529+ const firstClass = classInputs [ 0 ] ;
530+ const mapBasedClassInput = firstClass && isClassBinding ( firstClass ) ? firstClass : null ;
531+
532+ const stylingInput = mapBasedStyleInput || mapBasedClassInput ;
533+ if ( stylingInput ) {
534+ const params : o . Expression [ ] = [ ] ;
535+ if ( mapBasedStyleInput ) {
536+ params . push ( this . convertPropertyBinding ( implicit , mapBasedStyleInput . value , true ) ) ;
537+ } else if ( mapBasedClassInput ) {
538+ params . push ( o . NULL_EXPR ) ;
539+ }
540+ if ( mapBasedClassInput ) {
541+ params . push ( this . convertPropertyBinding ( implicit , mapBasedClassInput . value , true ) ) ;
542+ }
543+ this . instruction (
544+ this . _bindingCode , stylingInput . sourceSpan , R3 . elementStylingMap , indexLiteral ,
545+ ...params ) ;
546+ }
547+
548+ let lastInputCommand : t . BoundAttribute | null = null ;
549+ if ( styleInputs . length ) {
550+ let i = mapBasedStyleInput ? 1 : 0 ;
551+ for ( i ; i < styleInputs . length ; i ++ ) {
552+ const input = styleInputs [ i ] ;
553+ const convertedBinding = this . convertPropertyBinding ( implicit , input . value , true ) ;
477554 const key = input . name ;
478- let styleIndex : number = stylesIndexMap [ key ] ! ;
555+ const styleIndex : number = stylesIndexMap [ key ] ! ;
479556 this . instruction (
480557 this . _bindingCode , input . sourceSpan , R3 . elementStyleProp , indexLiteral ,
481558 o . literal ( styleIndex ) , convertedBinding ) ;
482559 }
483- } ) ;
484560
485- const spanEnd = styleInputs [ styleInputs . length - 1 ] . sourceSpan ;
486- this . instruction ( this . _bindingCode , spanEnd , R3 . elementStylingApply , indexLiteral ) ;
561+ lastInputCommand = styleInputs [ styleInputs . length - 1 ] ;
562+ }
563+
564+ if ( classInputs . length ) {
565+ let i = mapBasedClassInput ? 1 : 0 ;
566+ for ( i ; i < classInputs . length ; i ++ ) {
567+ const input = classInputs [ i ] ;
568+ const convertedBinding = this . convertPropertyBinding ( implicit , input . value , true ) ;
569+ const key = input . name ;
570+ const classIndex : number = classesIndexMap [ key ] ! ;
571+ this . instruction (
572+ this . _bindingCode , input . sourceSpan , R3 . elementClassProp , indexLiteral ,
573+ o . literal ( classIndex ) , convertedBinding ) ;
574+ }
575+
576+ lastInputCommand = classInputs [ classInputs . length - 1 ] ;
577+ }
578+
579+ this . instruction (
580+ this . _bindingCode , lastInputCommand ! . sourceSpan , R3 . elementStylingApply , indexLiteral ) ;
487581 }
488582
489583 // Generate element input bindings
@@ -494,18 +588,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
494588 }
495589
496590 const convertedBinding = this . convertPropertyBinding ( implicit , input . value ) ;
497- const specialInstruction = SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP [ input . name ] ;
498- if ( specialInstruction ) {
499- // special case for [style] and [class] bindings since they are not handled as
500- // standard properties within this implementation. Instead they are
501- // handed off to special cased instruction handlers which will then
502- // delegate them as animation sequences (or input bindings for dirs/cmps)
503- this . instruction (
504- this . _bindingCode , input . sourceSpan , specialInstruction , o . literal ( elementIndex ) ,
505- convertedBinding ) ;
506- return ;
507- }
508-
509591 const instruction = mapBindingToInstruction ( input . type ) ;
510592 if ( instruction ) {
511593 // TODO(chuckj): runtime: security context?
@@ -975,3 +1057,7 @@ export function makeBindingParser(): BindingParser {
9751057 new Parser ( new Lexer ( ) ) , DEFAULT_INTERPOLATION_CONFIG , new DomElementSchemaRegistry ( ) , null ,
9761058 [ ] ) ;
9771059}
1060+
1061+ function isClassBinding ( input : t . BoundAttribute ) : boolean {
1062+ return input . name == 'className' || input . name == 'class' ;
1063+ }
0 commit comments