feat(ivy): support ChangeDetectorRef.detectChanges (#22614)

PR Close #22614
This commit is contained in:
Kara Erickson
2018-03-06 11:58:08 -08:00
parent d485346d3c
commit 4c089c1d93
10 changed files with 655 additions and 294 deletions

View File

@ -13,7 +13,7 @@ import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_facto
import {assertNotNull} from './assert';
import {queueLifecycleHooks} from './hooks';
import {CLEAN_PROMISE, _getComponentHostLElementNode, createLView, createTView, detectChanges, directiveCreate, enterView, getDirectiveInstance, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, scheduleChangeDetection} from './instructions';
import {CLEAN_PROMISE, _getComponentHostLElementNode, createLView, createTView, directiveCreate, enterView, getDirectiveInstance, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, scheduleTick, tick} from './instructions';
import {ComponentDef, ComponentType} from './interfaces/definition';
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {LViewFlags, RootContext} from './interfaces/view';
@ -43,12 +43,12 @@ export interface CreateComponentOptions {
* Typically, the features in this list are features that cannot be added to the
* other features list in the component definition because they rely on other factors.
*
* Example: RootLifecycleHooks is a function that adds lifecycle hook capabilities
* Example: `RootLifecycleHooks` is a function that adds lifecycle hook capabilities
* to root components in a tree-shakable way. It cannot be added to the component
* features list because there's no way of knowing when the component will be used as
* a root component.
*/
features?: (<T>(component: T, componentDef: ComponentDef<T>) => void)[];
hostFeatures?: (<T>(component: T, componentDef: ComponentDef<T>) => void)[];
/**
* A function which is used to schedule change detection work in the future.
@ -141,12 +141,11 @@ export function renderComponent<T>(
enterView(oldView, null);
}
opts.features && opts.features.forEach((feature) => feature(component, componentDef));
detectChanges(component);
opts.hostFeatures && opts.hostFeatures.forEach((feature) => feature(component, componentDef));
tick(component);
return component;
}
/**
* Used to enable lifecycle hooks on the root component.
*
@ -156,9 +155,11 @@ export function renderComponent<T>(
*
* Example:
*
* ```
* renderComponent(AppComponent, {features: [RootLifecycleHooks]});
* ```
*/
export function RootLifecycleHooks(component: any, def: ComponentDef<any>): void {
export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): void {
const elementNode = _getComponentHostLElementNode(component);
queueLifecycleHooks(elementNode.flags, elementNode.view);
}
@ -170,13 +171,7 @@ export function RootLifecycleHooks(component: any, def: ComponentDef<any>): void
* @param component any component
*/
function getRootContext(component: any): RootContext {
ngDevMode && assertNotNull(component, 'component');
const lElementNode = _getComponentHostLElementNode(component);
let lView = lElementNode.view;
while (lView.parent) {
lView = lView.parent;
}
const rootContext = lView.context as RootContext;
const rootContext = getRootView(component).context as RootContext;
ngDevMode && assertNotNull(rootContext, 'rootContext');
return rootContext;
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {RootLifecycleHooks, createComponentRef, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
import {LifecycleHooksFeature, createComponentRef, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition';
import {InjectFlags} from './di';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition';
@ -109,7 +109,7 @@ export {
DirectiveType,
NgOnChangesFeature,
PublicFeature,
RootLifecycleHooks,
LifecycleHooksFeature,
defineComponent,
defineDirective,
definePipe,

View File

@ -1284,14 +1284,7 @@ export function directiveRefresh<T>(directiveIndex: number, elementIndex: number
// Only CheckAlways components or dirty OnPush components should be checked
if (hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
ngDevMode && assertDataInRange(directiveIndex);
const directive = getDirectiveInstance<T>(data[directiveIndex]);
const oldView = enterView(hostView, element);
try {
template(directive, creationMode);
} finally {
refreshDynamicChildren();
leaveView(oldView);
}
detectChangesInternal(hostView, element, getDirectiveInstance<T>(data[directiveIndex]));
}
}
}
@ -1511,23 +1504,71 @@ function markViewDirty(view: LView): void {
currentView.flags |= LViewFlags.Dirty;
ngDevMode && assertNotNull(currentView !.context, 'rootContext');
scheduleChangeDetection(currentView !.context as RootContext);
scheduleTick(currentView !.context as RootContext);
}
/** Given a root context, schedules change detection at that root. */
export function scheduleChangeDetection<T>(rootContext: RootContext) {
/**
* Used to schedule change detection on the whole application.
*
* Unlike `tick`, `scheduleTick` coalesces multiple calls into one change detection run.
* It is usually called indirectly by calling `markDirty` when the view needs to be
* re-rendered.
*
* Typically `scheduleTick` uses `requestAnimationFrame` to coalesce multiple
* `scheduleTick` requests. The scheduling function can be overridden in
* `renderComponent`'s `scheduler` option.
*/
export function scheduleTick<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);
tick(rootContext.component);
res !(null);
rootContext.clean = _CLEAN_PROMISE;
});
}
}
/**
* Used to perform change detection on the whole application.
*
* This is equivalent to `detectChanges`, but invoked on root component. Additionally, `tick`
* executes lifecycle hooks and conditionally checks components based on their
* `ChangeDetectionStrategy` and dirtiness.
*
* The preferred way to trigger change detection is to call `markDirty`. `markDirty` internally
* schedules `tick` using a scheduler in order to coalesce multiple `markDirty` calls into a
* single change detection run. By default, the scheduler is `requestAnimationFrame`, but can
* be changed when calling `renderComponent` and providing the `scheduler` option.
*/
export function tick<T>(component: T): void {
const rootView = getRootView(component);
const rootComponent = (rootView.context as RootContext).component;
const hostNode = _getComponentHostLElementNode(rootComponent);
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
renderComponentOrTemplate(hostNode, rootView, rootComponent);
}
/**
* Retrieve the root view from any component by walking the parent `LView` until
* reaching the root `LView`.
*
* @param component any component
*/
export function getRootView(component: any): LView {
ngDevMode && assertNotNull(component, 'component');
const lElementNode = _getComponentHostLElementNode(component);
let lView = lElementNode.view;
while (lView.parent) {
lView = lView.parent;
}
return lView;
}
/**
* Synchronously perform change detection on a component (and possibly its sub-components).
*
@ -1544,7 +1585,24 @@ export function scheduleChangeDetection<T>(rootContext: RootContext) {
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);
detectChangesInternal(hostNode.data as LView, hostNode, component);
}
/** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */
function detectChangesInternal<T>(hostView: LView, hostNode: LElementNode, component: T) {
const componentIndex = hostNode.flags >> LNodeFlags.INDX_SHIFT;
const template = (hostNode.view.tView.data[componentIndex] as ComponentDef<T>).template;
const oldView = enterView(hostView, hostNode);
if (template != null) {
try {
template(component, creationMode);
} finally {
refreshDynamicChildren();
leaveView(oldView);
}
}
}

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref';
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
import {detectChanges} from './instructions';
import {ComponentTemplate} from './interfaces/definition';
import {LViewNode} from './interfaces/node';
import {notImplemented} from './util';
@ -26,7 +26,9 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
onDestroy(callback: Function) { notImplemented(); }
markForCheck(): void { notImplemented(); }
detach(): void { notImplemented(); }
detectChanges(): void { notImplemented(); }
detectChanges(): void { detectChanges(this.context); }
checkNoChanges(): void { notImplemented(); }
reattach(): void { notImplemented(); }
}