parent
e454c5a98e
commit
8c358844dd
|
@ -13,11 +13,11 @@ import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_facto
|
||||||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
|
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
|
||||||
|
|
||||||
import {assertNotNull} from './assert';
|
import {assertNotNull} from './assert';
|
||||||
import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate, enterView, getDirectiveInstance, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions';
|
import {CLEAN_PROMISE, NG_HOST_SYMBOL, _getComponentHostLElementNode, createError, createLView, createTView, detectChanges, directiveCreate, enterView, getDirectiveInstance, hostElement, leaveView, locateHostElement, scheduleChangeDetection} from './instructions';
|
||||||
import {ComponentDef, ComponentType} from './interfaces/definition';
|
import {ComponentDef, ComponentType} from './interfaces/definition';
|
||||||
import {LElementNode} from './interfaces/node';
|
import {LElementNode} from './interfaces/node';
|
||||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||||
import {RootContext} from './interfaces/view';
|
import {LViewFlags, RootContext} from './interfaces/view';
|
||||||
import {notImplemented, stringify} from './util';
|
import {notImplemented, stringify} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
@ -169,12 +169,6 @@ export const NULL_INJECTOR: Injector = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* A permanent marker promise which signifies that the current CD tree is
|
|
||||||
* clean.
|
|
||||||
*/
|
|
||||||
const CLEAN_PROMISE = Promise.resolve(null);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bootstraps a Component into an existing host element and returns an instance
|
* Bootstraps a Component into an existing host element and returns an instance
|
||||||
* of the component.
|
* of the component.
|
||||||
|
@ -204,7 +198,7 @@ export function renderComponent<T>(
|
||||||
const oldView = enterView(
|
const oldView = enterView(
|
||||||
createLView(
|
createLView(
|
||||||
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(),
|
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(),
|
||||||
null, rootContext),
|
null, rootContext, componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways),
|
||||||
null !);
|
null !);
|
||||||
try {
|
try {
|
||||||
// Create element node at index 0 in data array
|
// Create element node at index 0 in data array
|
||||||
|
@ -221,51 +215,6 @@ export function renderComponent<T>(
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronously perform change detection on a component (and possibly its sub-components).
|
|
||||||
*
|
|
||||||
* This function triggers change detection in a synchronous way on a component. There should
|
|
||||||
* be very little reason to call this function directly since a preferred way to do change
|
|
||||||
* detection is to {@link markDirty} the component and wait for the scheduler to call this method
|
|
||||||
* at some future point in time. This is because a single user action often results in many
|
|
||||||
* components being invalidated and calling change detection on each component synchronously
|
|
||||||
* would be inefficient. It is better to wait until all components are marked as dirty and
|
|
||||||
* then perform single change detection across all of the components
|
|
||||||
*
|
|
||||||
* @param component The component which the change detection should be performed on.
|
|
||||||
*/
|
|
||||||
export function detectChanges<T>(component: T): void {
|
|
||||||
const hostNode = _getComponentHostLElementNode(component);
|
|
||||||
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
|
|
||||||
renderComponentOrTemplate(hostNode, hostNode.view, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark the component as dirty (needing change detection).
|
|
||||||
*
|
|
||||||
* Marking a component dirty will schedule a change detection on this
|
|
||||||
* component at some point in the future. Marking an already dirty
|
|
||||||
* component as dirty is a noop. Only one outstanding change detection
|
|
||||||
* can be scheduled per component tree. (Two components bootstrapped with
|
|
||||||
* separate `renderComponent` will have separate schedulers)
|
|
||||||
*
|
|
||||||
* When the root component is bootstrapped with `renderComponent` a scheduler
|
|
||||||
* can be provided.
|
|
||||||
*
|
|
||||||
* @param component Component to mark as dirty.
|
|
||||||
*/
|
|
||||||
export function markDirty<T>(component: T) {
|
|
||||||
const rootContext = getRootContext(component);
|
|
||||||
if (rootContext.clean == CLEAN_PROMISE) {
|
|
||||||
let res: null|((val: null) => void);
|
|
||||||
rootContext.clean = new Promise<null>((r) => res = r);
|
|
||||||
rootContext.scheduler(() => {
|
|
||||||
detectChanges(rootContext.component);
|
|
||||||
res !(null);
|
|
||||||
rootContext.clean = CLEAN_PROMISE;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the root component of any component by walking the parent `LView` until
|
* Retrieve the root component of any component by walking the parent `LView` until
|
||||||
|
@ -285,13 +234,6 @@ function getRootContext(component: any): RootContext {
|
||||||
return rootContext;
|
return rootContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getComponentHostLElementNode<T>(component: T): LElementNode {
|
|
||||||
ngDevMode && assertNotNull(component, 'expecting component got null');
|
|
||||||
const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
|
|
||||||
ngDevMode && assertNotNull(component, 'object is not a component');
|
|
||||||
return lElementNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the host element of the component.
|
* Retrieve the host element of the component.
|
||||||
*
|
*
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {SimpleChange} from '../change_detection/change_detection_util';
|
import {SimpleChange} from '../change_detection/change_detection_util';
|
||||||
|
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
||||||
import {PipeTransform} from '../change_detection/pipe_transform';
|
import {PipeTransform} from '../change_detection/pipe_transform';
|
||||||
import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks';
|
import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks';
|
||||||
import {RendererType2} from '../render/api';
|
import {RendererType2} from '../render/api';
|
||||||
|
@ -55,7 +56,9 @@ export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): Co
|
||||||
afterContentChecked: type.prototype.ngAfterContentChecked || null,
|
afterContentChecked: type.prototype.ngAfterContentChecked || null,
|
||||||
afterViewInit: type.prototype.ngAfterViewInit || null,
|
afterViewInit: type.prototype.ngAfterViewInit || null,
|
||||||
afterViewChecked: type.prototype.ngAfterViewChecked || null,
|
afterViewChecked: type.prototype.ngAfterViewChecked || null,
|
||||||
onDestroy: type.prototype.ngOnDestroy || null
|
onDestroy: type.prototype.ngOnDestroy || null,
|
||||||
|
onPush: (componentDefinition as ComponentDefArgs<T>).changeDetection ===
|
||||||
|
ChangeDetectionStrategy.OnPush
|
||||||
};
|
};
|
||||||
const feature = componentDefinition.features;
|
const feature = componentDefinition.features;
|
||||||
feature && feature.forEach((fn) => fn(def));
|
feature && feature.forEach((fn) => fn(def));
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {createComponentRef, detectChanges, getHostElement, getRenderedText, markDirty, renderComponent, whenRendered} from './component';
|
import {createComponentRef, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
|
||||||
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition';
|
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition';
|
||||||
import {InjectFlags} from './di';
|
import {InjectFlags} from './di';
|
||||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition';
|
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition';
|
||||||
|
@ -64,6 +64,8 @@ export {
|
||||||
|
|
||||||
embeddedViewStart as V,
|
embeddedViewStart as V,
|
||||||
embeddedViewEnd as v,
|
embeddedViewEnd as v,
|
||||||
|
detectChanges,
|
||||||
|
markDirty,
|
||||||
} from './instructions';
|
} from './instructions';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -109,11 +111,9 @@ export {
|
||||||
defineComponent,
|
defineComponent,
|
||||||
defineDirective,
|
defineDirective,
|
||||||
definePipe,
|
definePipe,
|
||||||
detectChanges,
|
|
||||||
createComponentRef,
|
createComponentRef,
|
||||||
getHostElement,
|
getHostElement,
|
||||||
getRenderedText,
|
getRenderedText,
|
||||||
markDirty,
|
|
||||||
renderComponent,
|
renderComponent,
|
||||||
whenRendered,
|
whenRendered,
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,14 +12,14 @@ import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull,
|
||||||
import {LContainer, TContainer} from './interfaces/container';
|
import {LContainer, TContainer} from './interfaces/container';
|
||||||
import {CssSelector, LProjection} from './interfaces/projection';
|
import {CssSelector, LProjection} from './interfaces/projection';
|
||||||
import {LQueries} from './interfaces/query';
|
import {LQueries} from './interfaces/query';
|
||||||
import {LView, LViewFlags, LifecycleStage, TData, TView} from './interfaces/view';
|
import {LView, LViewFlags, LifecycleStage, RootContext, 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} from './node_assert';
|
||||||
import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode} from './node_manipulation';
|
import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode} from './node_manipulation';
|
||||||
import {matchingSelectorIndex} from './node_selector_matcher';
|
import {matchingSelectorIndex} from './node_selector_matcher';
|
||||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType} from './interfaces/definition';
|
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType} from './interfaces/definition';
|
||||||
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||||
import {isDifferent, stringify} from './util';
|
import {isDifferent, stringify} from './util';
|
||||||
import {executeHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
import {executeHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
||||||
|
|
||||||
|
@ -30,6 +30,13 @@ import {executeHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks,
|
||||||
*/
|
*/
|
||||||
export const NG_HOST_SYMBOL = '__ngHostLNode__';
|
export const NG_HOST_SYMBOL = '__ngHostLNode__';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A permanent marker promise which signifies that the current CD tree is
|
||||||
|
* clean.
|
||||||
|
*/
|
||||||
|
const _CLEAN_PROMISE = Promise.resolve(null);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This property gets set before entering a template.
|
* This property gets set before entering a template.
|
||||||
*
|
*
|
||||||
|
@ -159,7 +166,7 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
|
||||||
data = newView && newView.data;
|
data = newView && newView.data;
|
||||||
bindingIndex = newView && newView.bindingStartIndex || 0;
|
bindingIndex = newView && newView.bindingStartIndex || 0;
|
||||||
tData = newView && newView.tView.data;
|
tData = newView && newView.tView.data;
|
||||||
creationMode = newView && (newView.flags & LViewFlags.CreationMode) === 1;
|
creationMode = newView && (newView.flags & LViewFlags.CreationMode) === LViewFlags.CreationMode;
|
||||||
|
|
||||||
cleanup = newView && newView.cleanup;
|
cleanup = newView && newView.cleanup;
|
||||||
renderer = newView && newView.renderer;
|
renderer = newView && newView.renderer;
|
||||||
|
@ -183,7 +190,8 @@ export function leaveView(newView: LView): void {
|
||||||
executeHooks(
|
executeHooks(
|
||||||
currentView.data, currentView.tView.viewHooks, currentView.tView.viewCheckHooks,
|
currentView.data, currentView.tView.viewHooks, currentView.tView.viewCheckHooks,
|
||||||
creationMode);
|
creationMode);
|
||||||
currentView.flags &= ~LViewFlags.CreationMode; // Clear creationMode bit in view flags
|
// Views should be clean and in update mode after being checked, so these bits are cleared
|
||||||
|
currentView.flags &= ~(LViewFlags.CreationMode | LViewFlags.Dirty);
|
||||||
currentView.lifecycleStage = LifecycleStage.INIT;
|
currentView.lifecycleStage = LifecycleStage.INIT;
|
||||||
currentView.tView.firstTemplatePass = false;
|
currentView.tView.firstTemplatePass = false;
|
||||||
enterView(newView, null);
|
enterView(newView, null);
|
||||||
|
@ -191,11 +199,11 @@ export function leaveView(newView: LView): void {
|
||||||
|
|
||||||
export function createLView(
|
export function createLView(
|
||||||
viewId: number, renderer: Renderer3, tView: TView, template: ComponentTemplate<any>| null,
|
viewId: number, renderer: Renderer3, tView: TView, template: ComponentTemplate<any>| null,
|
||||||
context: any | null): LView {
|
context: any | null, flags: LViewFlags): LView {
|
||||||
const newView = {
|
const newView = {
|
||||||
parent: currentView,
|
parent: currentView,
|
||||||
id: viewId, // -1 for component views
|
id: viewId, // -1 for component views
|
||||||
flags: LViewFlags.CreationMode,
|
flags: flags | LViewFlags.CreationMode,
|
||||||
node: null !, // until we initialize it in createNode.
|
node: null !, // until we initialize it in createNode.
|
||||||
data: [],
|
data: [],
|
||||||
tView: tView,
|
tView: tView,
|
||||||
|
@ -326,7 +334,7 @@ export function renderTemplate<T>(
|
||||||
null, LNodeFlags.Element, hostNode,
|
null, LNodeFlags.Element, hostNode,
|
||||||
createLView(
|
createLView(
|
||||||
-1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template),
|
-1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template),
|
||||||
null, null));
|
null, {}, LViewFlags.CheckAlways));
|
||||||
}
|
}
|
||||||
const hostView = host.data !;
|
const hostView = host.data !;
|
||||||
ngDevMode && assertNotNull(hostView, 'Host node should have an LView defined in host.data.');
|
ngDevMode && assertNotNull(hostView, 'Host node should have an LView defined in host.data.');
|
||||||
|
@ -344,7 +352,8 @@ export function renderEmbeddedTemplate<T>(
|
||||||
previousOrParentNode = null !;
|
previousOrParentNode = null !;
|
||||||
let cm: boolean = false;
|
let cm: boolean = false;
|
||||||
if (viewNode == null) {
|
if (viewNode == null) {
|
||||||
const view = createLView(-1, renderer, createTView(), template, context);
|
const view =
|
||||||
|
createLView(-1, renderer, createTView(), template, context, LViewFlags.CheckAlways);
|
||||||
viewNode = createLNode(null, LNodeFlags.View, null, view);
|
viewNode = createLNode(null, LNodeFlags.View, null, view);
|
||||||
cm = true;
|
cm = true;
|
||||||
}
|
}
|
||||||
|
@ -431,9 +440,10 @@ export function elementStart(
|
||||||
let componentView: LView|null = null;
|
let componentView: LView|null = null;
|
||||||
if (isHostElement) {
|
if (isHostElement) {
|
||||||
const tView = getOrCreateTView(hostComponentDef !.template);
|
const tView = getOrCreateTView(hostComponentDef !.template);
|
||||||
componentView = addToViewTree(createLView(
|
const hostView = createLView(
|
||||||
-1, rendererFactory.createRenderer(native, hostComponentDef !.rendererType), tView,
|
-1, rendererFactory.createRenderer(native, hostComponentDef !.rendererType), tView,
|
||||||
null, null));
|
null, null, hostComponentDef !.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways);
|
||||||
|
componentView = addToViewTree(hostView);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only component views should be added to the view tree directly. Embedded views are
|
// Only component views should be added to the view tree directly. Embedded views are
|
||||||
|
@ -583,8 +593,9 @@ export function locateHostElement(
|
||||||
export function hostElement(rNode: RElement | null, def: ComponentDef<any>) {
|
export function hostElement(rNode: RElement | null, def: ComponentDef<any>) {
|
||||||
resetApplicationState();
|
resetApplicationState();
|
||||||
createLNode(
|
createLNode(
|
||||||
0, LNodeFlags.Element, rNode,
|
0, LNodeFlags.Element, rNode, createLView(
|
||||||
createLView(-1, renderer, getOrCreateTView(def.template), null, null));
|
-1, renderer, getOrCreateTView(def.template), null, null,
|
||||||
|
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -602,15 +613,17 @@ export function listener(eventName: string, listener: EventListener, useCapture
|
||||||
ngDevMode && assertPreviousIsParent();
|
ngDevMode && assertPreviousIsParent();
|
||||||
const node = previousOrParentNode;
|
const node = previousOrParentNode;
|
||||||
const native = node.native as RElement;
|
const native = node.native as RElement;
|
||||||
|
const wrappedListener = wrapListenerWithDirtyLogic(currentView, listener);
|
||||||
|
|
||||||
// In order to match current behavior, native DOM event listeners must be added for all
|
// In order to match current behavior, native DOM event listeners must be added for all
|
||||||
// events (including outputs).
|
// events (including outputs).
|
||||||
|
const cleanupFns = cleanup || (cleanup = currentView.cleanup = []);
|
||||||
if (isProceduralRenderer(renderer)) {
|
if (isProceduralRenderer(renderer)) {
|
||||||
const cleanupFn = renderer.listen(native, eventName, listener);
|
const cleanupFn = renderer.listen(native, eventName, wrappedListener);
|
||||||
(cleanup || (cleanup = currentView.cleanup = [])).push(cleanupFn, null);
|
cleanupFns.push(cleanupFn, null);
|
||||||
} else {
|
} else {
|
||||||
native.addEventListener(eventName, listener, useCapture);
|
native.addEventListener(eventName, wrappedListener, useCapture);
|
||||||
(cleanup || (cleanup = currentView.cleanup = [])).push(eventName, native, listener, useCapture);
|
cleanupFns.push(eventName, native, wrappedListener, useCapture);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tNode: TNode|null = node.tNode !;
|
let tNode: TNode|null = node.tNode !;
|
||||||
|
@ -703,6 +716,7 @@ export function elementProperty<T>(index: number, propName: string, value: T | N
|
||||||
let dataValue: PropertyAliasValue|undefined;
|
let dataValue: PropertyAliasValue|undefined;
|
||||||
if (inputData && (dataValue = inputData[propName])) {
|
if (inputData && (dataValue = inputData[propName])) {
|
||||||
setInputsForProperty(dataValue, value);
|
setInputsForProperty(dataValue, value);
|
||||||
|
markDirtyIfOnPush(node);
|
||||||
} else {
|
} else {
|
||||||
const native = node.native;
|
const native = node.native;
|
||||||
isProceduralRenderer(renderer) ? renderer.setProperty(native, propName, value) :
|
isProceduralRenderer(renderer) ? renderer.setProperty(native, propName, value) :
|
||||||
|
@ -1149,7 +1163,8 @@ export function embeddedViewStart(viewBlockId: number): boolean {
|
||||||
} else {
|
} else {
|
||||||
// When we create a new LView, we always reset the state of the instructions.
|
// When we create a new LView, we always reset the state of the instructions.
|
||||||
const newView = createLView(
|
const newView = createLView(
|
||||||
viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null);
|
viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null,
|
||||||
|
LViewFlags.CheckAlways);
|
||||||
if (lContainer.queries) {
|
if (lContainer.queries) {
|
||||||
newView.queries = lContainer.queries.enterView(lContainer.nextIndex);
|
newView.queries = lContainer.queries.enterView(lContainer.nextIndex);
|
||||||
}
|
}
|
||||||
|
@ -1226,15 +1241,19 @@ export function directiveRefresh<T>(directiveIndex: number, elementIndex: number
|
||||||
ngDevMode && assertNodeType(element, LNodeFlags.Element);
|
ngDevMode && assertNodeType(element, LNodeFlags.Element);
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertNotNull(element.data, `Component's host node should have an LView attached.`);
|
assertNotNull(element.data, `Component's host node should have an LView attached.`);
|
||||||
ngDevMode && assertDataInRange(directiveIndex);
|
|
||||||
const directive = getDirectiveInstance<T>(data[directiveIndex]);
|
|
||||||
const hostView = element.data !;
|
const hostView = element.data !;
|
||||||
const oldView = enterView(hostView, element);
|
|
||||||
try {
|
// Only CheckAlways components or dirty OnPush components should be checked
|
||||||
template(directive, creationMode);
|
if (hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
||||||
} finally {
|
ngDevMode && assertDataInRange(directiveIndex);
|
||||||
refreshDynamicChildren();
|
const directive = getDirectiveInstance<T>(data[directiveIndex]);
|
||||||
leaveView(oldView);
|
const oldView = enterView(hostView, element);
|
||||||
|
try {
|
||||||
|
template(directive, creationMode);
|
||||||
|
} finally {
|
||||||
|
refreshDynamicChildren();
|
||||||
|
leaveView(oldView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1389,6 +1408,97 @@ export function addToViewTree<T extends LView|LContainer>(state: T): T {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
//// Change detection
|
||||||
|
///////////////////////////////
|
||||||
|
|
||||||
|
/** If node is an OnPush component, marks its LView dirty. */
|
||||||
|
export function markDirtyIfOnPush(node: LElementNode): void {
|
||||||
|
// Because data flows down the component tree, ancestors do not need to be marked dirty
|
||||||
|
if (node.data && !(node.data.flags & LViewFlags.CheckAlways)) {
|
||||||
|
node.data.flags |= LViewFlags.Dirty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an event listener so its host view and its ancestor views will be marked dirty
|
||||||
|
* whenever the event fires. Necessary to support OnPush components.
|
||||||
|
*/
|
||||||
|
export function wrapListenerWithDirtyLogic(view: LView, listener: EventListener): EventListener {
|
||||||
|
return function(e: Event) {
|
||||||
|
markViewDirty(view);
|
||||||
|
listener(e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Marks current view and all ancestors dirty */
|
||||||
|
function markViewDirty(view: LView): void {
|
||||||
|
let currentView: LView|null = view;
|
||||||
|
|
||||||
|
while (currentView.parent != null) {
|
||||||
|
currentView.flags |= LViewFlags.Dirty;
|
||||||
|
currentView = currentView.parent;
|
||||||
|
}
|
||||||
|
currentView.flags |= LViewFlags.Dirty;
|
||||||
|
|
||||||
|
ngDevMode && assertNotNull(currentView !.context, 'rootContext');
|
||||||
|
scheduleChangeDetection(currentView !.context as RootContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Given a root context, schedules change detection at that root. */
|
||||||
|
export function scheduleChangeDetection<T>(rootContext: RootContext) {
|
||||||
|
if (rootContext.clean == _CLEAN_PROMISE) {
|
||||||
|
let res: null|((val: null) => void);
|
||||||
|
rootContext.clean = new Promise<null>((r) => res = r);
|
||||||
|
rootContext.scheduler(() => {
|
||||||
|
detectChanges(rootContext.component);
|
||||||
|
res !(null);
|
||||||
|
rootContext.clean = _CLEAN_PROMISE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronously perform change detection on a component (and possibly its sub-components).
|
||||||
|
*
|
||||||
|
* This function triggers change detection in a synchronous way on a component. There should
|
||||||
|
* be very little reason to call this function directly since a preferred way to do change
|
||||||
|
* detection is to {@link markDirty} the component and wait for the scheduler to call this method
|
||||||
|
* at some future point in time. This is because a single user action often results in many
|
||||||
|
* components being invalidated and calling change detection on each component synchronously
|
||||||
|
* would be inefficient. It is better to wait until all components are marked as dirty and
|
||||||
|
* then perform single change detection across all of the components
|
||||||
|
*
|
||||||
|
* @param component The component which the change detection should be performed on.
|
||||||
|
*/
|
||||||
|
export function detectChanges<T>(component: T): void {
|
||||||
|
const hostNode = _getComponentHostLElementNode(component);
|
||||||
|
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
|
||||||
|
renderComponentOrTemplate(hostNode, hostNode.view, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the component as dirty (needing change detection).
|
||||||
|
*
|
||||||
|
* Marking a component dirty will schedule a change detection on this
|
||||||
|
* component at some point in the future. Marking an already dirty
|
||||||
|
* component as dirty is a noop. Only one outstanding change detection
|
||||||
|
* can be scheduled per component tree. (Two components bootstrapped with
|
||||||
|
* separate `renderComponent` will have separate schedulers)
|
||||||
|
*
|
||||||
|
* When the root component is bootstrapped with `renderComponent`, a scheduler
|
||||||
|
* can be provided.
|
||||||
|
*
|
||||||
|
* @param component Component to mark as dirty.
|
||||||
|
*/
|
||||||
|
export function markDirty<T>(component: T) {
|
||||||
|
ngDevMode && assertNotNull(component, 'component');
|
||||||
|
const lElementNode = _getComponentHostLElementNode(component);
|
||||||
|
markViewDirty(lElementNode.view);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
//// Bindings & interpolations
|
//// Bindings & interpolations
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
|
@ -1649,3 +1759,12 @@ function assertDataInRange(index: number, arr?: any[]) {
|
||||||
function assertDataNext(index: number) {
|
function assertDataNext(index: number) {
|
||||||
assertEqual(data.length, index, 'index expected to be at the end of data');
|
assertEqual(data.length, index, 'index expected to be at the end of data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function _getComponentHostLElementNode<T>(component: T): LElementNode {
|
||||||
|
ngDevMode && assertNotNull(component, 'expecting component got null');
|
||||||
|
const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
|
||||||
|
ngDevMode && assertNotNull(component, 'object is not a component');
|
||||||
|
return lElementNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CLEAN_PROMISE = _CLEAN_PROMISE;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {ChangeDetectionStrategy} from '../../change_detection/constants';
|
||||||
import {PipeTransform} from '../../change_detection/pipe_transform';
|
import {PipeTransform} from '../../change_detection/pipe_transform';
|
||||||
import {RendererType2} from '../../render/api';
|
import {RendererType2} from '../../render/api';
|
||||||
import {Type} from '../../type';
|
import {Type} from '../../type';
|
||||||
|
@ -124,6 +125,9 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
|
||||||
* NOTE: only used with component directives.
|
* NOTE: only used with component directives.
|
||||||
*/
|
*/
|
||||||
readonly rendererType: RendererType2|null;
|
readonly rendererType: RendererType2|null;
|
||||||
|
|
||||||
|
/** Whether or not this component's ChangeDetectionStrategy is OnPush */
|
||||||
|
readonly onPush: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,6 +173,7 @@ export interface ComponentDefArgs<T> extends DirectiveDefArgs<T> {
|
||||||
template: ComponentTemplate<T>;
|
template: ComponentTemplate<T>;
|
||||||
features?: ComponentDefFeature[];
|
features?: ComponentDefFeature[];
|
||||||
rendererType?: RendererType2;
|
rendererType?: RendererType2;
|
||||||
|
changeDetection?: ChangeDetectionStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T>) => void;
|
export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T>) => void;
|
||||||
|
|
|
@ -24,16 +24,7 @@ import {Renderer3} from './renderer';
|
||||||
* don't have to edit the data array based on which views are present.
|
* don't have to edit the data array based on which views are present.
|
||||||
*/
|
*/
|
||||||
export interface LView {
|
export interface LView {
|
||||||
/**
|
/** Flags for this view (see LViewFlags for definition of each bit). */
|
||||||
* Flags for this view.
|
|
||||||
*
|
|
||||||
* First bit: Whether or not the view is in creationMode.
|
|
||||||
*
|
|
||||||
* This must be stored in the view rather than using `data` as a marker so that
|
|
||||||
* we can properly support embedded views. Otherwise, when exiting a child view
|
|
||||||
* back into the parent view, `data` will be defined and `creationMode` will be
|
|
||||||
* improperly reported as false.
|
|
||||||
*/
|
|
||||||
flags: LViewFlags;
|
flags: LViewFlags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,9 +173,23 @@ export interface LView {
|
||||||
queries: LQueries|null;
|
queries: LQueries|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Flags associated with an LView (see LView.flags) */
|
/** Flags associated with an LView (saved in LView.flags) */
|
||||||
export enum LViewFlags {
|
export const enum LViewFlags {
|
||||||
CreationMode = 0b001
|
/**
|
||||||
|
* Whether or not the view is in creationMode.
|
||||||
|
*
|
||||||
|
* This must be stored in the view rather than using `data` as a marker so that
|
||||||
|
* we can properly support embedded views. Otherwise, when exiting a child view
|
||||||
|
* back into the parent view, `data` will be defined and `creationMode` will be
|
||||||
|
* improperly reported as false.
|
||||||
|
*/
|
||||||
|
CreationMode = 0b001,
|
||||||
|
|
||||||
|
/** Whether this view has default change detection strategy (checks always) or onPush */
|
||||||
|
CheckAlways = 0b010,
|
||||||
|
|
||||||
|
/** Whether or not this view is currently dirty (needing check) */
|
||||||
|
Dirty = 0b100
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Interface necessary to work with view tree traversal */
|
/** Interface necessary to work with view tree traversal */
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
{
|
{
|
||||||
"name": "EMPTY$1"
|
"name": "EMPTY$1"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "NG_HOST_SYMBOL"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "NO_CHANGE"
|
"name": "NO_CHANGE"
|
||||||
},
|
},
|
||||||
|
@ -38,6 +41,9 @@
|
||||||
{
|
{
|
||||||
"name": "currentView"
|
"name": "currentView"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "detectChanges"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "domRendererFactory3"
|
"name": "domRendererFactory3"
|
||||||
},
|
},
|
||||||
|
@ -77,9 +83,6 @@
|
||||||
{
|
{
|
||||||
"name": "refreshDynamicChildren"
|
"name": "refreshDynamicChildren"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "renderComponentOrTemplate"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "renderEmbeddedTemplate"
|
"name": "renderEmbeddedTemplate"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
/**
|
||||||
|
* @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 {ChangeDetectionStrategy, DoCheck} from '../../src/core';
|
||||||
|
import {getRenderedText} from '../../src/render3/component';
|
||||||
|
import {defineComponent} from '../../src/render3/index';
|
||||||
|
import {bind, detectChanges, directiveRefresh, elementEnd, elementProperty, elementStart, interpolation1, interpolation2, listener, text, textBinding} from '../../src/render3/instructions';
|
||||||
|
import {containerEl, renderComponent, requestAnimationFrame} from './render_util';
|
||||||
|
|
||||||
|
describe('OnPush change detection', () => {
|
||||||
|
let comp: MyComponent;
|
||||||
|
|
||||||
|
class MyComponent implements DoCheck {
|
||||||
|
/* @Input() */
|
||||||
|
name = 'Nancy';
|
||||||
|
doCheckCount = 0;
|
||||||
|
|
||||||
|
ngDoCheck(): void { this.doCheckCount++; }
|
||||||
|
|
||||||
|
onClick() {}
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: MyComponent,
|
||||||
|
tag: 'my-comp',
|
||||||
|
factory: () => comp = new MyComponent(),
|
||||||
|
/**
|
||||||
|
* {{ doCheckCount }} - {{ name }}
|
||||||
|
* <button (click)="onClick()"></button>
|
||||||
|
*/
|
||||||
|
template: (ctx: MyComponent, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
text(0);
|
||||||
|
elementStart(1, 'button');
|
||||||
|
{
|
||||||
|
listener('click', () => { ctx.onClick(); });
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
textBinding(0, interpolation2('', ctx.doCheckCount, ' - ', ctx.name, ''));
|
||||||
|
},
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
inputs: {name: 'name'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp {
|
||||||
|
name: string = 'Nancy';
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: MyApp,
|
||||||
|
tag: 'my-app',
|
||||||
|
factory: () => new MyApp(),
|
||||||
|
/** <my-comp [name]="name"></my-comp> */
|
||||||
|
template: (ctx: MyApp, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, MyComponent);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
elementProperty(0, 'name', bind(ctx.name));
|
||||||
|
MyComponent.ngComponentDef.h(1, 0);
|
||||||
|
directiveRefresh(1, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should check OnPush components on initialization', () => {
|
||||||
|
const myApp = renderComponent(MyApp);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('1 - Nancy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call doCheck even when OnPush components are not dirty', () => {
|
||||||
|
const myApp = renderComponent(MyApp);
|
||||||
|
|
||||||
|
detectChanges(myApp);
|
||||||
|
expect(comp.doCheckCount).toEqual(2);
|
||||||
|
|
||||||
|
detectChanges(myApp);
|
||||||
|
expect(comp.doCheckCount).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip OnPush components in update mode when they are not dirty', () => {
|
||||||
|
const myApp = renderComponent(MyApp);
|
||||||
|
|
||||||
|
detectChanges(myApp);
|
||||||
|
// doCheckCount is 2, but 1 should be rendered since it has not been marked dirty.
|
||||||
|
expect(getRenderedText(myApp)).toEqual('1 - Nancy');
|
||||||
|
|
||||||
|
detectChanges(myApp);
|
||||||
|
// doCheckCount is 3, but 1 should be rendered since it has not been marked dirty.
|
||||||
|
expect(getRenderedText(myApp)).toEqual('1 - Nancy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check OnPush components in update mode when inputs change', () => {
|
||||||
|
const myApp = renderComponent(MyApp);
|
||||||
|
|
||||||
|
myApp.name = 'Bess';
|
||||||
|
detectChanges(myApp);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('2 - Bess');
|
||||||
|
|
||||||
|
myApp.name = 'George';
|
||||||
|
detectChanges(myApp);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('3 - George');
|
||||||
|
|
||||||
|
detectChanges(myApp);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('3 - George');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check OnPush components in update mode when component events occur', () => {
|
||||||
|
const myApp = renderComponent(MyApp);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('1 - Nancy');
|
||||||
|
|
||||||
|
const button = containerEl.querySelector('button') !;
|
||||||
|
button.click();
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
expect(getRenderedText(myApp)).toEqual('2 - Nancy');
|
||||||
|
|
||||||
|
detectChanges(myApp);
|
||||||
|
expect(getRenderedText(myApp)).toEqual('2 - Nancy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not check OnPush components in update mode when parent events occur', () => {
|
||||||
|
class ButtonParent {
|
||||||
|
noop() {}
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: ButtonParent,
|
||||||
|
tag: 'button-parent',
|
||||||
|
factory: () => new ButtonParent(),
|
||||||
|
/**
|
||||||
|
* <my-comp></my-comp>
|
||||||
|
* <button id="parent" (click)="noop()"></button>
|
||||||
|
*/
|
||||||
|
template: (ctx: ButtonParent, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, MyComponent);
|
||||||
|
elementEnd();
|
||||||
|
elementStart(2, 'button', ['id', 'parent']);
|
||||||
|
{ listener('click', () => ctx.noop()); }
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
MyComponent.ngComponentDef.h(1, 0);
|
||||||
|
directiveRefresh(1, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const buttonParent = renderComponent(ButtonParent);
|
||||||
|
expect(getRenderedText(buttonParent)).toEqual('1 - Nancy');
|
||||||
|
|
||||||
|
const button = containerEl.querySelector('button#parent') !;
|
||||||
|
(button as HTMLButtonElement).click();
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
expect(getRenderedText(buttonParent)).toEqual('1 - Nancy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check parent OnPush components in update mode when child events occur', () => {
|
||||||
|
let parent: ButtonParent;
|
||||||
|
|
||||||
|
class ButtonParent implements DoCheck {
|
||||||
|
doCheckCount = 0;
|
||||||
|
ngDoCheck(): void { this.doCheckCount++; }
|
||||||
|
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: ButtonParent,
|
||||||
|
tag: 'button-parent',
|
||||||
|
factory: () => parent = new ButtonParent(),
|
||||||
|
/** {{ doCheckCount }} - <my-comp></my-comp> */
|
||||||
|
template: (ctx: ButtonParent, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
text(0);
|
||||||
|
elementStart(1, MyComponent);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
textBinding(0, interpolation1('', ctx.doCheckCount, ' - '));
|
||||||
|
MyComponent.ngComponentDef.h(2, 1);
|
||||||
|
directiveRefresh(2, 1);
|
||||||
|
},
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyButtonApp {
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: MyButtonApp,
|
||||||
|
tag: 'my-button-app',
|
||||||
|
factory: () => new MyButtonApp(),
|
||||||
|
/** <button-parent></button-parent> */
|
||||||
|
template: (ctx: MyButtonApp, cm: boolean) => {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, ButtonParent);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
ButtonParent.ngComponentDef.h(1, 0);
|
||||||
|
directiveRefresh(1, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myButtonApp = renderComponent(MyButtonApp);
|
||||||
|
expect(parent !.doCheckCount).toEqual(1);
|
||||||
|
expect(comp !.doCheckCount).toEqual(1);
|
||||||
|
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
|
||||||
|
|
||||||
|
detectChanges(myButtonApp);
|
||||||
|
expect(parent !.doCheckCount).toEqual(2);
|
||||||
|
// parent isn't checked, so child doCheck won't run
|
||||||
|
expect(comp !.doCheckCount).toEqual(1);
|
||||||
|
expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy');
|
||||||
|
|
||||||
|
const button = containerEl.querySelector('button');
|
||||||
|
button !.click();
|
||||||
|
requestAnimationFrame.flush();
|
||||||
|
expect(parent !.doCheckCount).toEqual(3);
|
||||||
|
expect(comp !.doCheckCount).toEqual(2);
|
||||||
|
expect(getRenderedText(myButtonApp)).toEqual('3 - 2 - Nancy');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
|
import {ChangeDetectionStrategy, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core';
|
||||||
import * as $r3$ from '../../../src/core_render3_private_export';
|
import * as $r3$ from '../../../src/core_render3_private_export';
|
||||||
|
|
||||||
import {renderComponent, toHtml} from '../render_util';
|
import {renderComponent, toHtml} from '../render_util';
|
||||||
|
@ -316,6 +316,65 @@ describe('compiler specification', () => {
|
||||||
expect(renderComp(MyApp)).toEqual(`<div aria-label="some label" hostbindingdir=""></div>`);
|
expect(renderComp(MyApp)).toEqual(`<div aria-label="some label" hostbindingdir=""></div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support onPush components', () => {
|
||||||
|
type $MyApp$ = MyApp;
|
||||||
|
type $MyComp$ = MyComp;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-comp',
|
||||||
|
template: `
|
||||||
|
{{ name }}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
class MyComp {
|
||||||
|
@Input() name: string;
|
||||||
|
|
||||||
|
// NORMATIVE
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyComp,
|
||||||
|
tag: 'my-comp',
|
||||||
|
factory: function MyComp_Factory() { return new MyComp(); },
|
||||||
|
template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵT(0);
|
||||||
|
}
|
||||||
|
$r3$.ɵt(0, $r3$.ɵb(ctx.name));
|
||||||
|
},
|
||||||
|
inputs: {name: 'name'},
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
});
|
||||||
|
// /NORMATIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: `
|
||||||
|
<my-comp [name]="name"></my-comp>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class MyApp {
|
||||||
|
name = 'some name';
|
||||||
|
|
||||||
|
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||||
|
type: MyApp,
|
||||||
|
tag: 'my-app',
|
||||||
|
factory: function MyApp_Factory() { return new MyApp(); },
|
||||||
|
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
|
||||||
|
if (cm) {
|
||||||
|
$r3$.ɵE(0, MyComp);
|
||||||
|
$r3$.ɵe();
|
||||||
|
}
|
||||||
|
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name));
|
||||||
|
MyComp.ngComponentDef.h(1, 0);
|
||||||
|
$r3$.ɵr(1, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(renderComp(MyApp)).toEqual(`<my-comp>some name</my-comp>`);
|
||||||
|
});
|
||||||
|
|
||||||
xit('should support structural directives', () => {
|
xit('should support structural directives', () => {
|
||||||
type $MyComponent$ = MyComponent;
|
type $MyComponent$ = MyComponent;
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
import {withBody} from '@angular/core/testing';
|
import {withBody} from '@angular/core/testing';
|
||||||
|
|
||||||
import {DoCheck, ViewEncapsulation} from '../../src/core';
|
import {DoCheck, ViewEncapsulation} from '../../src/core';
|
||||||
import {detectChanges, getRenderedText, whenRendered} from '../../src/render3/component';
|
import {getRenderedText, whenRendered} from '../../src/render3/component';
|
||||||
import {defineComponent, markDirty} from '../../src/render3/index';
|
import {defineComponent, markDirty} from '../../src/render3/index';
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, directiveRefresh, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, directiveRefresh, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding} from '../../src/render3/instructions';
|
||||||
import {createRendererType2} from '../../src/view/index';
|
import {createRendererType2} from '../../src/view/index';
|
||||||
|
|
||||||
import {getRendererFactory2} from './imported_renderer2';
|
import {getRendererFactory2} from './imported_renderer2';
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {PublicFeature, defineDirective, inject, injectElementRef, injectTemplate
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, text, textBinding} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, text, textBinding} 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 {LViewFlags} from '../../src/render3/interfaces/view';
|
||||||
|
|
||||||
import {renderComponent, renderToHtml} from './render_util';
|
import {renderComponent, renderToHtml} from './render_util';
|
||||||
|
|
||||||
|
@ -320,7 +321,8 @@ describe('di', () => {
|
||||||
|
|
||||||
describe('getOrCreateNodeInjector', () => {
|
describe('getOrCreateNodeInjector', () => {
|
||||||
it('should handle initial undefined state', () => {
|
it('should handle initial undefined state', () => {
|
||||||
const contentView = createLView(-1, null !, createTView(), null, null);
|
const contentView =
|
||||||
|
createLView(-1, null !, createTView(), null, null, LViewFlags.CheckAlways);
|
||||||
const oldView = enterView(contentView, null !);
|
const oldView = enterView(contentView, null !);
|
||||||
try {
|
try {
|
||||||
const parent = createLNode(0, LNodeFlags.Element, null, null);
|
const parent = createLNode(0, LNodeFlags.Element, null, null);
|
||||||
|
|
Loading…
Reference in New Issue