diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts index 2a13f675b1..6a63a39bd4 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts @@ -1270,6 +1270,28 @@ describe('i18n support in the view compiler', () => { verify(input, output); }); + + it('should generate event listeners instructions before i18n ones', () => { + const input = ` +
Hello
+ `; + + const output = String.raw ` + const $_c0$ = [${AttributeMarker.Bindings}, "click"]; + const $MSG_EXTERNAL_3902961887793684628$$APP_SPEC_TS_1$ = goog.getMsg("Hello"); + … + template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵelementStart(0, "div", $_c0$); + $r3$.ɵlistener("click", function MyComponent_Template_div_click_0_listener($event) { return ctx.onClick(); }); + $r3$.ɵi18n(1, $MSG_EXTERNAL_3902961887793684628$$APP_SPEC_TS_1$); + $r3$.ɵelementEnd(); + } + } + `; + + verify(input, output); + }); }); describe('self-closing i18n instructions', () => { diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 5038bf8b44..bca6947265 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -657,12 +657,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } } - // Note: it's important to keep i18n/i18nStart instructions after i18nAttributes ones, - // to make sure i18nAttributes instruction targets current element at runtime. - if (isI18nRootElement) { - this.i18nStart(element.sourceSpan, element.i18n !, createSelfClosingI18nInstruction); - } - // The style bindings code is placed into two distinct blocks within the template function AOT // code: creation and update. The creation code contains the `elementStyling` instructions // which will apply the collected binding values to the element. `elementStyling` is @@ -680,6 +674,12 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver outputAst.sourceSpan, R3.listener, this.prepareListenerParameter(element.name, outputAst, elementIndex)); }); + + // Note: it's important to keep i18n/i18nStart instructions after i18nAttributes and + // listeners, to make sure i18nAttributes instruction targets current element at runtime. + if (isI18nRootElement) { + this.i18nStart(element.sourceSpan, element.i18n !, createSelfClosingI18nInstruction); + } } // the code here will collect all update-level styling instructions and add them to the diff --git a/packages/core/test/i18n_integration_spec.ts b/packages/core/test/i18n_integration_spec.ts index 7a9e297d39..e04bc596e0 100644 --- a/packages/core/test/i18n_integration_spec.ts +++ b/packages/core/test/i18n_integration_spec.ts @@ -28,6 +28,9 @@ class MyComp { age = 20; count = 2; otherLabel = 'other label'; + clicks = 0; + + onClick() { this.clicks++; } } const TRANSLATIONS: any = { @@ -254,6 +257,23 @@ onlyInIvy('Ivy i18n logic').describe('i18n', function() { const element = fixture.nativeElement.firstChild; expect(element).toHaveText('Bonjour John'); }); + + it('should work correctly with event listeners', () => { + const content = 'Hello {{ name }}'; + const template = ` +
${content}
+ `; + const fixture = getFixtureWithOverrides({template}); + + const element = fixture.nativeElement.firstChild; + const instance = fixture.componentInstance; + + expect(element).toHaveText('Bonjour John'); + expect(instance.clicks).toBe(0); + + element.click(); + expect(instance.clicks).toBe(1); + }); }); describe('ng-container and ng-template support', () => {