
committed by
Matias Niemelä

parent
f33dbf42fd
commit
d5b70e0c66
@ -14,7 +14,7 @@ import {Sanitizer} from '../sanitization/security';
|
||||
|
||||
import {assertComponentType, assertDefined} from './assert';
|
||||
import {queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||
import {CLEAN_PROMISE, ROOT_DIRECTIVE_INDICES, _getComponentHostLElementNode, baseDirectiveCreate, createLViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, setHostBindings,} from './instructions';
|
||||
import {CLEAN_PROMISE, _getComponentHostLElementNode, baseDirectiveCreate, createLViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, getRootView, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, setHostBindings, queueHostBindingForCheck,} from './instructions';
|
||||
import {ComponentDef, ComponentDefInternal, ComponentType} from './interfaces/definition';
|
||||
import {LElementNode} from './interfaces/node';
|
||||
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
@ -122,6 +122,9 @@ export function renderComponent<T>(
|
||||
|
||||
// Create directive instance with factory() and store at index 0 in directives array
|
||||
component = baseDirectiveCreate(0, componentDef.factory() as T, componentDef);
|
||||
if (componentDef.hostBindings) {
|
||||
queueHostBindingForCheck(0, componentDef.hostVars);
|
||||
}
|
||||
rootContext.components.push(component);
|
||||
(elementNode.data as LViewData)[CONTEXT] = component;
|
||||
initChangeDetectorIfExisting(elementNode.nodeInjector, component, elementNode.data !);
|
||||
@ -129,7 +132,7 @@ export function renderComponent<T>(
|
||||
opts.hostFeatures && opts.hostFeatures.forEach((feature) => feature(component, componentDef));
|
||||
|
||||
executeInitAndContentHooks();
|
||||
setHostBindings(ROOT_DIRECTIVE_INDICES);
|
||||
setHostBindings(rootView[TVIEW].hostBindings);
|
||||
detectChangesInternal(elementNode.data as LViewData, elementNode, component);
|
||||
} finally {
|
||||
leaveView(oldView);
|
||||
|
@ -18,7 +18,7 @@ import {Type} from '../type';
|
||||
|
||||
import {assertComponentType, assertDefined} from './assert';
|
||||
import {LifecycleHooksFeature, createRootContext} from './component';
|
||||
import {baseDirectiveCreate, createLNode, createLViewData, createTView, elementCreate, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderEmbeddedTemplate} from './instructions';
|
||||
import {adjustBlueprintForNewNode, baseDirectiveCreate, createLNode, createLViewData, createTView, elementCreate, enterView, hostElement, initChangeDetectorIfExisting, locateHostElement, renderEmbeddedTemplate} from './instructions';
|
||||
import {ComponentDefInternal, ComponentType, RenderFlags} from './interfaces/definition';
|
||||
import {LElementNode, TNode, TNodeType} from './interfaces/node';
|
||||
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
@ -156,6 +156,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||
let firstTNode: TNode|null = null;
|
||||
let previousTNode: TNode|null = null;
|
||||
for (let j = 0; j < nodeList.length; j++) {
|
||||
adjustBlueprintForNewNode(rootView);
|
||||
const lNode =
|
||||
createLNode(++index, TNodeType.Element, nodeList[j] as RElement, null, null);
|
||||
if (previousTNode) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {assertEqual, assertLessThan} from './assert';
|
||||
import {NO_CHANGE, _getViewData, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createLNode, getPreviousOrParentNode, getRenderer, load, resetApplicationState} from './instructions';
|
||||
import {NO_CHANGE, _getViewData, adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createLNode, getPreviousOrParentNode, getRenderer, load, resetApplicationState} from './instructions';
|
||||
import {RENDER_PARENT} from './interfaces/container';
|
||||
import {LContainerNode, LNode, TContainerNode, TElementNode, TNodeType} from './interfaces/node';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, TVIEW} from './interfaces/view';
|
||||
@ -330,8 +330,10 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]):
|
||||
// If we were to only create a `RNode` then projections won't move the text.
|
||||
// Create text node at the current end of viewData. Must subtract header offset because
|
||||
// createLNode takes a raw index (not adjusted by header offset).
|
||||
adjustBlueprintForNewNode(viewData);
|
||||
const lastNodeIndex = viewData.length - 1;
|
||||
const textLNode =
|
||||
createLNode(viewData.length - HEADER_OFFSET, TNodeType.Element, textRNode, null, null);
|
||||
createLNode(lastNodeIndex - HEADER_OFFSET, TNodeType.Element, textRNode, null, null);
|
||||
localPreviousNode = appendI18nNode(textLNode, localParentNode, localPreviousNode);
|
||||
resetApplicationState();
|
||||
break;
|
||||
|
@ -22,7 +22,7 @@ import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LEleme
|
||||
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
||||
import {LQueries} from './interfaces/query';
|
||||
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {BINDING_INDEX, CLEANUP, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
|
||||
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
@ -48,17 +48,6 @@ const _CLEAN_PROMISE = Promise.resolve(null);
|
||||
*/
|
||||
export type SanitizerFn = (value: any) => string;
|
||||
|
||||
/**
|
||||
* Directive and element indices for top-level directive.
|
||||
*
|
||||
* Saved here to avoid re-instantiating an array on every change detection run.
|
||||
*
|
||||
* Note: Element is not actually stored at index 0 because of the LViewData
|
||||
* header, but the host bindings function expects an index that is NOT adjusted
|
||||
* because it will ultimately be fed to instructions like elementProperty.
|
||||
*/
|
||||
const _ROOT_DIRECTIVE_INDICES = [0, 0];
|
||||
|
||||
/**
|
||||
* TView.data needs to fill the same number of slots as the LViewData header
|
||||
* so the indices of nodes are consistent between LViewData and TView.data.
|
||||
@ -346,7 +335,7 @@ export function setHostBindings(bindings: number[] | null): void {
|
||||
for (let i = 0; i < bindings.length; i += 2) {
|
||||
const dirIndex = bindings[i];
|
||||
const def = defs[dirIndex] as DirectiveDefInternal<any>;
|
||||
def.hostBindings && def.hostBindings(dirIndex, bindings[i + 1]);
|
||||
def.hostBindings !(dirIndex, bindings[i + 1]);
|
||||
bindingRootIndex = viewData[BINDING_INDEX] = bindingRootIndex + def.hostVars;
|
||||
}
|
||||
}
|
||||
@ -383,26 +372,14 @@ export function executeInitAndContentHooks(): void {
|
||||
export function createLViewData<T>(
|
||||
renderer: Renderer3, tView: TView, context: T | null, flags: LViewFlags,
|
||||
sanitizer?: Sanitizer | null): LViewData {
|
||||
// TODO(kara): create from blueprint
|
||||
return [
|
||||
tView, // tView
|
||||
viewData, // parent
|
||||
null, // next
|
||||
null, // queries
|
||||
flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit, // flags
|
||||
null !, // hostNode
|
||||
tView.bindingStartIndex, // bindingIndex
|
||||
null, // directives
|
||||
null, // cleanupInstances
|
||||
context, // context
|
||||
viewData ? viewData[INJECTOR] : null, // injector
|
||||
renderer, // renderer
|
||||
sanitizer || null, // sanitizer
|
||||
null, // tail
|
||||
-1, // containerIndex
|
||||
null, // contentQueries
|
||||
null // declarationView
|
||||
];
|
||||
const instance = tView.blueprint.slice() as LViewData;
|
||||
instance[PARENT] = viewData;
|
||||
instance[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.RunInit;
|
||||
instance[CONTEXT] = context;
|
||||
instance[INJECTOR] = viewData ? viewData[INJECTOR] : null;
|
||||
instance[RENDERER] = renderer;
|
||||
instance[SANITIZER] = sanitizer || null;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -474,13 +451,13 @@ export function createLNode(
|
||||
const adjustedIndex = index + HEADER_OFFSET;
|
||||
|
||||
// This is an element or container or projection node
|
||||
ngDevMode && assertDataNext(adjustedIndex);
|
||||
const tData = tView.data;
|
||||
ngDevMode && assertLessThan(
|
||||
adjustedIndex, viewData.length, `Slot should have been initialized with null`);
|
||||
|
||||
viewData[adjustedIndex] = node;
|
||||
|
||||
// Every node adds a value to the static data array to avoid a sparse array
|
||||
if (adjustedIndex >= tData.length) {
|
||||
if (tData[adjustedIndex] == null) {
|
||||
const tNode = tData[adjustedIndex] =
|
||||
createTNode(type, adjustedIndex, name, attrs, tParent, null);
|
||||
if (!isParent && previousOrParentNode) {
|
||||
@ -504,8 +481,9 @@ export function createLNode(
|
||||
// View nodes and host elements need to set their host node (components set host nodes later)
|
||||
if ((type & TNodeType.ViewOrElement) === TNodeType.ViewOrElement && isState) {
|
||||
const lViewData = state as LViewData;
|
||||
ngDevMode && assertNotDefined(
|
||||
lViewData[HOST_NODE], 'lViewData[HOST_NODE] should not have been initialized');
|
||||
ngDevMode &&
|
||||
assertEqual(
|
||||
lViewData[HOST_NODE], null, 'lViewData[HOST_NODE] should not have been initialized');
|
||||
lViewData[HOST_NODE] = node;
|
||||
if (firstTemplatePass) lViewData[TVIEW].node = node.tNode;
|
||||
}
|
||||
@ -515,6 +493,20 @@ export function createLNode(
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* When LNodes are created dynamically after a view blueprint is created (e.g. through
|
||||
* i18nApply() or ComponentFactory.create), we need to adjust the blueprint for future
|
||||
* template passes.
|
||||
*/
|
||||
export function adjustBlueprintForNewNode(view: LViewData) {
|
||||
const tView = view[TVIEW];
|
||||
if (tView.firstTemplatePass) {
|
||||
tView.hostBindingStartIndex++;
|
||||
tView.blueprint.push(null);
|
||||
view.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////
|
||||
//// Render
|
||||
@ -664,7 +656,7 @@ export function renderComponentOrTemplate<T>(
|
||||
|
||||
// Element was stored at 0 in data and directive was stored at 0 in directives
|
||||
// in renderComponent()
|
||||
setHostBindings(_ROOT_DIRECTIVE_INDICES);
|
||||
setHostBindings(tView.hostBindings);
|
||||
componentRefresh(HEADER_OFFSET);
|
||||
}
|
||||
} finally {
|
||||
@ -910,19 +902,23 @@ export function resolveDirective(
|
||||
/** Stores index of component's host element so it will be queued for view refresh during CD. */
|
||||
function queueComponentIndexForCheck(): void {
|
||||
if (firstTemplatePass) {
|
||||
(tView.components || (tView.components = [])).push(viewData.length - 1);
|
||||
(tView.components || (tView.components = [])).push(previousOrParentNode.tNode.index);
|
||||
}
|
||||
}
|
||||
|
||||
/** Stores index of directive and host element so it will be queued for binding refresh during CD.
|
||||
*/
|
||||
function queueHostBindingForCheck(dirIndex: number): void {
|
||||
export function queueHostBindingForCheck(dirIndex: number, hostVars: number): void {
|
||||
// Must subtract the header offset because hostBindings functions are generated with
|
||||
// instructions that expect element indices that are NOT adjusted (e.g. elementProperty).
|
||||
ngDevMode &&
|
||||
assertEqual(firstTemplatePass, true, 'Should only be called in first template pass.');
|
||||
for (let i = 0; i < hostVars; i++) {
|
||||
tView.blueprint.push(NO_CHANGE);
|
||||
viewData.push(NO_CHANGE);
|
||||
}
|
||||
(tView.hostBindings || (tView.hostBindings = [
|
||||
])).push(dirIndex, viewData.length - 1 - HEADER_OFFSET);
|
||||
])).push(dirIndex, previousOrParentNode.tNode.index - HEADER_OFFSET);
|
||||
}
|
||||
|
||||
/** Sets the context for a ChangeDetectorRef to the given instance. */
|
||||
@ -1005,10 +1001,11 @@ function saveResolvedLocalsInData(
|
||||
lNode: LNodeWithLocalRefs, localRefExtractor: LocalRefExtractor): void {
|
||||
const localNames = lNode.tNode.localNames;
|
||||
if (localNames) {
|
||||
let localIndex = lNode.tNode.index + 1;
|
||||
for (let i = 0; i < localNames.length; i += 2) {
|
||||
const index = localNames[i + 1] as number;
|
||||
const value = index === -1 ? localRefExtractor(lNode) : directives ![index];
|
||||
viewData.push(value);
|
||||
viewData[localIndex++] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1055,15 +1052,21 @@ export function createTView(
|
||||
viewQuery: ComponentQuery<any>| null): TView {
|
||||
ngDevMode && ngDevMode.tView++;
|
||||
const bindingStartIndex = HEADER_OFFSET + consts;
|
||||
return {
|
||||
// This length does not yet contain host bindings from child directives because at this point,
|
||||
// we don't know which directives are active on this template. As soon as a directive is matched
|
||||
// that has a host binding, we will update the blueprint with that def's hostVars count.
|
||||
const initialViewLength = bindingStartIndex + vars;
|
||||
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
|
||||
return blueprint[TVIEW] = {
|
||||
id: viewIndex,
|
||||
blueprint: blueprint,
|
||||
template: templateFn,
|
||||
viewQuery: viewQuery,
|
||||
node: null !,
|
||||
data: HEADER_FILLER.slice(), // Fill in to match HEADER_OFFSET in LViewData
|
||||
childIndex: -1, // Children set in addToViewTree(), if any
|
||||
bindingStartIndex: bindingStartIndex,
|
||||
hostBindingStartIndex: bindingStartIndex + vars,
|
||||
hostBindingStartIndex: initialViewLength,
|
||||
directives: null,
|
||||
firstTemplatePass: true,
|
||||
initHooks: null,
|
||||
@ -1084,6 +1087,15 @@ export function createTView(
|
||||
};
|
||||
}
|
||||
|
||||
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LViewData {
|
||||
const blueprint = new Array(initialViewLength)
|
||||
.fill(null, 0, bindingStartIndex)
|
||||
.fill(NO_CHANGE, bindingStartIndex) as LViewData;
|
||||
blueprint[CONTAINER_INDEX] = -1;
|
||||
blueprint[BINDING_INDEX] = bindingStartIndex;
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
function setUpAttributes(native: RElement, attrs: TAttributes): void {
|
||||
const isProc = isProceduralRenderer(renderer);
|
||||
let i = 0;
|
||||
@ -1680,7 +1692,7 @@ export function directiveCreate<T>(
|
||||
// any projected components.
|
||||
queueInitHooks(directiveDefIdx, directiveDef.onInit, directiveDef.doCheck, tView);
|
||||
|
||||
if (directiveDef.hostBindings) queueHostBindingForCheck(directiveDefIdx);
|
||||
if (directiveDef.hostBindings) queueHostBindingForCheck(directiveDefIdx, directiveDef.hostVars);
|
||||
}
|
||||
|
||||
if (tNode && tNode.attrs) {
|
||||
@ -2758,8 +2770,10 @@ export function getBinding(bindingIndex: number): any {
|
||||
/** Updates binding if changed, then returns whether it was updated. */
|
||||
export function bindingUpdated(bindingIndex: number, value: any): boolean {
|
||||
ngDevMode && assertNotEqual(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.');
|
||||
ngDevMode && assertLessThan(
|
||||
bindingIndex, viewData.length, `Slot should have been initialized to NO_CHANGE`);
|
||||
|
||||
if (bindingIndex >= viewData.length) {
|
||||
if (viewData[bindingIndex] === NO_CHANGE) {
|
||||
viewData[bindingIndex] = value;
|
||||
} else if (isDifferent(viewData[bindingIndex], value, checkNoChangesMode)) {
|
||||
throwErrorIfNoChangesMode(creationMode, checkNoChangesMode, viewData[bindingIndex], value);
|
||||
@ -2843,4 +2857,3 @@ export function _getComponentHostLElementNode<T>(component: T): LElementNode {
|
||||
}
|
||||
|
||||
export const CLEAN_PROMISE = _CLEAN_PROMISE;
|
||||
export const ROOT_DIRECTIVE_INDICES = _ROOT_DIRECTIVE_INDICES;
|
||||
|
@ -253,6 +253,12 @@ export interface TView {
|
||||
*/
|
||||
readonly id: number;
|
||||
|
||||
/**
|
||||
* This is a blueprint used to generate LViewData instances for this TView. Copying this
|
||||
* blueprint is faster than creating a new LViewData from scratch.
|
||||
*/
|
||||
blueprint: LViewData;
|
||||
|
||||
/**
|
||||
* The template function used to refresh the view of dynamically created views
|
||||
* and components. Will be null for inline views.
|
||||
|
Reference in New Issue
Block a user