Skip to content

Commit

Permalink
test(ivy): Add small_app spec for sprint angular#3 (angular#22018)
Browse files Browse the repository at this point in the history
PR Close angular#22018
  • Loading branch information
mhevery authored and smdunn809 committed Feb 28, 2018
1 parent 406ab1b commit 6f320e4
Show file tree
Hide file tree
Showing 21 changed files with 699 additions and 86 deletions.
162 changes: 144 additions & 18 deletions packages/core/src/render3/component.ts
Expand Up @@ -17,6 +17,7 @@ import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate,
import {ComponentDef, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {RootContext} from './interfaces/view';
import {notImplemented, stringify} from './util';


Expand All @@ -43,6 +44,19 @@ export interface CreateComponentOptions {
* Example: PublicFeature is a function that makes the component public to the DI system.
*/
features?: (<T>(component: T, componentDef: ComponentDef<T>) => void)[];

/**
* A function which is used to schedule change detection work in the future.
*
* When marking components as dirty, it is necessary to schedule the work of
* change detection in the future. This is done to coalesce multiple
* {@link markDirty} calls into a single changed detection processing.
*
* The default value of the scheduler is the `requestAnimationFrame` function.
*
* It is also useful to override this function for testing purposes.
*/
scheduler?: (work: () => void) => void;
}


Expand Down Expand Up @@ -155,11 +169,22 @@ 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
* of the component.
*
* Use this function to bootstrap a component into the DOM tree. Each invocation
* of this function will create a separate tree of components, injectors and
* change detection cycles and lifetimes. To dynamically insert a new component
* into an existing tree such that it shares the same injection, change detection
* and object lifetime, use {@link ViewContainer#createComponent}.
*
* @param componentType Component to bootstrap
* @param options Optional parameters which control bootstrapping
*/
Expand All @@ -170,15 +195,23 @@ export function renderComponent<T>(
if (componentDef.type != componentType) componentDef.type = componentType;
let component: T;
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
const rootContext: RootContext = {
// Incomplete initialization due to circular reference.
component: null !,
scheduler: opts.scheduler || requestAnimationFrame,
clean: CLEAN_PROMISE,
};
const oldView = enterView(
createLView(
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView()),
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(),
null, rootContext),
null !);
try {
// Create element node at index 0 in data array
hostElement(hostNode, componentDef);
// Create directive instance with n() and store at index 1 in data array (el is 0)
component = getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef));
component = rootContext.component =
getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef));
} finally {
leaveView(oldView);
}
Expand All @@ -188,27 +221,120 @@ export function renderComponent<T>(
return component;
}

export function detectChanges<T>(component: T) {
ngDevMode && assertNotNull(component, 'detectChanges should be called with a component');
const hostNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
if (ngDevMode && !hostNode) {
createError('Not a directive instance', 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);
isDirty = false;
}

let isDirty = false;
export function markDirty<T>(
component: T, scheduler: (fn: () => void) => void = requestAnimationFrame) {
ngDevMode && assertNotNull(component, 'markDirty should be called with a component');
if (!isDirty) {
isDirty = true;
scheduler(() => detectChanges(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;
});
}
}

export function getHostElement<T>(component: T): RElement {
return ((component as any)[NG_HOST_SYMBOL] as LElementNode).native;
/**
* Retrieve the root component of any component by walking the parent `LView` until
* reaching the root `LView`.
*
* @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;
ngDevMode && assertNotNull(rootContext, '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.
*
* 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 _getComponentHostLElementNode(component).native as any;
}

/**
* 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.
*
* This function returns a `Promise` which is resolved when the component's
* change detection is executed. This is determined by finding the scheduler
* associated with the `component`'s render tree and waiting until the scheduler
* flushes. If nothing is scheduled, the function returns a resolved promise.
*
* Example:
* ```
* await whenRendered(myComponent);
* ```
*
* @param component Component to wait upon
* @returns Promise which resolves when the component is rendered.
*/
export function whenRendered(component: any): Promise<null> {
return getRootContext(component).clean;
}
13 changes: 10 additions & 3 deletions packages/core/src/render3/index.ts
Expand Up @@ -6,12 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/

import {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent} from './component';
import {createComponentRef, detectChanges, getHostElement, getRenderedText, markDirty, 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';

export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
export {CssSelector} from './interfaces/projection';


// Naming scheme:
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
Expand Down Expand Up @@ -106,6 +108,11 @@ export {
defineComponent,
defineDirective,
definePipe,
detectChanges,
createComponentRef,
getHostElement,
getRenderedText,
markDirty,
renderComponent,
whenRendered,
};
export {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent};
export {CssSelector} from './interfaces/projection';
44 changes: 26 additions & 18 deletions packages/core/src/render3/instructions.ts
Expand Up @@ -70,10 +70,15 @@ let isParent: boolean;
*/
let tData: TData;

/** State of the current view being processed. */
let currentView: LView;
// The initialization has to be after the `let`, otherwise `createLView` can't see `let`.
currentView = createLView(null !, null !, createTView());
/**
* State of the current view being processed.
*
* NOTE: we cheat here and initialize it to `null` even thought the type does not
* contain `null`. This is because we expect this value to be not `null` as soon
* as we enter the view. Declaring the type as `null` would require us to place `!`
* in most instructions since they all assume that `currentView` is defined.
*/
let currentView: LView = null !;

let currentQueries: LQueries|null;

Expand Down Expand Up @@ -131,21 +136,21 @@ const enum BindingDirection {
*/
export function enterView(newView: LView, host: LElementNode | LViewNode | null): LView {
const oldView = currentView;
data = newView.data;
bindingIndex = newView.bindingStartIndex || 0;
tData = newView.tView.data;
creationMode = newView.creationMode;
data = newView && newView.data;
bindingIndex = newView && newView.bindingStartIndex || 0;
tData = newView && newView.tView.data;
creationMode = newView && newView.creationMode;

cleanup = newView.cleanup;
renderer = newView.renderer;
cleanup = newView && newView.cleanup;
renderer = newView && newView.renderer;

if (host != null) {
previousOrParentNode = host;
isParent = true;
}

currentView = newView;
currentQueries = newView.queries;
currentQueries = newView && newView.queries;

return oldView !;
}
Expand All @@ -165,8 +170,8 @@ export function leaveView(newView: LView): void {
}

export function createLView(
viewId: number, renderer: Renderer3, tView: TView,
template: ComponentTemplate<any>| null = null, context: any | null = null): LView {
viewId: number, renderer: Renderer3, tView: TView, template: ComponentTemplate<any>| null,
context: any | null): LView {
const newView = {
parent: currentView,
id: viewId, // -1 for component views
Expand Down Expand Up @@ -300,7 +305,8 @@ export function renderTemplate<T>(
host = createLNode(
null, LNodeFlags.Element, hostNode,
createLView(
-1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template)));
-1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template),
null, null));
}
const hostView = host.data !;
ngDevMode && assertNotNull(hostView, 'Host node should have an LView defined in host.data.');
Expand Down Expand Up @@ -406,7 +412,8 @@ export function elementStart(
if (isHostElement) {
const tView = getOrCreateTView(hostComponentDef !.template);
componentView = addToViewTree(createLView(
-1, rendererFactory.createRenderer(native, hostComponentDef !.rendererType), tView));
-1, rendererFactory.createRenderer(native, hostComponentDef !.rendererType), tView,
null, null));
}

// Only component views should be added to the view tree directly. Embedded views are
Expand Down Expand Up @@ -556,7 +563,8 @@ export function locateHostElement(
export function hostElement(rNode: RElement | null, def: ComponentDef<any>) {
resetApplicationState();
createLNode(
0, LNodeFlags.Element, rNode, createLView(-1, renderer, getOrCreateTView(def.template)));
0, LNodeFlags.Element, rNode,
createLView(-1, renderer, getOrCreateTView(def.template), null, null));
}


Expand Down Expand Up @@ -1114,8 +1122,8 @@ export function embeddedViewStart(viewBlockId: number): boolean {
enterView((existingView as LViewNode).data, previousOrParentNode as LViewNode);
} else {
// When we create a new LView, we always reset the state of the instructions.
const newView =
createLView(viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container));
const newView = createLView(
viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null);
if (lContainer.queries) {
newView.queries = lContainer.queries.enterView(lContainer.nextIndex);
}
Expand Down
31 changes: 29 additions & 2 deletions packages/core/src/render3/interfaces/view.ts
Expand Up @@ -160,9 +160,11 @@ export interface LView {
template: ComponentTemplate<{}>|null;

/**
* For embedded views, the context with which to render the template.
* - For embedded views, the context with which to render the template.
* - For root view of the root component the context contains change detection data.
* - `null` otherwise.
*/
context: {}|null;
context: {}|RootContext|null;

/**
* A count of dynamic views that are children of this view (indirectly via containers).
Expand Down Expand Up @@ -261,6 +263,31 @@ export interface TView {
destroyHooks: HookData|null;
}

/**
* RootContext contains information which is shared for all components which
* were bootstrapped with {@link renderComponent}.
*/
export interface RootContext {
/**
* A function used for scheduling change detection in the future. Usually
* this is `requestAnimationFrame`.
*/
scheduler: (workFn: () => void) => void;

/**
* A promise which is resolved when all components are considered clean (not dirty).
*
* This promise is overwritten every time a first call to {@link markDirty} is invoked.
*/
clean: Promise<null>;

/**
* RootComponent - The component which was instantiated by the call to
* {@link renderComponent}.
*/
component: {};
}

/**
* Array of hooks that should be executed for a view and their directive indices.
*
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/bundling/hello_world/BUILD.bazel
Expand Up @@ -34,6 +34,7 @@ ts_library(
srcs = ["domino_typings.d.ts"] + glob(["*_spec.ts"]),
deps = [
"//packages:types",
"//packages/core/testing",
],
)

Expand Down

0 comments on commit 6f320e4

Please sign in to comment.