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:
parent
6472661ae8
commit
0ad02de47e
|
@ -18,7 +18,7 @@
|
|||
"hello_world__render3__closure": {
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"bundle": 6671
|
||||
"bundle": 7065
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -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(); }
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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>; }
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
});
|
||||
});
|
|
@ -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',
|
||||
}
|
||||
});
|
|
@ -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 =
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue