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

@ -18,7 +18,7 @@
"hello_world__render3__closure": { "hello_world__render3__closure": {
"master": { "master": {
"uncompressed": { "uncompressed": {
"bundle": 6671 "bundle": 7065
} }
} }
}, },

View File

@ -6,13 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {SimpleChange} from '../change_detection/change_detection_util';
import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks';
import {RendererType2} from '../render/api'; import {RendererType2} from '../render/api';
import {Type} from '../type'; import {Type} from '../type';
import {resolveRendererType2} from '../view/util'; import {resolveRendererType2} from '../view/util';
import {diPublic} from './di'; import {diPublic} from './di';
import {componentRefresh} from './instructions'; import {componentRefresh} from './instructions';
import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs} from './interfaces/definition'; import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs, TypedDirectiveDef} from './interfaces/definition';
@ -54,8 +56,57 @@ export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): Co
return def; return def;
} }
export function NgOnChangesFeature<T>(definition: DirectiveDef<T>) {
// TODO: implement. See: https://app.asana.com/0/443577627818617/465170715764659 const PRIVATE_PREFIX = '__ngOnChanges_';
type OnChangesExpando = OnChanges & {
__ngOnChanges_: SimpleChanges|null|undefined;
[key: string]: any;
};
export function NgOnChangesFeature<T>(type: Type<T>): (definition: DirectiveDef<any>) => void {
return function(definition: DirectiveDef<any>): void {
const inputs = definition.inputs;
const proto = type.prototype;
// Place where we will store SimpleChanges if there is a change
Object.defineProperty(proto, PRIVATE_PREFIX, {value: undefined, writable: true});
for (let pubKey in inputs) {
const minKey = inputs[pubKey];
const privateMinKey = PRIVATE_PREFIX + minKey;
// Create a place where the actual value will be stored and make it non-enumerable
Object.defineProperty(proto, privateMinKey, {value: undefined, writable: true});
const existingDesc = Object.getOwnPropertyDescriptor(proto, minKey);
// create a getter and setter for property
Object.defineProperty(proto, minKey, {
get: function(this: OnChangesExpando) {
return (existingDesc && existingDesc.get) ? existingDesc.get.call(this) :
this[privateMinKey];
},
set: function(this: OnChangesExpando, value: any) {
let simpleChanges = this[PRIVATE_PREFIX];
let isFirstChange = simpleChanges === undefined;
if (simpleChanges == null) {
simpleChanges = this[PRIVATE_PREFIX] = {};
}
simpleChanges[pubKey] = new SimpleChange(this[privateMinKey], value, isFirstChange);
(existingDesc && existingDesc.set) ? existingDesc.set.call(this, value) :
this[privateMinKey] = value;
}
});
}
proto.ngDoCheck = (function(delegateDoCheck) {
return function(this: OnChangesExpando) {
let simpleChanges = this[PRIVATE_PREFIX];
if (simpleChanges != null) {
this.ngOnChanges(simpleChanges);
this[PRIVATE_PREFIX] = null;
}
delegateDoCheck && delegateDoCheck.apply(this);
};
})(proto.ngDoCheck);
};
} }
export function PublicFeature<T>(definition: DirectiveDef<T>) { export function PublicFeature<T>(definition: DirectiveDef<T>) {

View File

@ -17,12 +17,16 @@ import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_co
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref'; import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref';
import {Type} from '../type'; import {Type} from '../type';
import {assertPreviousIsParent, getPreviousOrParentNode} from './instructions'; import {assertLessThan} from './assert';
import {assertPreviousIsParent, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions';
import {ComponentTemplate, DirectiveDef, TypedDirectiveDef} from './interfaces/definition'; import {ComponentTemplate, DirectiveDef, TypedDirectiveDef} from './interfaces/definition';
import {LInjector} from './interfaces/injector'; import {LInjector} from './interfaces/injector';
import {LContainerNode, LElementNode, LNode, LNodeFlags} from './interfaces/node'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node';
import {QueryReadType} from './interfaces/query'; import {QueryReadType} from './interfaces/query';
import {Renderer3} from './interfaces/renderer';
import {LView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {insertView} from './node_manipulation';
import {notImplemented, stringify} from './util'; import {notImplemented, stringify} from './util';
@ -189,8 +193,8 @@ export function diPublic(def: TypedDirectiveDef<any>): void {
* @param flags Injection flags (e.g. CheckParent) * @param flags Injection flags (e.g. CheckParent)
* @returns The instance found * @returns The instance found
*/ */
export function inject<T>(token: Type<T>, flags?: InjectFlags): T { export function inject<T>(token: Type<T>, flags?: InjectFlags, defaultValue?: T): T {
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags); return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags, defaultValue);
} }
/** /**
@ -240,7 +244,8 @@ export function injectViewContainerRef(): viewEngine_ViewContainerRef {
* @param flags Injection flags (e.g. CheckParent) * @param flags Injection flags (e.g. CheckParent)
* @returns The instance found * @returns The instance found
*/ */
export function getOrCreateInjectable<T>(di: LInjector, token: Type<T>, flags?: InjectFlags): T { export function getOrCreateInjectable<T>(
di: LInjector, token: Type<T>, flags?: InjectFlags, defaultValue?: T): T {
const bloomHash = bloomHashBit(token); const bloomHash = bloomHashBit(token);
// If the token has a bloom hash, then it is a directive that is public to the injection system // If the token has a bloom hash, then it is a directive that is public to the injection system
@ -248,6 +253,9 @@ export function getOrCreateInjectable<T>(di: LInjector, token: Type<T>, flags?:
if (bloomHash === null) { if (bloomHash === null) {
const moduleInjector = di.injector; const moduleInjector = di.injector;
if (!moduleInjector) { if (!moduleInjector) {
if (defaultValue != null) {
return defaultValue;
}
throw createInjectionError('NotFound', token); throw createInjectionError('NotFound', token);
} }
moduleInjector.get(token); moduleInjector.get(token);
@ -418,31 +426,6 @@ class ElementRef implements viewEngine_ElementRef {
constructor(nativeElement: any) { this.nativeElement = nativeElement; } constructor(nativeElement: any) { this.nativeElement = nativeElement; }
} }
/**
* Creates a TemplateRef and stores it on the injector. Or, if the TemplateRef already
* exists, retrieves the existing TemplateRef.
*
* @param di The node injector where we should store a created TemplateRef
* @returns The TemplateRef instance to use
*/
export function getOrCreateTemplateRef<T>(di: LInjector): viewEngine_TemplateRef<T> {
ngDevMode && assertNodeType(di.node, LNodeFlags.Container);
const data = (di.node as LContainerNode).data;
return di.templateRef ||
(di.templateRef = new TemplateRef<any>(getOrCreateElementRef(di), data.template));
}
/** A ref to a particular template. */
class TemplateRef<T> implements viewEngine_TemplateRef<T> {
readonly elementRef: viewEngine_ElementRef;
constructor(elementRef: viewEngine_ElementRef, template: ComponentTemplate<T>|null) {
this.elementRef = elementRef;
}
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> { throw notImplemented(); }
}
/** /**
* Creates a ViewContainerRef and stores it on the injector. Or, if the ViewContainerRef * Creates a ViewContainerRef and stores it on the injector. Or, if the ViewContainerRef
* already exists, retrieves the existing ViewContainerRef. * already exists, retrieves the existing ViewContainerRef.
@ -463,7 +446,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
injector: Injector; injector: Injector;
parentInjector: Injector; parentInjector: Injector;
constructor(node: LContainerNode) {} constructor(private _node: LContainerNode) {}
clear(): void { throw notImplemented(); } clear(): void { throw notImplemented(); }
get(index: number): viewEngine_ViewRef|null { throw notImplemented(); } get(index: number): viewEngine_ViewRef|null { throw notImplemented(); }
@ -471,7 +454,9 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
createEmbeddedView<C>( createEmbeddedView<C>(
templateRef: viewEngine_TemplateRef<C>, context?: C|undefined, templateRef: viewEngine_TemplateRef<C>, context?: C|undefined,
index?: number|undefined): viewEngine_EmbeddedViewRef<C> { index?: number|undefined): viewEngine_EmbeddedViewRef<C> {
throw notImplemented(); const viewRef = templateRef.createEmbeddedView(context !);
this.insert(viewRef, index);
return viewRef;
} }
createComponent<C>( createComponent<C>(
componentFactory: viewEngine_ComponentFactory<C>, index?: number|undefined, componentFactory: viewEngine_ComponentFactory<C>, index?: number|undefined,
@ -480,7 +465,29 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
throw notImplemented(); throw notImplemented();
} }
insert(viewRef: viewEngine_ViewRef, index?: number|undefined): viewEngine_ViewRef { insert(viewRef: viewEngine_ViewRef, index?: number|undefined): viewEngine_ViewRef {
throw notImplemented(); if (index == null) {
index = this._node.data.views.length;
} else {
// +1 because it's legal to insert at the end.
ngDevMode && assertLessThan(index, this._node.data.views.length + 1, 'index');
}
const lView = (viewRef as EmbeddedViewRef<any>)._lViewNode;
insertView(this._node, lView, index);
// If the view is dynamic (has a template), it needs to be counted both at the container
// level and at the node above the container.
if (lView.data.template !== null) {
// Increment the container view count.
this._node.data.dynamicViewCount++;
// Look for the parent node and increment its dynamic view count.
if (this._node.parent !== null && this._node.parent.data !== null) {
ngDevMode &&
assertNodeOfPossibleTypes(this._node.parent, LNodeFlags.View, LNodeFlags.Element);
this._node.parent.data.dynamicViewCount++;
}
}
return viewRef;
} }
move(viewRef: viewEngine_ViewRef, currentIndex: number): viewEngine_ViewRef { move(viewRef: viewEngine_ViewRef, currentIndex: number): viewEngine_ViewRef {
throw notImplemented(); throw notImplemented();
@ -489,3 +496,57 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
remove(index?: number|undefined): void { throw notImplemented(); } remove(index?: number|undefined): void { throw notImplemented(); }
detach(index?: number|undefined): viewEngine_ViewRef|null { throw notImplemented(); } detach(index?: number|undefined): viewEngine_ViewRef|null { throw notImplemented(); }
} }
/**
* Creates a TemplateRef and stores it on the injector. Or, if the TemplateRef already
* exists, retrieves the existing TemplateRef.
*
* @param di The node injector where we should store a created TemplateRef
* @returns The TemplateRef instance to use
*/
export function getOrCreateTemplateRef<T>(di: LInjector): viewEngine_TemplateRef<T> {
ngDevMode && assertNodeType(di.node, LNodeFlags.Container);
const data = (di.node as LContainerNode).data;
return di.templateRef || (di.templateRef = new TemplateRef<any>(
getOrCreateElementRef(di), data.template !, getRenderer()));
}
class TemplateRef<T> implements viewEngine_TemplateRef<T> {
readonly elementRef: viewEngine_ElementRef;
private _template: ComponentTemplate<T>;
constructor(
elementRef: viewEngine_ElementRef, template: ComponentTemplate<T>,
private _renderer: Renderer3) {
this.elementRef = elementRef;
this._template = template;
}
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
let viewNode: LViewNode = renderEmbeddedTemplate(null, this._template, context, this._renderer);
return new EmbeddedViewRef(viewNode, this._template, context);
}
}
class EmbeddedViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
context: T;
rootNodes: any[];
/**
* @internal
*/
_lViewNode: LViewNode;
constructor(viewNode: LViewNode, template: ComponentTemplate<T>, context: T) {
this._lViewNode = viewNode;
this.context = context;
}
destroy(): void { notImplemented(); }
destroyed: boolean;
onDestroy(callback: Function) { notImplemented(); }
markForCheck(): void { notImplemented(); }
detach(): void { notImplemented(); }
detectChanges(): void { notImplemented(); }
checkNoChanges(): void { notImplemented(); }
reattach(): void { notImplemented(); }
}

View File

@ -21,7 +21,7 @@ import {LQuery, QueryReadType} from './interfaces/query';
import {LView, TData, TView} from './interfaces/view'; import {LView, TData, TView} from './interfaces/view';
import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node';
import {assertNodeType} from './node_assert'; import {assertNodeType, assertNodeOfPossibleTypes} from './node_assert';
import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation'; import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation';
import {isNodeMatchingSelector} from './node_selector_matcher'; import {isNodeMatchingSelector} from './node_selector_matcher';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, TypedDirectiveDef, TypedComponentDef} from './interfaces/definition'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, TypedDirectiveDef, TypedComponentDef} from './interfaces/definition';
@ -173,7 +173,9 @@ export function leaveView(newView: LView): void {
enterView(newView, null); enterView(newView, null);
} }
export function createLView(viewId: number, renderer: Renderer3, tView: TView): LView { export function createLView(
viewId: number, renderer: Renderer3, tView: TView,
template: ComponentTemplate<any>| null = null, context: any | null = null): LView {
const newView = { const newView = {
parent: currentView, parent: currentView,
id: viewId, // -1 for component views id: viewId, // -1 for component views
@ -187,7 +189,10 @@ export function createLView(viewId: number, renderer: Renderer3, tView: TView):
next: null, next: null,
bindingStartIndex: null, bindingStartIndex: null,
creationMode: true, creationMode: true,
viewHookStartIndex: null viewHookStartIndex: null,
template: template,
context: context,
dynamicViewCount: 0,
}; };
return newView; return newView;
@ -305,6 +310,32 @@ export function renderTemplate<T>(
return host; return host;
} }
export function renderEmbeddedTemplate<T>(
viewNode: LViewNode | null, template: ComponentTemplate<T>, context: T,
renderer: Renderer3): LViewNode {
const _isParent = isParent;
const _previousOrParentNode = previousOrParentNode;
try {
isParent = true;
previousOrParentNode = null !;
let cm: boolean = false;
if (viewNode == null) {
const view = createLView(-1, renderer, {data: []}, template, context);
viewNode = createLNode(null, LNodeFlags.View, null, view);
cm = true;
}
enterView(viewNode.data, viewNode);
template(context, cm);
} finally {
refreshDynamicChildren();
leaveView(currentView !.parent !);
isParent = _isParent;
previousOrParentNode = _previousOrParentNode;
}
return viewNode;
}
export function renderComponentOrTemplate<T>( export function renderComponentOrTemplate<T>(
node: LElementNode, hostView: LView, componentOrContext: T, template?: ComponentTemplate<T>) { node: LElementNode, hostView: LView, componentOrContext: T, template?: ComponentTemplate<T>) {
const oldView = enterView(hostView, node); const oldView = enterView(hostView, node);
@ -1045,7 +1076,8 @@ export function container(
nextIndex: 0, renderParent, nextIndex: 0, renderParent,
template: template == null ? null : template, template: template == null ? null : template,
next: null, next: null,
parent: currentView parent: currentView,
dynamicViewCount: 0,
}); });
if (node.tNode == null) { if (node.tNode == null) {
@ -1101,6 +1133,18 @@ export function containerRefreshEnd(): void {
} }
} }
function refreshDynamicChildren() {
for (let current = currentView.child; current !== null; current = current.next) {
if (current.dynamicViewCount !== 0 && (current as LContainer).views) {
const container = current as LContainer;
for (let i = 0; i < container.views.length; i++) {
const view = container.views[i];
renderEmbeddedTemplate(view, view.data.template !, view.data.context !, renderer);
}
}
}
}
/** /**
* Creates an LViewNode. * Creates an LViewNode.
* *
@ -1160,22 +1204,25 @@ export function viewEnd(): void {
isParent = false; isParent = false;
const viewNode = previousOrParentNode = currentView.node as LViewNode; const viewNode = previousOrParentNode = currentView.node as LViewNode;
const container = previousOrParentNode.parent as LContainerNode; const container = previousOrParentNode.parent as LContainerNode;
ngDevMode && assertNodeType(viewNode, LNodeFlags.View); if (container) {
ngDevMode && assertNodeType(container, LNodeFlags.Container); ngDevMode && assertNodeType(viewNode, LNodeFlags.View);
const lContainer = container.data; ngDevMode && assertNodeType(container, LNodeFlags.Container);
const previousView = lContainer.nextIndex <= lContainer.views.length ? const containerState = container.data;
lContainer.views[lContainer.nextIndex - 1] as LViewNode : const previousView = containerState.nextIndex <= containerState.views.length ?
null; containerState.views[containerState.nextIndex - 1] as LViewNode :
const viewIdChanged = previousView == null ? true : previousView.data.id !== viewNode.data.id; null;
const viewIdChanged = previousView == null ? true : previousView.data.id !== viewNode.data.id;
if (viewIdChanged) { if (viewIdChanged) {
insertView(container, viewNode, lContainer.nextIndex - 1); insertView(container, viewNode, containerState.nextIndex - 1);
currentView.creationMode = false; currentView.creationMode = false;
}
} }
leaveView(currentView !.parent !); leaveView(currentView !.parent !);
ngDevMode && assertEqual(isParent, false, 'isParent'); ngDevMode && assertEqual(isParent, false, 'isParent');
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View); ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View);
} }
///////////// /////////////
/** /**
@ -1193,7 +1240,7 @@ export const componentRefresh:
directiveIndex: number, elementIndex: number, template: ComponentTemplate<T>) { directiveIndex: number, elementIndex: number, template: ComponentTemplate<T>) {
ngDevMode && assertDataInRange(elementIndex); ngDevMode && assertDataInRange(elementIndex);
const element = data ![elementIndex] as LElementNode; const element = data ![elementIndex] as LElementNode;
ngDevMode && assertNodeType(element, LNodeFlags.Element); ngDevMode && assertNodeOfPossibleTypes(element, LNodeFlags.Element, LNodeFlags.Container);
ngDevMode && assertNotEqual(element.data, null, 'isComponent'); ngDevMode && assertNotEqual(element.data, null, 'isComponent');
ngDevMode && assertDataInRange(directiveIndex); ngDevMode && assertDataInRange(directiveIndex);
const hostView = element.data !; const hostView = element.data !;
@ -1204,6 +1251,7 @@ export const componentRefresh:
template(directive, creationMode); template(directive, creationMode);
} finally { } finally {
hostView.creationMode = false; hostView.creationMode = false;
refreshDynamicChildren();
leaveView(oldView); leaveView(oldView);
} }
}; };
@ -1827,6 +1875,10 @@ export function getPreviousOrParentNode(): LNode {
return previousOrParentNode; return previousOrParentNode;
} }
export function getRenderer(): Renderer3 {
return renderer;
}
export function assertPreviousIsParent() { export function assertPreviousIsParent() {
assertEqual(isParent, true, 'isParent'); assertEqual(isParent, true, 'isParent');
} }

View File

@ -66,6 +66,13 @@ export interface LContainer {
* The template extracted from the location of the Container. * The template extracted from the location of the Container.
*/ */
readonly template: ComponentTemplate<any>|null; readonly template: ComponentTemplate<any>|null;
/**
* A count of dynamic views rendered into this container. If this is non-zero, the `views` array
* will be traversed when refreshing dynamic views on this container.
*/
dynamicViewCount: number;
} }
/** /**

View File

@ -16,7 +16,6 @@ import {resolveRendererType2} from '../../view/util';
export type ComponentTemplate<T> = { export type ComponentTemplate<T> = {
(ctx: T, creationMode: boolean): void; ngPrivateData?: never; (ctx: T, creationMode: boolean): void; ngPrivateData?: never;
}; };
export type EmbeddedTemplate<T> = (ctx: T) => void;
export interface ComponentType<T> extends Type<T> { ngComponentDef: ComponentDef<T>; } export interface ComponentType<T> extends Type<T> { ngComponentDef: ComponentDef<T>; }

View File

@ -7,7 +7,7 @@
*/ */
import {LContainer} from './container'; import {LContainer} from './container';
import {DirectiveDef} from './definition'; import {ComponentTemplate, DirectiveDef} from './definition';
import {LElementNode, LViewNode, TNode} from './node'; import {LElementNode, LViewNode, TNode} from './node';
import {Renderer3} from './renderer'; import {Renderer3} from './renderer';
@ -138,6 +138,24 @@ export interface LView {
* directive defs are stored). * directive defs are stored).
*/ */
tView: TView; tView: TView;
/**
* For dynamically inserted views, the template function to refresh the view.
*/
template: ComponentTemplate<{}>|null;
/**
* For embedded views, the context with which to render the template.
*/
context: {}|null;
/**
* A count of dynamic views that are children of this view (indirectly via containers).
*
* This is used to decide whether to scan children of this view when refreshing dynamic views
* after refreshing the view itself.
*/
dynamicViewCount: number;
} }
/** Interface necessary to work with view tree traversal */ /** Interface necessary to work with view tree traversal */

View File

@ -22,6 +22,7 @@ ts_library(
"//packages/animations", "//packages/animations",
"//packages/animations/browser", "//packages/animations/browser",
"//packages/animations/browser/testing", "//packages/animations/browser/testing",
"//packages/common",
"//packages/core", "//packages/core",
"//packages/platform-browser", "//packages/platform-browser",
"//packages/platform-browser/animations", "//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 {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 {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 {createLNode, createLView, enterView, leaveView} from '../../src/render3/instructions';
import {LInjector} from '../../src/render3/interfaces/injector'; import {LInjector} from '../../src/render3/interfaces/injector';
import {LNodeFlags} from '../../src/render3/interfaces/node'; import {LNodeFlags} from '../../src/render3/interfaces/node';
import {renderToHtml} from './render_util'; import {renderComponent, renderToHtml} from './render_util';
describe('di', () => { describe('di', () => {
describe('no dependencies', () => { 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', () => { it('should inject from parent view', () => {
class ParentDirective { class ParentDirective {
static ngDirectiveDef = 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');
});
});