perf(ivy): chain listener instructions (#33720)
Chains multiple listener instructions on a particular element into a single call which results in less generated code. Also handles listeners on templates, host listeners and synthetic host listeners. PR Close #33720
This commit is contained in:
@ -1134,6 +1134,113 @@ describe('compiler compliance: bindings', () => {
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain multiple host listeners into a single instruction', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'example.ts': `
|
||||
import {Directive, HostListener} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[my-dir]',
|
||||
host: {
|
||||
'(mousedown)': 'mousedown()',
|
||||
'(mouseup)': 'mouseup()',
|
||||
}
|
||||
})
|
||||
export class MyDirective {
|
||||
mousedown() {}
|
||||
mouseup() {}
|
||||
|
||||
@HostListener('click')
|
||||
click() {}
|
||||
}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const template = `
|
||||
…
|
||||
hostBindings: function MyDirective_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵlistener("mousedown", function MyDirective_mousedown_HostBindingHandler($event) { return ctx.mousedown(); })("mouseup", function MyDirective_mouseup_HostBindingHandler($event) { return ctx.mouseup(); })("click", function MyDirective_click_HostBindingHandler($event) { return ctx.click(); });
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain multiple synthetic host listeners into a single instruction', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'example.ts': `
|
||||
import {Component, HostListener} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-comp',
|
||||
template: '',
|
||||
host: {
|
||||
'(@animation.done)': 'done()',
|
||||
}
|
||||
})
|
||||
export class MyComponent {
|
||||
@HostListener('@animation.start')
|
||||
start() {}
|
||||
}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const template = `
|
||||
…
|
||||
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcomponentHostSyntheticListener("@animation.done", function MyComponent_animation_animation_done_HostBindingHandler($event) { return ctx.done(); })("@animation.start", function MyComponent_animation_animation_start_HostBindingHandler($event) { return ctx.start(); });
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain multiple regular and synthetic host listeners into two instructions', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'example.ts': `
|
||||
import {Component, HostListener} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-comp',
|
||||
template: '',
|
||||
host: {
|
||||
'(mousedown)': 'mousedown()',
|
||||
'(@animation.done)': 'done()',
|
||||
'(mouseup)': 'mouseup()',
|
||||
}
|
||||
})
|
||||
export class MyComponent {
|
||||
@HostListener('@animation.start')
|
||||
start() {}
|
||||
|
||||
@HostListener('click')
|
||||
click() {}
|
||||
}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const template = `
|
||||
…
|
||||
hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcomponentHostSyntheticListener("@animation.done", function MyComponent_animation_animation_done_HostBindingHandler($event) { return ctx.done(); })("@animation.start", function MyComponent_animation_animation_start_HostBindingHandler($event) { return ctx.start(); });
|
||||
$r3$.ɵɵlistener("mousedown", function MyComponent_mousedown_HostBindingHandler($event) { return ctx.mousedown(); })("mouseup", function MyComponent_mouseup_HostBindingHandler($event) { return ctx.mouseup(); })("click", function MyComponent_click_HostBindingHandler($event) { return ctx.click(); });
|
||||
}
|
||||
}
|
||||
`;
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('non bindable behavior', () => {
|
||||
|
@ -229,4 +229,119 @@ describe('compiler compliance: listen()', () => {
|
||||
expectEmit(source, MyComponentFactory, 'Incorrect MyComponent.ɵfac');
|
||||
});
|
||||
|
||||
it('should chain multiple listeners on the same element', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<div (click)="click()" (change)="change()"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
consts: [[${AttributeMarker.Bindings}, "click", "change"]],
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div", 0);
|
||||
$r3$.ɵɵlistener("click", function MyComponent_Template_div_click_0_listener($event) {
|
||||
return ctx.click();
|
||||
})("change", function MyComponent_Template_div_change_0_listener($event) {
|
||||
return ctx.change();
|
||||
});
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain multiple listeners across elements', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<div (click)="click()" (change)="change()"></div>
|
||||
<some-comp (update)="update()" (delete)="delete()"></some-comp>
|
||||
\`
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
consts: [[${AttributeMarker.Bindings}, "click", "change"], [${AttributeMarker.Bindings}, "update", "delete"]],
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div", 0);
|
||||
$r3$.ɵɵlistener("click", function MyComponent_Template_div_click_0_listener($event) { return ctx.click(); })("change", function MyComponent_Template_div_change_0_listener($event) { return ctx.change(); });
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelementStart(1, "some-comp", 1);
|
||||
$r3$.ɵɵlistener("update", function MyComponent_Template_some_comp_update_1_listener($event) { return ctx.update(); })("delete", function MyComponent_Template_some_comp_delete_1_listener($event) { return ctx.delete(); });
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain multiple listeners on the same template', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`<ng-template (click)="click()" (change)="change()"></ng-template>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
consts: [[${AttributeMarker.Bindings}, "click", "change"]],
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 0, 0, "ng-template", 0);
|
||||
$r3$.ɵɵlistener("click", function MyComponent_Template_ng_template_click_0_listener($event) { return ctx.click(); })("change", function MyComponent_Template_ng_template_change_0_listener($event) { return ctx.change(); });
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
@ -277,8 +277,7 @@ describe('compiler compliance: styling', () => {
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵlistener("@myAnimation.start", function MyComponent_Template_div_animation_myAnimation_start_0_listener($event) { return ctx.onStart($event); });
|
||||
$r3$.ɵɵlistener("@myAnimation.done", function MyComponent_Template_div_animation_myAnimation_done_0_listener($event) { return ctx.onDone($event); });
|
||||
$r3$.ɵɵlistener("@myAnimation.start", function MyComponent_Template_div_animation_myAnimation_start_0_listener($event) { return ctx.onStart($event); })("@myAnimation.done", function MyComponent_Template_div_animation_myAnimation_done_0_listener($event) { return ctx.onDone($event); });
|
||||
$r3$.ɵɵelementEnd();
|
||||
} if (rf & 2) {
|
||||
$r3$.ɵɵproperty("@myAnimation", ctx.exp);
|
||||
@ -337,8 +336,7 @@ describe('compiler compliance: styling', () => {
|
||||
hostBindings: function MyAnimDir_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵallocHostVars(1);
|
||||
$r3$.ɵɵcomponentHostSyntheticListener("@myAnim.start", function MyAnimDir_animation_myAnim_start_HostBindingHandler($event) { return ctx.onStart(); });
|
||||
$r3$.ɵɵcomponentHostSyntheticListener("@myAnim.done", function MyAnimDir_animation_myAnim_done_HostBindingHandler($event) { return ctx.onDone(); });
|
||||
$r3$.ɵɵcomponentHostSyntheticListener("@myAnim.start", function MyAnimDir_animation_myAnim_start_HostBindingHandler($event) { return ctx.onStart(); })("@myAnim.done", function MyAnimDir_animation_myAnim_done_HostBindingHandler($event) { return ctx.onDone(); });
|
||||
} if (rf & 2) {
|
||||
$r3$.ɵɵupdateSyntheticHostBinding("@myAnim", ctx.myAnimState);
|
||||
}
|
||||
|
@ -2041,9 +2041,7 @@ runInEachFileSystem(os => {
|
||||
const hostBindingsFn = `
|
||||
hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick(); });
|
||||
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onDocumentClick($event.target); }, false, i0.ɵɵresolveDocument);
|
||||
i0.ɵɵlistener("scroll", function FooCmp_scroll_HostBindingHandler($event) { return ctx.onWindowScroll(); }, false, i0.ɵɵresolveWindow);
|
||||
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick(); })("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onDocumentClick($event.target); }, false, i0.ɵɵresolveDocument)("scroll", function FooCmp_scroll_HostBindingHandler($event) { return ctx.onWindowScroll(); }, false, i0.ɵɵresolveWindow);
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -2136,9 +2134,7 @@ runInEachFileSystem(os => {
|
||||
hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵallocHostVars(3);
|
||||
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); });
|
||||
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onBodyClick($event); }, false, i0.ɵɵresolveBody);
|
||||
i0.ɵɵlistener("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); });
|
||||
i0.ɵɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); })("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onBodyClick($event); }, false, i0.ɵɵresolveBody)("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); });
|
||||
}
|
||||
if (rf & 2) {
|
||||
i0.ɵɵhostProperty("prop", ctx.bar);
|
||||
@ -3144,7 +3140,7 @@ runInEachFileSystem(os => {
|
||||
|
||||
env.write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
template: '<div #ref="unknownTarget"></div>',
|
||||
})
|
||||
|
Reference in New Issue
Block a user