feat(ivy): support attaching and detaching views from change detection (#22670)

PR Close #22670
This commit is contained in:
Kara Erickson
2018-03-08 16:55:47 -08:00
parent b0b9ca3386
commit b26a90567c
6 changed files with 237 additions and 29 deletions

View File

@ -16,7 +16,7 @@ import {queueLifecycleHooks} from './hooks';
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';
import {LView, LViewFlags, RootContext} from './interfaces/view';
import {stringify} from './util';
import {createViewRef} from './view_ref';
@ -74,13 +74,14 @@ export interface CreateComponentOptions {
export function createComponentRef<T>(
componentType: ComponentType<T>, opts: CreateComponentOptions): viewEngine_ComponentRef<T> {
const component = renderComponent(componentType, opts);
const hostView = createViewRef(component);
const hostView = _getComponentHostLElementNode(component).data as LView;
const hostViewRef = createViewRef(hostView, component);
return {
location: {nativeElement: getHostElement(component)},
injector: opts.injector || NULL_INJECTOR,
instance: component,
hostView: hostView,
changeDetectorRef: hostView,
hostView: hostViewRef,
changeDetectorRef: hostViewRef,
componentType: componentType,
// TODO: implement destroy and onDestroy
destroy: () => {},

View File

@ -25,6 +25,7 @@ import {LInjector} from './interfaces/injector';
import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node';
import {QueryReadType} from './interfaces/query';
import {Renderer3} from './interfaces/renderer';
import {LView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {insertView} from './node_manipulation';
import {notImplemented, stringify} from './util';
@ -301,7 +302,7 @@ export function getOrCreateChangeDetectorRef(
return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view.node);
} else if ((currentNode.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) {
// if it's an element node with data, it's a component and context will be set later
return di.changeDetectorRef = createViewRef(context);
return di.changeDetectorRef = createViewRef(currentNode.data as LView, context);
}
return null !;
}
@ -313,8 +314,10 @@ function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode):
const hostInjector = hostNode.nodeInjector;
const existingRef = hostInjector && hostInjector.changeDetectorRef;
return existingRef ? existingRef :
createViewRef(hostNode.view.data[hostNode.flags >> LNodeFlags.INDX_SHIFT]);
return existingRef ?
existingRef :
createViewRef(
hostNode.data as LView, hostNode.view.data[hostNode.flags >> LNodeFlags.INDX_SHIFT]);
}
/**

View File

@ -210,7 +210,7 @@ export function createLView(
const newView = {
parent: currentView,
id: viewId, // -1 for component views
flags: flags | LViewFlags.CreationMode,
flags: flags | LViewFlags.CreationMode | LViewFlags.Attached,
node: null !, // until we initialize it in createNode.
data: [],
tView: tView,
@ -1281,14 +1281,19 @@ export function directiveRefresh<T>(directiveIndex: number, elementIndex: number
assertNotNull(element.data, `Component's host node should have an LView attached.`);
const hostView = element.data !;
// Only CheckAlways components or dirty OnPush components should be checked
if (hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
if (viewAttached(hostView) && hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
ngDevMode && assertDataInRange(directiveIndex);
detectChangesInternal(hostView, element, getDirectiveInstance<T>(data[directiveIndex]));
}
}
}
/** Returns a boolean for whether the view is attached */
function viewAttached(view: LView): boolean {
return (view.flags & LViewFlags.Attached) === LViewFlags.Attached;
}
/**
* Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.
* It takes all the selectors from the entire component's template and decides where

View File

@ -183,13 +183,16 @@ export const enum LViewFlags {
* back into the parent view, `data` will be defined and `creationMode` will be
* improperly reported as false.
*/
CreationMode = 0b001,
CreationMode = 0b0001,
/** Whether this view has default change detection strategy (checks always) or onPush */
CheckAlways = 0b010,
CheckAlways = 0b0010,
/** Whether or not this view is currently dirty (needing check) */
Dirty = 0b100
Dirty = 0b0100,
/** Whether or not this view is currently attached to change detection tree. */
Attached = 0b1000,
}
/** Interface necessary to work with view tree traversal */

View File

@ -7,16 +7,18 @@
*/
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
import {detectChanges} from './instructions';
import {ComponentTemplate} from './interfaces/definition';
import {LViewNode} from './interfaces/node';
import {LView, LViewFlags} from './interfaces/view';
import {notImplemented} from './util';
export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
context: T;
rootNodes: any[];
constructor(context: T|null) { this.context = context !; }
constructor(private _view: LView, context: T|null, ) { this.context = context !; }
/** @internal */
_setComponentContext(context: T) { this.context = context; }
@ -25,12 +27,27 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
destroyed: boolean;
onDestroy(callback: Function) { notImplemented(); }
markForCheck(): void { notImplemented(); }
detach(): void { notImplemented(); }
/**
* Detaches a view from the change detection tree.
*
* Detached views will not be checked during change detection runs, even if the view
* is dirty. This can be used in combination with detectChanges to implement local
* change detection checks.
*/
detach(): void { this._view.flags &= ~LViewFlags.Attached; }
/**
* Re-attaches a view to the change detection tree.
*
* This can be used to re-attach views that were previously detached from the tree
* using detach(). Views are attached to the tree by default.
*/
reattach(): void { this._view.flags |= LViewFlags.Attached; }
detectChanges(): void { detectChanges(this.context); }
checkNoChanges(): void { notImplemented(); }
reattach(): void { notImplemented(); }
}
@ -41,7 +58,7 @@ export class EmbeddedViewRef<T> extends ViewRef<T> {
_lViewNode: LViewNode;
constructor(viewNode: LViewNode, template: ComponentTemplate<T>, context: T) {
super(context);
super(viewNode.data, context);
this._lViewNode = viewNode;
}
}
@ -52,9 +69,9 @@ export class EmbeddedViewRef<T> extends ViewRef<T> {
* @param context The context for this view
* @returns The ViewRef
*/
export function createViewRef<T>(context: T): ViewRef<T> {
export function createViewRef<T>(view: LView, context: T): ViewRef<T> {
// TODO: add detectChanges back in when implementing ChangeDetectorRef.detectChanges
return addDestroyable(new ViewRef(context));
return addDestroyable(new ViewRef(view, context));
}
/** Interface for destroy logic. Implemented by addDestroyable. */