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:
Alex Rickabaugh
2018-01-17 10:09:05 -08:00
committed by Miško Hevery
parent 6472661ae8
commit 0ad02de47e
13 changed files with 456 additions and 56 deletions

View File

@ -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",

View 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
});
});

View 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',
}
});

View 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'
]);
});
});
});
});

View File

@ -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 =

View 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');
});
});