Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sprint3 #22018

Closed
wants to merge 3 commits into from
Closed

Sprint3 #22018

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/BAZEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,13 @@ source: https://github.com/bazelbuild/bazel/issues/4603

If VSCode is not the root cause, you might try:

- Quit VSCode (make sure no VSCode is running).

```
bazel clean --expunge
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license
bazel build //packages/core # Run a build outside VSCode to pre-build the xcode; then safe to run VSCode
```

Source: https://stackoverflow.com/questions/45276830/xcode-version-must-be-specified-to-use-an-apple-crosstool
162 changes: 144 additions & 18 deletions packages/core/src/render3/component.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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