fix(ivy): unable to bind to implicit receiver in embedded views (#30897)
To provide some context: The implicit receiver is part of the
parsed Angular template AST. Any property reads in bindings,
interpolations etc. read from a given object (usually the component
instance). In that case there is an _implicit_ receiver which can also
be specified explicitly by just using `this`.
e.g.
```html
<ng-template>{{this.myProperty}}</ng-template>
```
This works as expected in Ivy and View Engine, but breaks in case the
implicit receiver is not used for property reads. For example:
```html
<my-dir [myFn]="greetFn.bind(this)"></my-dir>
```
In that case the `this` will not be properly translated into the generated
template function code because the Ivy compiler currently always treats
the `ctx` variable as the implicit receiver. This is **not correct** and breaks
compatibility with View Engine. Rather we need to ensure that we retrieve
the root context for the standalone implicit receiver similar to how it works
for property reads (as seen in the example above with `this.myProperty`)
Note that this requires some small changes to the `expression_converter`
because we only want to generate the `eenextContent()` instruction if the
implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and
checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication
since templates are isolated in different functions and it another pass
through the output AST for every template expression.
[ve_check]: 0d6c9d36a1/packages/compiler/src/view_compiler/view_compiler.ts (L206-L208)
Resolves FW-1366.
PR Close #30897
This commit is contained in:

committed by
Andrew Kushnir

parent
7912db3829
commit
58be2ff884
@ -178,6 +178,59 @@ describe('compiler compliance: template', () => {
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should correctly bind to implicit receiver in template', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: \`
|
||||
<div *ngIf="true" (click)="greet(this)"></div>
|
||||
<div *ngIf="true" [id]="this"></div>
|
||||
\`
|
||||
})
|
||||
export class MyComponent {
|
||||
greet(val: any) {}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
function MyComponent_div_0_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
const $_r2$ = i0.ɵɵgetCurrentView();
|
||||
$r3$.ɵɵelementStart(0, "div", $_c1$);
|
||||
$r3$.ɵɵlistener("click", function MyComponent_div_0_Template_div_click_0_listener($event) {
|
||||
i0.ɵɵrestoreView($_r2$);
|
||||
const $ctx_r1$ = i0.ɵɵnextContext();
|
||||
return $ctx_r1$.greet($ctx_r1$);
|
||||
});
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
// ...
|
||||
function MyComponent_div_1_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelement(0, "div", $_c3$);
|
||||
} if (rf & 2) {
|
||||
const $ctx_0$ = i0.ɵɵnextContext();
|
||||
$r3$.ɵɵselect(0);
|
||||
$r3$.ɵɵproperty("id", $ctx_0$);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should support ngFor context variables', () => {
|
||||
const files = {
|
||||
app: {
|
||||
|
Reference in New Issue
Block a user