fix(ivy): align discovery methods for consistency (#27117)

PR Close #27117
This commit is contained in:
Misko Hevery
2018-11-15 08:43:56 -08:00
parent ca40565f9a
commit e56c8bf8d1
21 changed files with 433 additions and 217 deletions

View File

@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Optional, SkipSelf, StaticProvider} from '../../di';
import {Optional, SkipSelf, StaticProvider, defineInjectable} from '../../di';
import {DefaultKeyValueDifferFactory} from './default_keyvalue_differ';
/**
@ -116,6 +117,12 @@ export interface KeyValueDifferFactory {
* @publicApi
*/
export class KeyValueDiffers {
/** @nocollapse */
static ngInjectableDef = defineInjectable({
providedIn: 'root',
factory: () => new KeyValueDiffers([new DefaultKeyValueDifferFactory()])
});
/**
* @deprecated v4.0.0 - Should be private.
*/

View File

@ -200,6 +200,7 @@ export class R3Injector {
defOrWrappedDef: InjectorType<any>|InjectorTypeWithProviders<any>,
parents: InjectorType<any>[], dedupStack: InjectorType<any>[]) {
defOrWrappedDef = resolveForwardRef(defOrWrappedDef);
if (!defOrWrappedDef) return;
// Either the defOrWrappedDef is an InjectorType (with ngInjectorDef) or an
// InjectorDefTypeWithProviders (aka ModuleWithProviders). Detecting either is a megamorphic

View File

@ -13,9 +13,10 @@ import {Injector} from '../di/injector';
import {Sanitizer} from '../sanitization/security';
import {assertComponentType, assertDefined} from './assert';
import {getComponentViewByInstance} from './context_discovery';
import {getContext} from './context_discovery';
import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {getHostElement} from './discovery_utils';
import {publishDefaultGlobalUtils} from './global_utils';
import {queueInitHooks, queueLifecycleHooks} from './hooks';
import {CLEAN_PROMISE, createLViewData, createNodeAtIndex, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, prefillHostVars, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
@ -28,6 +29,7 @@ import {enterView, leaveView, resetComponentState} from './state';
import {defaultScheduler, getRootView, readElementValue, readPatchedLViewData, stringify} from './util';
/** Options that control how the component should be bootstrapped. */
export interface CreateComponentOptions {
/** Which renderer factory to use. */
@ -121,8 +123,8 @@ export function renderComponent<T>(
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
const rootView: LViewData = createLViewData(
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, undefined,
opts.injector || null);
null, renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags,
undefined, opts.injector || null);
const oldView = enterView(rootView, null);
let component: T;
@ -159,7 +161,7 @@ export function createRootComponentView(
resetComponentState();
const tView = rootView[TVIEW];
const componentView = createLViewData(
renderer,
rootView, renderer,
getOrCreateTView(
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery),
null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer);
@ -243,32 +245,6 @@ function getRootContext(component: any): RootContext {
return rootContext;
}
/**
* Retrieve the host element of the component.
*
* Use this function to retrieve the host element of the component. The host
* element is the element which the component is associated with.
*
* @param component Component for which the host element should be retrieved.
*/
export function getHostElement<T>(component: T): HTMLElement {
return readElementValue(getComponentViewByInstance(component)) as HTMLElement;
}
/**
* Retrieves the rendered text for a given component.
*
* This function retrieves the host element of a component and
* and then returns the `textContent` for that element. This implies
* that the text returned will include re-projected content of
* the component as well.
*
* @param component The component to return the content text for.
*/
export function getRenderedText(component: any): string {
const hostElement = getHostElement(component);
return hostElement.textContent || '';
}
/**
* Wait on component until it is rendered.

View File

@ -151,8 +151,8 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
// Create the root view. Uses empty TView and ContentTemplate.
const rootView: LViewData = createLViewData(
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, undefined,
rootViewInjector);
null, renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags,
undefined, rootViewInjector);
// rootView is the parent when bootstrapping
const oldView = enterView(rootView, null);

View File

@ -8,6 +8,7 @@
import './ng_dev_mode';
import {assertEqual} from './assert';
import {EMPTY_ARRAY} from './definition';
import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
import {TNode, TNodeFlags} from './interfaces/node';
import {RElement} from './interfaces/renderer';
@ -15,6 +16,7 @@ import {CONTEXT, HEADER_OFFSET, HOST, LViewData, TVIEW} from './interfaces/view'
import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util';
/** Returns the matching `LContext` data for a given DOM node, directive or component instance.
*
* This function will examine the provided DOM element, component, or directive instance\'s
@ -31,6 +33,8 @@ import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatched
* will be updated with a new context (which is then returned). If the monkey-patch value is not
* detected for a component/directive instance then it will throw an error (all components and
* directives should be automatically monkey-patched by ivy).
*
* @param target Component, Directive or DOM Node.
*/
export function getContext(target: any): LContext|null {
let mpValue = readPatchedData(target);
@ -54,7 +58,7 @@ export function getContext(target: any): LContext|null {
if (nodeIndex == -1) {
throw new Error('The provided directive was not found in the application');
}
directives = discoverDirectives(nodeIndex, lViewData, false);
directives = getDirectivesAtNodeIndex(nodeIndex, lViewData, false);
} else {
nodeIndex = findViaNativeElement(lViewData, target as RElement);
if (nodeIndex == -1) {
@ -132,7 +136,8 @@ export function getContext(target: any): LContext|null {
function createLContext(lViewData: LViewData, nodeIndex: number, native: RElement): LContext {
return {
lViewData,
nodeIndex: nodeIndex, native,
nodeIndex,
native,
component: undefined,
directives: undefined,
localRefs: undefined,
@ -271,15 +276,22 @@ function assertDomElement(element: any) {
* @param lViewData The target view data
* @param includeComponents Whether or not to include components in returned directives
*/
export function discoverDirectives(
export function getDirectivesAtNodeIndex(
nodeIndex: number, lViewData: LViewData, includeComponents: boolean): any[]|null {
const tNode = lViewData[TVIEW].data[nodeIndex] as TNode;
let directiveStartIndex = getDirectiveStartIndex(tNode);
if (directiveStartIndex == 0) return EMPTY_ARRAY;
const directiveEndIndex = getDirectiveEndIndex(tNode, directiveStartIndex);
if (!includeComponents && tNode.flags & TNodeFlags.isComponent) directiveStartIndex++;
return lViewData.slice(directiveStartIndex, directiveEndIndex);
}
export function getComponentAtNodeIndex(nodeIndex: number, lViewData: LViewData): {}|null {
const tNode = lViewData[TVIEW].data[nodeIndex] as TNode;
let directiveStartIndex = getDirectiveStartIndex(tNode);
return tNode.flags & TNodeFlags.isComponent ? lViewData[directiveStartIndex] : null;
}
/**
* Returns a map of local references (local reference name => element or directive instance) that
* exist on a given element.

View File

@ -11,11 +11,12 @@ import {Renderer2, RendererType2} from '../render/api';
import {DebugContext} from '../view';
import {DebugRenderer2, DebugRendererFactory2} from '../view/services';
import {getHostComponent, getInjector, getLocalRefs, loadContext} from './discovery_utils';
import {getComponent, getInjector, getLocalRefs, loadContext} from './discovery_utils';
import {DirectiveDef} from './interfaces/definition';
import {TNode, TNodeFlags} from './interfaces/node';
import {TVIEW} from './interfaces/view';
/**
* Adapts the DebugRendererFactory2 to create a DebugRenderer2 specific for IVY.
*
@ -43,7 +44,7 @@ class Render3DebugContext implements DebugContext {
get injector(): Injector { return getInjector(this._nativeNode); }
get component(): any { return getHostComponent(this._nativeNode); }
get component(): any { return getComponent(this._nativeNode); }
get providerTokens(): any[] {
const lDebugCtx = loadContext(this._nativeNode);

View File

@ -8,78 +8,83 @@
import {Injector} from '../di/injector';
import {assertDefined} from './assert';
import {discoverDirectives, discoverLocalRefs, getContext, isComponentInstance} from './context_discovery';
import {discoverLocalRefs, getComponentAtNodeIndex, getContext, getDirectivesAtNodeIndex} from './context_discovery';
import {LContext} from './interfaces/context';
import {TElementNode, TNode, TNodeFlags} from './interfaces/node';
import {CONTEXT, FLAGS, LViewData, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
import {getComponentViewByIndex, readPatchedLViewData} from './util';
import {TElementNode} from './interfaces/node';
import {CONTEXT, FLAGS, HOST, LViewData, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view';
import {readPatchedLViewData, stringify} from './util';
import {NodeInjector} from './view_engine_compatibility';
/**
* NOTE: The following functions might not be ideal for core usage in Angular...
* Returns the component instance associated with a given DOM host element.
* Elements which don't represent components return `null`.
*
* Each function below is designed
*/
/**
* Returns the component instance associated with the target.
* @param element Host DOM element from which the component should be retrieved for.
*
* If a DOM is used then it will return the component that
* owns the view where the element is situated.
* If a component instance is used then it will return the
* instance of the parent component depending on where
* the component instance is exists in a template.
* If a directive instance is used then it will return the
* component that contains that directive in it's template.
* ```
* <my-app>
* #VIEW
* <div>
* <child-comp></child-comp>
* </div>
* </mp-app>
*
* expect(getComponent(<child-comp>) instanceof ChildComponent).toBeTruthy();
* expect(getComponent(<my-app>) instanceof MyApp).toBeTruthy();
* ```
*
* @publicApi
*/
export function getComponent<T = {}>(target: {}): T|null {
const context = loadContext(target) !;
export function getComponent<T = {}>(element: Element): T|null {
if (!(element instanceof Node)) throw new Error('Expecting instance of DOM Node');
const context = loadContext(element) !;
if (context.component === undefined) {
let lViewData: LViewData|null = context.lViewData;
while (lViewData) {
const ctx = lViewData ![CONTEXT] !as{};
if (ctx && isComponentInstance(ctx)) {
context.component = ctx;
break;
}
lViewData = lViewData[FLAGS] & LViewFlags.IsRoot ? null : lViewData ![PARENT] !;
}
if (context.component === undefined) {
context.component = null;
}
context.component = getComponentAtNodeIndex(context.nodeIndex, context.lViewData);
}
return context.component as T;
}
/**
* Returns the host component instance associated with the target.
* Returns the component instance associated with view which owns the DOM element (`null`
* otherwise).
*
* This will only return a component instance of the DOM node
* contains an instance of a component on it.
* @param element DOM element which is owned by an existing component's view.
*
* ```
* <my-app>
* #VIEW
* <div>
* <child-comp></child-comp>
* </div>
* </mp-app>
*
* expect(getViewComponent(<child-comp>) instanceof MyApp).toBeTruthy();
* expect(getViewComponent(<my-app>)).toEqual(null);
* ```
*
* @publicApi
*/
export function getHostComponent<T = {}>(target: {}): T|null {
const context = loadContext(target);
const tNode = context.lViewData[TVIEW].data[context.nodeIndex] as TNode;
if (tNode.flags & TNodeFlags.isComponent) {
const componentView = getComponentViewByIndex(context.nodeIndex, context.lViewData);
return componentView[CONTEXT] as any as T;
export function getViewComponent<T = {}>(element: Element | {}): T|null {
const context = loadContext(element) !;
let lView: LViewData = context.lViewData;
while (lView[PARENT] && lView[HOST] === null) {
// As long as lView[HOST] is null we know we are part of sub-template such as `*ngIf`
lView = lView[PARENT] !;
}
return null;
return lView[FLAGS] & LViewFlags.IsRoot ? null : lView[CONTEXT] as T;
}
/**
* Returns the `RootContext` instance that is associated with
* the application where the target is situated.
*
* @publicApi
*/
export function getRootContext(target: LViewData | {}): RootContext {
const lViewData = Array.isArray(target) ? target : loadContext(target) !.lViewData;
@ -88,8 +93,11 @@ export function getRootContext(target: LViewData | {}): RootContext {
}
/**
* Returns a list of all the components in the application
* that are have been bootstrapped.
* Retrieve all root components.
*
* Root components are those which have been bootstrapped by Angular.
*
* @param target A DOM element, component or directive instance.
*
* @publicApi
*/
@ -98,8 +106,9 @@ export function getRootComponents(target: {}): any[] {
}
/**
* Returns the injector instance that is associated with
* the element, component or directive.
* Retrieves an `Injector` associated with the element, component or directive.
*
* @param target A DOM element, component or directive instance.
*
* @publicApi
*/
@ -111,8 +120,9 @@ export function getInjector(target: {}): Injector {
}
/**
* Returns a list of all the directives that are associated
* with the underlying target element.
* Retrieves directives associated with a given DOM host element.
*
* @param target A DOM element, component or directive instance.
*
* @publicApi
*/
@ -120,7 +130,7 @@ export function getDirectives(target: {}): Array<{}> {
const context = loadContext(target) !;
if (context.directives === undefined) {
context.directives = discoverDirectives(context.nodeIndex, context.lViewData, false);
context.directives = getDirectivesAtNodeIndex(context.nodeIndex, context.lViewData, false);
}
return context.directives || [];
@ -130,13 +140,12 @@ export function getDirectives(target: {}): Array<{}> {
* Returns LContext associated with a target passed as an argument.
* Throws if a given target doesn't have associated LContext.
*
* @publicApi
*/
export function loadContext(target: {}): LContext {
const context = getContext(target);
if (!context) {
throw new Error(
ngDevMode ? 'Unable to find the given context data for the given target' :
ngDevMode ? `Unable to find context associated with ${stringify(target)}` :
'Invalid ng target');
}
return context;
@ -148,7 +157,6 @@ export function loadContext(target: {}): LContext {
*
* @param componentOrView any component or view
*
* @publicApi
*/
export function getRootView(componentOrView: LViewData | {}): LViewData {
let lViewData: LViewData;
@ -166,7 +174,11 @@ export function getRootView(componentOrView: LViewData | {}): LViewData {
}
/**
* Retrieve map of local references (local reference name => element or directive instance).
* Retrieve map of local references.
*
* The references are retrieved as a map of local reference name to element or directive instance.
*
* @param target A DOM element, component or directive instance.
*
* @publicApi
*/
@ -179,3 +191,32 @@ export function getLocalRefs(target: {}): {[key: string]: any} {
return context.localRefs || {};
}
/**
* Retrieve the host element of the component.
*
* Use this function to retrieve the host element of the component. The host
* element is the element which the component is associated with.
*
* @param directive Component or Directive for which the host element should be retrieved.
*
* @publicApi
*/
export function getHostElement<T>(directive: T): Element {
return getContext(directive) !.native as never as Element;
}
/**
* Retrieves the rendered text for a given component.
*
* This function retrieves the host element of a component and
* and then returns the `textContent` for that element. This implies
* that the text returned will include re-projected content of
* the component as well.
*
* @param component The component to return the content text for.
*/
export function getRenderedText(component: any): string {
const hostElement = getHostElement(component);
return hostElement.textContent || '';
}

View File

@ -6,7 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {global} from '../util';
import {getComponent, getDirectives, getHostComponent, getInjector, getPlayers, getRootComponents} from './global_utils_api';
import {assertDefined} from './assert';
import {getComponent, getDirectives, getHostElement, getInjector, getPlayers, getRootComponents, getViewComponent, markDirty} from './global_utils_api';
/**
* This file introduces series of globally accessible debug tools
@ -37,11 +41,13 @@ export function publishDefaultGlobalUtils() {
if (!_published) {
_published = true;
publishGlobalUtil('getComponent', getComponent);
publishGlobalUtil('getHostComponent', getHostComponent);
publishGlobalUtil('getViewComponent', getViewComponent);
publishGlobalUtil('getHostElement', getHostElement);
publishGlobalUtil('getInjector', getInjector);
publishGlobalUtil('getRootComponents', getRootComponents);
publishGlobalUtil('getDirectives', getDirectives);
publishGlobalUtil('getPlayers', getPlayers);
publishGlobalUtil('markDirty', markDirty);
}
}
@ -55,6 +61,7 @@ export declare type GlobalDevModeContainer = {
*/
export function publishGlobalUtil(name: string, fn: Function): void {
const w = global as any as GlobalDevModeContainer;
ngDevMode && assertDefined(fn, 'function not defined');
if (w) {
let container = w[GLOBAL_PUBLISH_EXPANDO_KEY];
if (!container) {

View File

@ -15,5 +15,6 @@
* file in the public_api_guard test.
*/
export {getComponent, getDirectives, getHostComponent, getInjector, getRootComponents} from './discovery_utils';
export {getComponent, getDirectives, getHostElement, getInjector, getRootComponents, getViewComponent} from './discovery_utils';
export {markDirty} from './instructions';
export {getPlayers} from './players';

View File

@ -5,8 +5,9 @@
* 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 {LifecycleHooksFeature, getHostElement, getRenderedText, renderComponent, whenRendered} from './component';
import {LifecycleHooksFeature, renderComponent, whenRendered} from './component';
import {defineBase, defineComponent, defineDirective, defineNgModule, definePipe} from './definition';
import {getHostElement, getRenderedText} from './discovery_utils';
import {InheritDefinitionFeature} from './features/inherit_definition_feature';
import {NgOnChangesFeature} from './features/ng_onchanges_feature';
import {ProvidersFeature} from './features/providers_feature';
@ -17,6 +18,7 @@ export {getFactoryOf, getInheritedFactory} from './di';
export {RenderFlags} from './interfaces/definition';
export {CssSelectorList} from './interfaces/projection';
// clang-format off
export {
bind,

View File

@ -163,15 +163,14 @@ function refreshChildComponents(
}
export function createLViewData<T>(
renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags,
sanitizer?: Sanitizer | null, injector?: Injector | null): LViewData {
const viewData = getViewData();
parentViewData: LViewData | null, renderer: Renderer3, tView: TView, context: T | null,
flags: LViewFlags, sanitizer?: Sanitizer | null, injector?: Injector | null): LViewData {
const instance = tView.blueprint.slice() as LViewData;
instance[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit;
instance[PARENT] = instance[DECLARATION_VIEW] = viewData;
instance[PARENT] = instance[DECLARATION_VIEW] = parentViewData;
instance[CONTEXT] = context;
instance[INJECTOR as any] =
injector === undefined ? (viewData ? viewData[INJECTOR] : null) : injector;
injector === undefined ? (parentViewData ? parentViewData[INJECTOR] : null) : injector;
instance[RENDERER] = renderer;
instance[SANITIZER] = sanitizer || null;
return instance;
@ -299,16 +298,15 @@ export function renderTemplate<T>(
setRenderer(renderer);
// We need to create a root view so it's possible to look up the host element through its index
enterView(
createLViewData(
renderer, createTView(-1, null, 1, 0, null, null, null), {},
LViewFlags.CheckAlways | LViewFlags.IsRoot),
null);
const lView = createLViewData(
null, renderer, createTView(-1, null, 1, 0, null, null, null), {},
LViewFlags.CheckAlways | LViewFlags.IsRoot);
enterView(lView, null);
const componentTView =
getOrCreateTView(templateFn, consts, vars, directives || null, pipes || null, null);
hostView =
createLViewData(renderer, componentTView, context, LViewFlags.CheckAlways, sanitizer);
hostView = createLViewData(
lView, renderer, componentTView, context, LViewFlags.CheckAlways, sanitizer);
hostView[HOST_NODE] = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null);
}
renderComponentOrTemplate(hostView, context, null, templateFn);
@ -329,8 +327,8 @@ export function createEmbeddedViewAndNode<T>(
setIsParent(true);
setPreviousOrParentTNode(null !);
const lView =
createLViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer());
const lView = createLViewData(
declarationView, renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer());
lView[DECLARATION_VIEW] = declarationView;
if (queries) {
@ -1672,7 +1670,7 @@ function addComponentLogic<T>(
const componentView = addToViewTree(
viewData, previousOrParentTNode.index as number,
createLViewData(
getRendererFactory().createRenderer(native as RElement, def), tView, null,
getViewData(), getRendererFactory().createRenderer(native as RElement, def), tView, null,
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, getCurrentSanitizer()));
componentView[HOST_NODE] = previousOrParentTNode as TElementNode;
@ -2000,7 +1998,7 @@ export function embeddedViewStart(viewBlockId: number, consts: number, vars: num
} else {
// When we create a new LView, we always reset the state of the instructions.
viewToRender = createLViewData(
getRenderer(),
getViewData(), getRenderer(),
getOrCreateEmbeddedTView(viewBlockId, consts, vars, containerTNode as TContainerNode), null,
LViewFlags.CheckAlways, getCurrentSanitizer());
@ -2469,6 +2467,8 @@ function updateViewQuery<T>(
* can be provided.
*
* @param component Component to mark as dirty.
*
* @publicApi
*/
export function markDirty<T>(component: T) {
ngDevMode && assertDefined(component, 'component');