Skip to content

Commit

Permalink
refactor(compiler): Add failing tests about structural attr bindings
Browse files Browse the repository at this point in the history
While running a g3 presubmit, I discovered two related novel failure modes:

1. Simple case: this new test uses an `ngFor` structural directive, which binds a context variable. That variable is immediately used in an attribute binding. It looks like we generate an extra attribute instruction, which might result in an invalid property read at runtime.
2. Complex case: this is another attribute binding, this time on a structural element, inside of an `ng-template`. Not sure what's going on here.
  • Loading branch information
dylhunn committed Dec 7, 2023
1 parent d114c4a commit fbb1c34
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1112,3 +1112,89 @@ export declare class MyComponent {
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, ["*"], true, never>;
}

/****************************************************************************************************
* PARTIAL FILE: ng_for_context_in_attr_binding.js
****************************************************************************************************/
import { Component, NgModule } from '@angular/core';
import * as i0 from "@angular/core";
export class MyComponent {
}
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: `
<div *ngFor="let someElem of someField.someMethod()"
[attr.someInputAttr]="someElem.someAttr()">
</div>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
type: Component,
args: [{
selector: 'my-component',
template: `
<div *ngFor="let someElem of someField.someMethod()"
[attr.someInputAttr]="someElem.someAttr()">
</div>
`,
}]
}] });
export class MyModule {
}
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] });
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{
type: NgModule,
args: [{ declarations: [MyComponent] }]
}] });

/****************************************************************************************************
* PARTIAL FILE: ng_for_context_in_attr_binding.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyComponent {
someField: any;
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never, false, never>;
}
export declare class MyModule {
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyComponent], never, never>;
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
}

/****************************************************************************************************
* PARTIAL FILE: attr_binding_on_structural_inside_ng_template.js
****************************************************************************************************/
import { Component } from '@angular/core';
import * as i0 from "@angular/core";
export class MyComponent {
}
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "my-component", ngImport: i0, template: `
<ng-template #someLocalRef>
<span [attr.someAttr]="someField" *ngIf="someBooleanField"></span>
</ng-template>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
type: Component,
args: [{
selector: 'my-component',
standalone: true,
template: `
<ng-template #someLocalRef>
<span [attr.someAttr]="someField" *ngIf="someBooleanField"></span>
</ng-template>
`,
}]
}] });

/****************************************************************************************************
* PARTIAL FILE: attr_binding_on_structural_inside_ng_template.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class MyComponent {
someField: any;
someBooleanField: boolean;
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never, true, never>;
}

Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,30 @@
"failureMessage": "Incorrect template"
}
]
},
{
"description": "should handle ngFor context variables when used in bindings",
"inputFiles": [
"ng_for_context_in_attr_binding.ts"
],
"expectations": [
{
"failureMessage": "Incorrect template"
}
],
"skipForTemplatePipeline": true
},
{
"description": "should handle attribute bindings inside an ng-template",
"inputFiles": [
"attr_binding_on_structural_inside_ng_template.ts"
],
"expectations": [
{
"failureMessage": "Incorrect template"
}
],
"skipForTemplatePipeline": true
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
function MyComponent_ng_template_0_span_0_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵelement(0, "span");
} if (rf & 2) {
const $ctx_r2$ = i0.ɵɵnextContext(2);
i0.ɵɵattribute("someAttr", $ctx_r2$.someField);
}
}

function MyComponent_ng_template_0_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵtemplate(0, MyComponent_ng_template_0_span_0_Template, 1, 1, "span", 1);
} if (rf & 2) {
const $ctx_r0$ = i0.ɵɵnextContext();
i0.ɵɵproperty("ngIf", $ctx_r0$.someBooleanField);
}
}

consts: [["someLocalRef", ""], [4, "ngIf"]], template: function MyComponent_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 1, 1, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {Component, NgModule} from '@angular/core';

@Component({
selector: 'my-component',
template: `
<ng-template #someLocalRef>
<span [attr.someAttr]="someField" *ngIf="someBooleanField"></span>
</ng-template>
`,
})
export class MyComponent {
someField!: any;
someBooleanField!: boolean;
}

@NgModule({declarations: [MyComponent]})
export class MyModule {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
MyComponent_div_0_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵelement(0, "div");
} if (rf & 2) {
const $someElem_r1$ = ctx.$implicit;
i0.ɵɵattribute("someInputAttr", $someElem_r1$.someAttr());
}
}

consts: [[__AttributeMarker.Template__, "ngFor", "ngForOf"]],
template:function MyComponent_Template(rf, ctx){
if (rf & 1) {
i0.ɵɵtemplate(0, MyComponent_div_0_Template, 1, 1, "div", 0);
} if (rf & 2) {
i0.ɵɵproperty("ngForOf", ctx.someField.someMethod());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {Component, NgModule} from '@angular/core';

@Component({
selector: 'my-component',
template: `
<div *ngFor="let someElem of someField.someMethod()"
[attr.someInputAttr]="someElem.someAttr()">
</div>
`,
})
export class MyComponent {
someField!: any;
}

@NgModule({declarations: [MyComponent]})
export class MyModule {
}

0 comments on commit fbb1c34

Please sign in to comment.