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": {
|
"hello_world__render3__closure": {
|
||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"bundle": 6671
|
"bundle": 7065
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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>) {
|
||||||
|
@ -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(); }
|
||||||
|
}
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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>; }
|
||||||
|
|
||||||
|
@ -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 */
|
||||||
|
@ -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",
|
||||||
|
56
packages/core/test/render3/common_integration_spec.ts
Normal file
56
packages/core/test/render3/common_integration_spec.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {NgForOfContext} from '@angular/common';
|
||||||
|
|
||||||
|
import {C, E, T, b, cR, cr, defineComponent, e, p, t} from '../../src/render3/index';
|
||||||
|
|
||||||
|
import {NgForOf} from './common_with_def';
|
||||||
|
import {renderComponent, toHtml} from './render_util';
|
||||||
|
|
||||||
|
describe('@angular/common integration', () => {
|
||||||
|
describe('NgForOf', () => {
|
||||||
|
it('should update a loop', () => {
|
||||||
|
class MyApp {
|
||||||
|
items: string[] = ['first', 'second'];
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
factory: () => new MyApp(),
|
||||||
|
tag: 'my-app',
|
||||||
|
// <ul>
|
||||||
|
// <li *ngFor="let item of items">{{item}}</li>
|
||||||
|
// </ul>
|
||||||
|
template: (myApp: MyApp, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
E(0, 'ul');
|
||||||
|
{ C(1, [NgForOf], liTemplate); }
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
p(1, 'ngForOf', b(myApp.items));
|
||||||
|
cR(1);
|
||||||
|
NgForOf.ngDirectiveDef.r(2, 0);
|
||||||
|
cr();
|
||||||
|
|
||||||
|
function liTemplate(row: NgForOfContext<string>, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
E(0, 'li');
|
||||||
|
{ T(1); }
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
t(1, b(row.$implicit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myApp = renderComponent(MyApp);
|
||||||
|
expect(toHtml(myApp)).toEqual('<ul><li>first</li><li>second</li></ul>');
|
||||||
|
});
|
||||||
|
// TODO: Test inheritance
|
||||||
|
});
|
||||||
|
});
|
30
packages/core/test/render3/common_with_def.ts
Normal file
30
packages/core/test/render3/common_with_def.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {NgForOf as NgForOfDef} from '@angular/common';
|
||||||
|
import {IterableDiffers} from '@angular/core';
|
||||||
|
|
||||||
|
import {defaultIterableDiffers} from '../../src/change_detection/change_detection';
|
||||||
|
import {DirectiveType, InjectFlags, NgOnChangesFeature, defineDirective, inject, injectTemplateRef, injectViewContainerRef, m} from '../../src/render3/index';
|
||||||
|
|
||||||
|
export const NgForOf: DirectiveType<NgForOfDef<any>> = NgForOfDef as any;
|
||||||
|
|
||||||
|
NgForOf.ngDirectiveDef = defineDirective({
|
||||||
|
factory: () => new NgForOfDef(
|
||||||
|
injectViewContainerRef(), injectTemplateRef(),
|
||||||
|
inject(IterableDiffers, InjectFlags.Default, defaultIterableDiffers)),
|
||||||
|
features: [NgOnChangesFeature(NgForOf)],
|
||||||
|
refresh: (directiveIndex: number, elementIndex: number) => {
|
||||||
|
m<NgForOfDef<any>>(directiveIndex).ngDoCheck();
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
ngForOf: 'ngForOf',
|
||||||
|
ngForTrackBy: 'ngForTrackBy',
|
||||||
|
ngForTemplate: 'ngForTemplate',
|
||||||
|
}
|
||||||
|
});
|
51
packages/core/test/render3/define_spec.ts
Normal file
51
packages/core/test/render3/define_spec.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {DoCheck, OnChanges, SimpleChanges} from '../../src/core';
|
||||||
|
import {NgOnChangesFeature, defineDirective} from '../../src/render3/index';
|
||||||
|
|
||||||
|
describe('define', () => {
|
||||||
|
describe('component', () => {
|
||||||
|
describe('NgOnChangesFeature', () => {
|
||||||
|
it('should patch class', () => {
|
||||||
|
class MyDirective implements OnChanges, DoCheck {
|
||||||
|
public log: string[] = [];
|
||||||
|
public valA: string = 'initValue';
|
||||||
|
public set valB(value: string) { this.log.push(value); }
|
||||||
|
|
||||||
|
public get valB() { return 'works'; }
|
||||||
|
|
||||||
|
ngDoCheck(): void { this.log.push('ngDoCheck'); }
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
this.log.push('ngOnChanges');
|
||||||
|
this.log.push('valA', changes['valA'].previousValue, changes['valA'].currentValue);
|
||||||
|
this.log.push('valB', changes['valB'].previousValue, changes['valB'].currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
factory: () => new MyDirective(),
|
||||||
|
features: [NgOnChangesFeature(MyDirective)],
|
||||||
|
inputs: {valA: 'valA', valB: 'valB'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir = MyDirective.ngDirectiveDef.n();
|
||||||
|
myDir.valA = 'first';
|
||||||
|
expect(myDir.valA).toEqual('first');
|
||||||
|
myDir.valB = 'second';
|
||||||
|
expect(myDir.log).toEqual(['second']);
|
||||||
|
expect(myDir.valB).toEqual('works');
|
||||||
|
myDir.log.length = 0;
|
||||||
|
myDir.ngDoCheck();
|
||||||
|
expect(myDir.log).toEqual([
|
||||||
|
'ngOnChanges', 'valA', 'initValue', 'first', 'valB', undefined, 'second', 'ngDoCheck'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -8,13 +8,14 @@
|
|||||||
|
|
||||||
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
|
import {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 =
|
||||||
|
56
packages/core/test/render3/view_container_ref_spec.ts
Normal file
56
packages/core/test/render3/view_container_ref_spec.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {TemplateRef, ViewContainerRef} from '../../src/core';
|
||||||
|
import {C, T, b, cR, cr, defineComponent, defineDirective, injectTemplateRef, injectViewContainerRef, m, t} from '../../src/render3/index';
|
||||||
|
|
||||||
|
import {renderComponent, toHtml} from './render_util';
|
||||||
|
|
||||||
|
describe('ViewContainerRef', () => {
|
||||||
|
class TestDirective {
|
||||||
|
constructor(public viewContainer: ViewContainerRef, public template: TemplateRef<any>, ) {}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
factory: () => new TestDirective(injectViewContainerRef(), injectTemplateRef(), ),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestComponent {
|
||||||
|
testDir: TestDirective;
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
tag: 'test-cmp',
|
||||||
|
factory: () => new TestComponent(),
|
||||||
|
template: (cmp: TestComponent, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
const subTemplate = (ctx: any, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
T(0);
|
||||||
|
}
|
||||||
|
t(0, b(ctx.$implicit));
|
||||||
|
};
|
||||||
|
C(0, [TestDirective], subTemplate);
|
||||||
|
}
|
||||||
|
cR(0);
|
||||||
|
cmp.testDir = m(1) as TestDirective;
|
||||||
|
TestDirective.ngDirectiveDef.r(1, 0);
|
||||||
|
cr();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
it('should add embedded view into container', () => {
|
||||||
|
const testCmp = renderComponent(TestComponent);
|
||||||
|
expect(toHtml(testCmp)).toEqual('');
|
||||||
|
const dir = testCmp.testDir;
|
||||||
|
const childCtx = {$implicit: 'works'};
|
||||||
|
const viewRef = dir.viewContainer.createEmbeddedView(dir.template, childCtx);
|
||||||
|
expect(toHtml(testCmp)).toEqual('works');
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user