feat(ivy): support for the ngForOf directive, with tests (#21430)
Implement NgOnChangesFeature, ViewContainerRef, TemplateRef, and the renderEmbeddedTemplate instruction, and wire together the pieces required for the ngForOf directive to work. PR Close #21430
This commit is contained in:

committed by
Miško Hevery

parent
6472661ae8
commit
0ad02de47e
@ -22,6 +22,7 @@ ts_library(
|
||||
"//packages/animations",
|
||||
"//packages/animations/browser",
|
||||
"//packages/animations/browser/testing",
|
||||
"//packages/common",
|
||||
"//packages/core",
|
||||
"//packages/platform-browser",
|
||||
"//packages/platform-browser/animations",
|
||||
|
56
packages/core/test/render3/common_integration_spec.ts
Normal file
56
packages/core/test/render3/common_integration_spec.ts
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgForOfContext} from '@angular/common';
|
||||
|
||||
import {C, E, T, b, cR, cr, defineComponent, e, p, t} from '../../src/render3/index';
|
||||
|
||||
import {NgForOf} from './common_with_def';
|
||||
import {renderComponent, toHtml} from './render_util';
|
||||
|
||||
describe('@angular/common integration', () => {
|
||||
describe('NgForOf', () => {
|
||||
it('should update a loop', () => {
|
||||
class MyApp {
|
||||
items: string[] = ['first', 'second'];
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
factory: () => new MyApp(),
|
||||
tag: 'my-app',
|
||||
// <ul>
|
||||
// <li *ngFor="let item of items">{{item}}</li>
|
||||
// </ul>
|
||||
template: (myApp: MyApp, cm: boolean) => {
|
||||
if (cm) {
|
||||
E(0, 'ul');
|
||||
{ C(1, [NgForOf], liTemplate); }
|
||||
e();
|
||||
}
|
||||
p(1, 'ngForOf', b(myApp.items));
|
||||
cR(1);
|
||||
NgForOf.ngDirectiveDef.r(2, 0);
|
||||
cr();
|
||||
|
||||
function liTemplate(row: NgForOfContext<string>, cm: boolean) {
|
||||
if (cm) {
|
||||
E(0, 'li');
|
||||
{ T(1); }
|
||||
e();
|
||||
}
|
||||
t(1, b(row.$implicit));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const myApp = renderComponent(MyApp);
|
||||
expect(toHtml(myApp)).toEqual('<ul><li>first</li><li>second</li></ul>');
|
||||
});
|
||||
// TODO: Test inheritance
|
||||
});
|
||||
});
|
30
packages/core/test/render3/common_with_def.ts
Normal file
30
packages/core/test/render3/common_with_def.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgForOf as NgForOfDef} from '@angular/common';
|
||||
import {IterableDiffers} from '@angular/core';
|
||||
|
||||
import {defaultIterableDiffers} from '../../src/change_detection/change_detection';
|
||||
import {DirectiveType, InjectFlags, NgOnChangesFeature, defineDirective, inject, injectTemplateRef, injectViewContainerRef, m} from '../../src/render3/index';
|
||||
|
||||
export const NgForOf: DirectiveType<NgForOfDef<any>> = NgForOfDef as any;
|
||||
|
||||
NgForOf.ngDirectiveDef = defineDirective({
|
||||
factory: () => new NgForOfDef(
|
||||
injectViewContainerRef(), injectTemplateRef(),
|
||||
inject(IterableDiffers, InjectFlags.Default, defaultIterableDiffers)),
|
||||
features: [NgOnChangesFeature(NgForOf)],
|
||||
refresh: (directiveIndex: number, elementIndex: number) => {
|
||||
m<NgForOfDef<any>>(directiveIndex).ngDoCheck();
|
||||
},
|
||||
inputs: {
|
||||
ngForOf: 'ngForOf',
|
||||
ngForTrackBy: 'ngForTrackBy',
|
||||
ngForTemplate: 'ngForTemplate',
|
||||
}
|
||||
});
|
51
packages/core/test/render3/define_spec.ts
Normal file
51
packages/core/test/render3/define_spec.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {DoCheck, OnChanges, SimpleChanges} from '../../src/core';
|
||||
import {NgOnChangesFeature, defineDirective} from '../../src/render3/index';
|
||||
|
||||
describe('define', () => {
|
||||
describe('component', () => {
|
||||
describe('NgOnChangesFeature', () => {
|
||||
it('should patch class', () => {
|
||||
class MyDirective implements OnChanges, DoCheck {
|
||||
public log: string[] = [];
|
||||
public valA: string = 'initValue';
|
||||
public set valB(value: string) { this.log.push(value); }
|
||||
|
||||
public get valB() { return 'works'; }
|
||||
|
||||
ngDoCheck(): void { this.log.push('ngDoCheck'); }
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.log.push('ngOnChanges');
|
||||
this.log.push('valA', changes['valA'].previousValue, changes['valA'].currentValue);
|
||||
this.log.push('valB', changes['valB'].previousValue, changes['valB'].currentValue);
|
||||
}
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
factory: () => new MyDirective(),
|
||||
features: [NgOnChangesFeature(MyDirective)],
|
||||
inputs: {valA: 'valA', valB: 'valB'}
|
||||
});
|
||||
}
|
||||
|
||||
const myDir = MyDirective.ngDirectiveDef.n();
|
||||
myDir.valA = 'first';
|
||||
expect(myDir.valA).toEqual('first');
|
||||
myDir.valB = 'second';
|
||||
expect(myDir.log).toEqual(['second']);
|
||||
expect(myDir.valB).toEqual('works');
|
||||
myDir.log.length = 0;
|
||||
myDir.ngDoCheck();
|
||||
expect(myDir.log).toEqual([
|
||||
'ngOnChanges', 'valA', 'initValue', 'first', 'valB', undefined, 'second', 'ngDoCheck'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -8,13 +8,14 @@
|
||||
|
||||
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di';
|
||||
import {defineComponent} from '../../src/render3/definition';
|
||||
import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di';
|
||||
import {C, E, PublicFeature, T, V, b, b2, cR, cr, defineDirective, e, inject, injectElementRef, injectTemplateRef, injectViewContainerRef, m, t, v} from '../../src/render3/index';
|
||||
import {createLNode, createLView, enterView, leaveView} from '../../src/render3/instructions';
|
||||
import {LInjector} from '../../src/render3/interfaces/injector';
|
||||
import {LNodeFlags} from '../../src/render3/interfaces/node';
|
||||
|
||||
import {renderToHtml} from './render_util';
|
||||
import {renderComponent, renderToHtml} from './render_util';
|
||||
|
||||
describe('di', () => {
|
||||
describe('no dependencies', () => {
|
||||
@ -217,6 +218,23 @@ describe('di', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('flags', () => {
|
||||
it('should return defaultValue not found', () => {
|
||||
class MyApp {
|
||||
constructor(public value: string) {}
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
// type: MyApp,
|
||||
tag: 'my-app',
|
||||
factory: () => new MyApp(inject(String as any, InjectFlags.Default, 'DefaultValue')),
|
||||
template: () => null
|
||||
});
|
||||
}
|
||||
const myApp = renderComponent(MyApp);
|
||||
expect(myApp.value).toEqual('DefaultValue');
|
||||
});
|
||||
});
|
||||
|
||||
it('should inject from parent view', () => {
|
||||
class ParentDirective {
|
||||
static ngDirectiveDef =
|
||||
|
56
packages/core/test/render3/view_container_ref_spec.ts
Normal file
56
packages/core/test/render3/view_container_ref_spec.ts
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {TemplateRef, ViewContainerRef} from '../../src/core';
|
||||
import {C, T, b, cR, cr, defineComponent, defineDirective, injectTemplateRef, injectViewContainerRef, m, t} from '../../src/render3/index';
|
||||
|
||||
import {renderComponent, toHtml} from './render_util';
|
||||
|
||||
describe('ViewContainerRef', () => {
|
||||
class TestDirective {
|
||||
constructor(public viewContainer: ViewContainerRef, public template: TemplateRef<any>, ) {}
|
||||
|
||||
static ngDirectiveDef = defineDirective({
|
||||
factory: () => new TestDirective(injectViewContainerRef(), injectTemplateRef(), ),
|
||||
});
|
||||
}
|
||||
|
||||
class TestComponent {
|
||||
testDir: TestDirective;
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
tag: 'test-cmp',
|
||||
factory: () => new TestComponent(),
|
||||
template: (cmp: TestComponent, cm: boolean) => {
|
||||
if (cm) {
|
||||
const subTemplate = (ctx: any, cm: boolean) => {
|
||||
if (cm) {
|
||||
T(0);
|
||||
}
|
||||
t(0, b(ctx.$implicit));
|
||||
};
|
||||
C(0, [TestDirective], subTemplate);
|
||||
}
|
||||
cR(0);
|
||||
cmp.testDir = m(1) as TestDirective;
|
||||
TestDirective.ngDirectiveDef.r(1, 0);
|
||||
cr();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
it('should add embedded view into container', () => {
|
||||
const testCmp = renderComponent(TestComponent);
|
||||
expect(toHtml(testCmp)).toEqual('');
|
||||
const dir = testCmp.testDir;
|
||||
const childCtx = {$implicit: 'works'};
|
||||
const viewRef = dir.viewContainer.createEmbeddedView(dir.template, childCtx);
|
||||
expect(toHtml(testCmp)).toEqual('works');
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user