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": {
"master": {
"uncompressed": {
"bundle": 6671
"bundle": 7065
}
}
},

View File

@ -6,13 +6,15 @@
* 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 {Type} from '../type';
import {resolveRendererType2} from '../view/util';
import {diPublic} from './di';
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;
}
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>) {

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 {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 {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 {Renderer3} from './interfaces/renderer';
import {LView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {insertView} from './node_manipulation';
import {notImplemented, stringify} from './util';
@ -189,8 +193,8 @@ export function diPublic(def: TypedDirectiveDef<any>): void {
* @param flags Injection flags (e.g. CheckParent)
* @returns The instance found
*/
export function inject<T>(token: Type<T>, flags?: InjectFlags): T {
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags);
export function inject<T>(token: Type<T>, flags?: InjectFlags, defaultValue?: T): T {
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags, defaultValue);
}
/**
@ -240,7 +244,8 @@ export function injectViewContainerRef(): viewEngine_ViewContainerRef {
* @param flags Injection flags (e.g. CheckParent)
* @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);
// 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) {
const moduleInjector = di.injector;
if (!moduleInjector) {
if (defaultValue != null) {
return defaultValue;
}
throw createInjectionError('NotFound', token);
}
moduleInjector.get(token);
@ -418,31 +426,6 @@ class ElementRef implements viewEngine_ElementRef {
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
* already exists, retrieves the existing ViewContainerRef.
@ -463,7 +446,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
injector: Injector;
parentInjector: Injector;
constructor(node: LContainerNode) {}
constructor(private _node: LContainerNode) {}
clear(): void { throw notImplemented(); }
get(index: number): viewEngine_ViewRef|null { throw notImplemented(); }
@ -471,7 +454,9 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
createEmbeddedView<C>(
templateRef: viewEngine_TemplateRef<C>, context?: C|undefined,
index?: number|undefined): viewEngine_EmbeddedViewRef<C> {
throw notImplemented();
const viewRef = templateRef.createEmbeddedView(context !);
this.insert(viewRef, index);
return viewRef;
}
createComponent<C>(
componentFactory: viewEngine_ComponentFactory<C>, index?: number|undefined,
@ -480,7 +465,29 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
throw notImplemented();
}
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 {
throw notImplemented();
@ -489,3 +496,57 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
remove(index?: number|undefined): void { 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 {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 {isNodeMatchingSelector} from './node_selector_matcher';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, TypedDirectiveDef, TypedComponentDef} from './interfaces/definition';
@ -173,7 +173,9 @@ export function leaveView(newView: LView): void {
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 = {
parent: currentView,
id: viewId, // -1 for component views
@ -187,7 +189,10 @@ export function createLView(viewId: number, renderer: Renderer3, tView: TView):
next: null,
bindingStartIndex: null,
creationMode: true,
viewHookStartIndex: null
viewHookStartIndex: null,
template: template,
context: context,
dynamicViewCount: 0,
};
return newView;
@ -305,6 +310,32 @@ export function renderTemplate<T>(
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>(
node: LElementNode, hostView: LView, componentOrContext: T, template?: ComponentTemplate<T>) {
const oldView = enterView(hostView, node);
@ -1045,7 +1076,8 @@ export function container(
nextIndex: 0, renderParent,
template: template == null ? null : template,
next: null,
parent: currentView
parent: currentView,
dynamicViewCount: 0,
});
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.
*
@ -1160,22 +1204,25 @@ export function viewEnd(): void {
isParent = false;
const viewNode = previousOrParentNode = currentView.node as LViewNode;
const container = previousOrParentNode.parent as LContainerNode;
ngDevMode && assertNodeType(viewNode, LNodeFlags.View);
ngDevMode && assertNodeType(container, LNodeFlags.Container);
const lContainer = container.data;
const previousView = lContainer.nextIndex <= lContainer.views.length ?
lContainer.views[lContainer.nextIndex - 1] as LViewNode :
null;
const viewIdChanged = previousView == null ? true : previousView.data.id !== viewNode.data.id;
if (container) {
ngDevMode && assertNodeType(viewNode, LNodeFlags.View);
ngDevMode && assertNodeType(container, LNodeFlags.Container);
const containerState = container.data;
const previousView = containerState.nextIndex <= containerState.views.length ?
containerState.views[containerState.nextIndex - 1] as LViewNode :
null;
const viewIdChanged = previousView == null ? true : previousView.data.id !== viewNode.data.id;
if (viewIdChanged) {
insertView(container, viewNode, lContainer.nextIndex - 1);
currentView.creationMode = false;
if (viewIdChanged) {
insertView(container, viewNode, containerState.nextIndex - 1);
currentView.creationMode = false;
}
}
leaveView(currentView !.parent !);
ngDevMode && assertEqual(isParent, false, 'isParent');
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.View);
}
/////////////
/**
@ -1193,7 +1240,7 @@ export const componentRefresh:
directiveIndex: number, elementIndex: number, template: ComponentTemplate<T>) {
ngDevMode && assertDataInRange(elementIndex);
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 && assertDataInRange(directiveIndex);
const hostView = element.data !;
@ -1204,6 +1251,7 @@ export const componentRefresh:
template(directive, creationMode);
} finally {
hostView.creationMode = false;
refreshDynamicChildren();
leaveView(oldView);
}
};
@ -1827,6 +1875,10 @@ export function getPreviousOrParentNode(): LNode {
return previousOrParentNode;
}
export function getRenderer(): Renderer3 {
return renderer;
}
export function assertPreviousIsParent() {
assertEqual(isParent, true, 'isParent');
}

View File

@ -66,6 +66,13 @@ export interface LContainer {
* The template extracted from the location of the Container.
*/
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> = {
(ctx: T, creationMode: boolean): void; ngPrivateData?: never;
};
export type EmbeddedTemplate<T> = (ctx: T) => void;
export interface ComponentType<T> extends Type<T> { ngComponentDef: ComponentDef<T>; }

View File

@ -7,7 +7,7 @@
*/
import {LContainer} from './container';
import {DirectiveDef} from './definition';
import {ComponentTemplate, DirectiveDef} from './definition';
import {LElementNode, LViewNode, TNode} from './node';
import {Renderer3} from './renderer';
@ -138,6 +138,24 @@ export interface LView {
* directive defs are stored).
*/
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 */

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