@@ -219,7 +219,7 @@ function i18nStartFirstCreatePassProcessTextNode(
219219 const tNode = createTNodeAndAddOpCode (
220220 tView , rootTNode , existingTNodes , lView , createOpCodes , hasBinding ? null : text , false ) ;
221221 if ( hasBinding ) {
222- generateBindingUpdateOpCodes ( updateOpCodes , text , tNode . index ) ;
222+ generateBindingUpdateOpCodes ( updateOpCodes , text , tNode . index , null , 0 , null ) ;
223223 }
224224}
225225
@@ -251,7 +251,11 @@ export function i18nAttributesFirstPass(tView: TView, index: number, values: str
251251
252252 // i18n attributes that hit this code path are guaranteed to have bindings, because
253253 // the compiler treats static i18n attributes as regular attribute bindings.
254- generateBindingUpdateOpCodes ( updateOpCodes , message , previousElementIndex , attrName ) ;
254+ // Since this may not be the first i18n attribute on this element we need to pass in how
255+ // many previous bindings there have already been.
256+ generateBindingUpdateOpCodes (
257+ updateOpCodes , message , previousElementIndex , attrName , countBindings ( updateOpCodes ) ,
258+ null ) ;
255259 }
256260 }
257261 tView . data [ index ] = updateOpCodes ;
@@ -267,10 +271,12 @@ export function i18nAttributesFirstPass(tView: TView, index: number, values: str
267271 * @param destinationNode Index of the destination node which will receive the binding.
268272 * @param attrName Name of the attribute, if the string belongs to an attribute.
269273 * @param sanitizeFn Sanitization function used to sanitize the string after update, if necessary.
274+ * @param bindingStart The lView index of the next expression that can be bound via an opCode.
275+ * @returns The mask value for these bindings
270276 */
271- export function generateBindingUpdateOpCodes (
272- updateOpCodes : I18nUpdateOpCodes , str : string , destinationNode : number , attrName ? : string ,
273- sanitizeFn : SanitizerFn | null = null ) : number {
277+ function generateBindingUpdateOpCodes (
278+ updateOpCodes : I18nUpdateOpCodes , str : string , destinationNode : number , attrName : string | null ,
279+ bindingStart : number , sanitizeFn : SanitizerFn | null ) : number {
274280 ngDevMode &&
275281 assertGreaterThanOrEqual (
276282 destinationNode , HEADER_OFFSET , 'Index must be in absolute LView offset' ) ;
@@ -289,7 +295,7 @@ export function generateBindingUpdateOpCodes(
289295
290296 if ( j & 1 ) {
291297 // Odd indexes are bindings
292- const bindingIndex = parseInt ( textValue , 10 ) ;
298+ const bindingIndex = bindingStart + parseInt ( textValue , 10 ) ;
293299 updateOpCodes . push ( - 1 - bindingIndex ) ;
294300 mask = mask | toMaskBit ( bindingIndex ) ;
295301 } else if ( textValue !== '' ) {
@@ -309,6 +315,28 @@ export function generateBindingUpdateOpCodes(
309315 return mask ;
310316}
311317
318+ /**
319+ * Count the number of bindings in the given `opCodes`.
320+ *
321+ * It could be possible to speed this up, by passing the number of bindings found back from
322+ * `generateBindingUpdateOpCodes()` to `i18nAttributesFirstPass()` but this would then require more
323+ * complexity in the code and/or transient objects to be created.
324+ *
325+ * Since this function is only called once when the template is instantiated, is trivial in the
326+ * first instance (since `opCodes` will be an empty array), and it is not common for elements to
327+ * contain multiple i18n bound attributes, it seems like this is a reasonable compromise.
328+ */
329+ function countBindings ( opCodes : I18nUpdateOpCodes ) : number {
330+ let count = 0 ;
331+ for ( let i = 0 ; i < opCodes . length ; i ++ ) {
332+ const opCode = opCodes [ i ] ;
333+ // Bindings are negative numbers.
334+ if ( typeof opCode === 'number' && opCode < 0 ) {
335+ count ++ ;
336+ }
337+ }
338+ return count ;
339+ }
312340
313341/**
314342 * Convert binding index to mask bit.
@@ -595,12 +623,12 @@ function walkIcuTree(
595623 if ( VALID_ATTRS . hasOwnProperty ( lowerAttrName ) ) {
596624 if ( URI_ATTRS [ lowerAttrName ] ) {
597625 generateBindingUpdateOpCodes (
598- update , attr . value , newIndex , attr . name , _sanitizeUrl ) ;
626+ update , attr . value , newIndex , attr . name , 0 , _sanitizeUrl ) ;
599627 } else if ( SRCSET_ATTRS [ lowerAttrName ] ) {
600628 generateBindingUpdateOpCodes (
601- update , attr . value , newIndex , attr . name , sanitizeSrcset ) ;
629+ update , attr . value , newIndex , attr . name , 0 , sanitizeSrcset ) ;
602630 } else {
603- generateBindingUpdateOpCodes ( update , attr . value , newIndex , attr . name ) ;
631+ generateBindingUpdateOpCodes ( update , attr . value , newIndex , attr . name , 0 , null ) ;
604632 }
605633 } else {
606634 ngDevMode &&
@@ -627,7 +655,8 @@ function walkIcuTree(
627655 addCreateNodeAndAppend ( create , null , hasBinding ? '' : value , parentIdx , newIndex ) ;
628656 addRemoveNode ( remove , newIndex , depth ) ;
629657 if ( hasBinding ) {
630- bindingMask = generateBindingUpdateOpCodes ( update , value , newIndex ) | bindingMask ;
658+ bindingMask =
659+ generateBindingUpdateOpCodes ( update , value , newIndex , null , 0 , null ) | bindingMask ;
631660 }
632661 break ;
633662 case Node . COMMENT_NODE :
0 commit comments