diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index b5df38197c..f7e3863652 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -588,7 +588,7 @@ describe('compiler compliance', () => { selectors: [["", "hostBindingDir", ""]], factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); }, hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵloadDirective(dirIndex).dirId)); + $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵload(dirIndex).dirId)); }, hostVars: 1, features: [$r3$.ɵPublicFeature] @@ -632,7 +632,7 @@ describe('compiler compliance', () => { selectors: [["host-binding-comp"]], factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); }, hostBindings: function HostBindingComp_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, $r3$.ɵloadDirective(dirIndex).id))); + $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, $r3$.ɵload(dirIndex).id))); }, hostVars: 3, features: [$r3$.ɵPublicFeature], @@ -1343,7 +1343,7 @@ describe('compiler compliance', () => { $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false)); }, contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) { - const instance = $r3$.ɵloadDirective(dirIndex); + const instance = $r3$.ɵload(dirIndex); var $tmp$; ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && ($instance$.someDir = $tmp$.first)); ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && ($instance$.someDirList = $tmp$)); @@ -1403,7 +1403,7 @@ describe('compiler compliance', () => { $r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef1", "myRef2", "myRef3"], false)); }, contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) { - const instance = $r3$.ɵloadDirective(dirIndex); + const instance = $r3$.ɵload(dirIndex); var $tmp$; ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && (instance.myRef = $tmp$.first)); ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && (instance.myRefs = $tmp$)); @@ -1452,7 +1452,7 @@ describe('compiler compliance', () => { $r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef1", "myRef2", "myRef3"], false, ElementRef)); }, contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) { - const instance = $r3$.ɵloadDirective(dirIndex); + const instance = $r3$.ɵload(dirIndex); var $tmp$; ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && (instance.myRef = $tmp$.first)); ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && (instance.myRefs = $tmp$)); diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index a440d02ccd..140cb6d097 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -463,17 +463,14 @@ describe('ngtsc behavioral tests', () => { env.driveMain(); const jsContents = env.getContents('test.js'); expect(jsContents) - .toContain( - `i0.ɵelementProperty(elIndex, "attr.hello", i0.ɵbind(i0.ɵloadDirective(dirIndex).foo));`); + .toContain(`i0.ɵelementProperty(elIndex, "attr.hello", i0.ɵbind(i0.ɵload(dirIndex).foo));`); + expect(jsContents) + .toContain(`i0.ɵelementProperty(elIndex, "prop", i0.ɵbind(i0.ɵload(dirIndex).bar));`); expect(jsContents) .toContain( - `i0.ɵelementProperty(elIndex, "prop", i0.ɵbind(i0.ɵloadDirective(dirIndex).bar));`); - expect(jsContents) - .toContain( - 'i0.ɵelementProperty(elIndex, "class.someclass", i0.ɵbind(i0.ɵloadDirective(dirIndex).someClass))'); - expect(jsContents).toContain('i0.ɵloadDirective(dirIndex).onClick($event)'); - expect(jsContents) - .toContain('i0.ɵloadDirective(dirIndex).onChange(i0.ɵloadDirective(dirIndex).arg)'); + 'i0.ɵelementProperty(elIndex, "class.someclass", i0.ɵbind(i0.ɵload(dirIndex).someClass))'); + expect(jsContents).toContain('i0.ɵload(dirIndex).onClick($event)'); + expect(jsContents).toContain('i0.ɵload(dirIndex).onChange(i0.ɵload(dirIndex).arg)'); }); it('should correctly recognize local symbols', () => { diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 0ed1b8f9e2..9d7539f996 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -96,7 +96,6 @@ export class Identifiers { static pipeBindV: o.ExternalReference = {name: 'ɵpipeBindV', moduleName: CORE}; static load: o.ExternalReference = {name: 'ɵload', moduleName: CORE}; - static loadDirective: o.ExternalReference = {name: 'ɵloadDirective', moduleName: CORE}; static loadQueryList: o.ExternalReference = {name: 'ɵloadQueryList', moduleName: CORE}; static pipe: o.ExternalReference = {name: 'ɵpipe', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index e96d632aa9..a4cc3f4761 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -473,10 +473,9 @@ function createContentQueriesRefreshFunction(meta: R3DirectiveMetadata): o.Expre // var $tmp$: any; const temporary = temporaryAllocator(statements, TEMPORARY_NAME); - // const $instance$ = $r3$.ɵloadDirective(dirIndex); - statements.push( - directiveInstanceVar.set(o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')])) - .toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); + // const $instance$ = $r3$.ɵload(dirIndex); + statements.push(directiveInstanceVar.set(o.importExpr(R3.load).callFn([o.variable('dirIndex')])) + .toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); meta.queries.forEach((query: R3QueryMetadata, idx: number) => { const loadQLArg = o.variable('queryStartIndex'); @@ -580,7 +579,7 @@ function createHostBindingsFunction( // Calculate the host property bindings const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan); - const bindingContext = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]); + const bindingContext = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); if (bindings) { const valueConverter = new ValueConverter( constantPool, diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 29e6c4eaa0..c8912a0959 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -47,7 +47,6 @@ export { embeddedViewStart as ɵembeddedViewStart, query as ɵquery, registerContentQuery as ɵregisterContentQuery, - loadDirective as ɵloadDirective, projection as ɵprojection, bind as ɵbind, interpolation1 as ɵinterpolation1, diff --git a/packages/core/src/render3/VIEW_DATA.md b/packages/core/src/render3/VIEW_DATA.md index 5356ffd2b8..20928eca12 100644 --- a/packages/core/src/render3/VIEW_DATA.md +++ b/packages/core/src/render3/VIEW_DATA.md @@ -128,8 +128,6 @@ NOTE: ## `EXPANDO` -*TODO*: This section is to be implemented. - `EXPANDO` contains information on data which size is not known at compile time. Examples include: - `Component`/`Directives` since we don't know at compile time which directives will match. @@ -203,7 +201,7 @@ The `EXPANDO` section needs additional information for information stored in `TV | Index | `TView.expandoInstructions` | Meaning | ----: | ---------------------------: | ------- -| 0 | -10 | Negative numbers signifies pointers to elements. In this case 10 (``) +| 0 | -10 | Negative numbers signify pointers to elements. In this case 10 (``) | 1 | 2 | Injector size. Number of values to skip to get to Host Bindings. | 2 | Child.ngComponentDef.hostBindings | The function to call. (Only when `hostVars` is not `0`) | 3 | Child.ngComponentDef.hostVars | Number of host bindings to process. (Only when `hostVars` is not `0`) @@ -215,9 +213,9 @@ The reason for this layout is to make the host binding update efficient using th let currentDirectiveIndex = -1; let currentElementIndex = -1; // This is global state which is used internally by hostBindings to know where the offset is -let bindingRootIndex = tView.expandoStart; -for(var i = 0; i < tview.expandoInstructions.length; i++) { - let instruction = tview.expandoInstructions[i]; +let bindingRootIndex = tView.expandoStartIndex; +for(var i = 0; i < tView.expandoInstructions.length; i++) { + let instruction = tView.expandoInstructions[i]; if (typeof instruction === 'number') { // Numbers are used to update the indices. if (instruction < 0) { diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index a3e32ef482..438d056c21 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -16,11 +16,10 @@ import {assertComponentType, assertDefined} from './assert'; import {getLElementFromComponent, readPatchedLViewData} from './context_discovery'; import {getComponentDef} from './definition'; import {queueInitHooks, queueLifecycleHooks} from './hooks'; -import {PlayerHandler} from './interfaces/player'; - -import {CLEAN_PROMISE, baseDirectiveCreate, createLViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, hostElement, leaveView, locateHostElement, setHostBindings, queueHostBindingForCheck,} from './instructions'; +import {CLEAN_PROMISE, baseDirectiveCreate, createLViewData, createTView, detectChangesInternal, enterView, executeInitAndContentHooks, hostElement, leaveView, locateHostElement, prefillHostVars, setHostBindings} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; -import {LElementNode} from './interfaces/node'; +import {LElementNode, TNodeFlags} from './interfaces/node'; +import {PlayerHandler} from './interfaces/player'; import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {CONTEXT, INJECTOR, LViewData, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view'; import {getRootView, stringify} from './util'; @@ -151,15 +150,16 @@ export function renderComponent( export function createRootComponent( elementNode: LElementNode, componentDef: ComponentDef, rootView: LViewData, rootContext: RootContext, hostFeatures: HostFeature[] | null): any { - // Create directive instance with factory() and store at index 0 in directives array - const component = baseDirectiveCreate(0, componentDef.factory() as T, componentDef, elementNode); + // Create directive instance with factory() and store at next index in viewData + const component = + baseDirectiveCreate(rootView.length, componentDef.factory() as T, componentDef, elementNode); - if (componentDef.hostBindings) queueHostBindingForCheck(0, componentDef.hostVars); rootContext.components.push(component); (elementNode.data as LViewData)[CONTEXT] = component; hostFeatures && hostFeatures.forEach((feature) => feature(component, componentDef)); - setHostBindings(rootView[TVIEW].hostBindings); + if (rootView[TVIEW].firstTemplatePass) prefillHostVars(componentDef.hostVars); + setHostBindings(); return component; } @@ -190,11 +190,10 @@ export function createRootContext( */ export function LifecycleHooksFeature(component: any, def: ComponentDef): void { const rootTView = readPatchedLViewData(component) ![TVIEW]; + const dirIndex = rootTView.data.length - 1; - // Root component is always created at dir index 0 - queueInitHooks(0, def.onInit, def.doCheck, rootTView); - // Directive starting index 0, directive count 1 -> directive flags: 1 - queueLifecycleHooks(1, rootTView); + queueInitHooks(dirIndex, def.onInit, def.doCheck, rootTView); + queueLifecycleHooks(dirIndex << TNodeFlags.DirectiveStartingIndexShift | 1, rootTView); } /** diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index b936f20fa5..b861fca7c8 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -139,13 +139,6 @@ export class ComponentFactory extends viewEngine_ComponentFactory { // Create element node at index 0 in data array elementNode = hostElement(componentTag, hostNode, this.componentDef); - - // TODO: should LifecycleHooksFeature and other host features be generated by the compiler and - // executed here? - // Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref - component = createRootComponent( - elementNode, this.componentDef, rootView, rootContext, [LifecycleHooksFeature]); - tElementNode = getTNode(0) as TElementNode; // Transform the arrays of native nodes into a LNode structure that can be consumed by the @@ -168,6 +161,12 @@ export class ComponentFactory extends viewEngine_ComponentFactory { } } + // TODO: should LifecycleHooksFeature and other host features be generated by the compiler and + // executed here? + // Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref + component = createRootComponent( + elementNode, this.componentDef, rootView, rootContext, [LifecycleHooksFeature]); + // Execute the template in creation mode only, and then turn off the CreationMode flag const componentView = elementNode.data as LViewData; renderEmbeddedTemplate(componentView, componentView[TVIEW], component, RenderFlags.Create); diff --git a/packages/core/src/render3/context_discovery.ts b/packages/core/src/render3/context_discovery.ts index 30701f9f21..42250da71b 100644 --- a/packages/core/src/render3/context_discovery.ts +++ b/packages/core/src/render3/context_discovery.ts @@ -10,7 +10,7 @@ import './ng_dev_mode'; import {assertEqual} from './assert'; import {LElementNode, TNode, TNodeFlags} from './interfaces/node'; import {RElement} from './interfaces/renderer'; -import {CONTEXT, DIRECTIVES, HEADER_OFFSET, LViewData, TVIEW} from './interfaces/view'; +import {CONTEXT, HEADER_OFFSET, LViewData, TVIEW} from './interfaces/view'; /** * This property will be monkey-patched on elements, components and directives @@ -306,21 +306,17 @@ function findViaDirective(lViewData: LViewData, directiveInstance: {}): number { // element bound to the directive being search lives somewhere // in the view data. We loop through the nodes and check their // list of directives for the instance. - const directivesAcrossView = lViewData[DIRECTIVES]; let tNode = lViewData[TVIEW].firstChild; - if (directivesAcrossView != null) { - while (tNode) { - const directiveIndexStart = getDirectiveStartIndex(tNode); - const directiveIndexEnd = getDirectiveEndIndex(tNode, directiveIndexStart); - for (let i = directiveIndexStart; i < directiveIndexEnd; i++) { - if (directivesAcrossView[i] === directiveInstance) { - return tNode.index; - } + while (tNode) { + const directiveIndexStart = getDirectiveStartIndex(tNode); + const directiveIndexEnd = getDirectiveEndIndex(tNode, directiveIndexStart); + for (let i = directiveIndexStart; i < directiveIndexEnd; i++) { + if (lViewData[i] === directiveInstance) { + return tNode.index; } - tNode = traverseNextElement(tNode); } + tNode = traverseNextElement(tNode); } - return -1; } @@ -341,24 +337,20 @@ function getLNodeFromViewData(lViewData: LViewData, lElementIndex: number): LEle } /** - * Returns a list of directives extracted from the given view. Does not contain - * the component. + * Returns a list of directives extracted from the given view based on the + * provided list of directive index values. * - * @param nodeIndex Index of node to search + * @param nodeIndex The node index * @param lViewData The target view data * @param includeComponents Whether or not to include components in returned directives */ export function discoverDirectives( nodeIndex: number, lViewData: LViewData, includeComponents: boolean): any[]|null { - const directivesAcrossView = lViewData[DIRECTIVES]; - if (directivesAcrossView != null) { - const tNode = lViewData[TVIEW].data[nodeIndex] as TNode; - let directiveStartIndex = getDirectiveStartIndex(tNode); - const directiveEndIndex = getDirectiveEndIndex(tNode, directiveStartIndex); - if (!includeComponents && tNode.flags & TNodeFlags.isComponent) directiveStartIndex++; - return directivesAcrossView.slice(directiveStartIndex, directiveEndIndex); - } - return null; + const tNode = lViewData[TVIEW].data[nodeIndex] as TNode; + let directiveStartIndex = getDirectiveStartIndex(tNode); + const directiveEndIndex = getDirectiveEndIndex(tNode, directiveStartIndex); + if (!includeComponents && tNode.flags & TNodeFlags.isComponent) directiveStartIndex++; + return lViewData.slice(directiveStartIndex, directiveEndIndex); } /** @@ -375,7 +367,7 @@ export function discoverLocalRefs(lViewData: LViewData, lNodeIndex: number): {[k const directiveIndex = tNode.localNames[i + 1] as number; result[localRefName] = directiveIndex === -1 ? getLNodeFromViewData(lViewData, lNodeIndex) !.native : - lViewData[DIRECTIVES] ![directiveIndex]; + lViewData[directiveIndex]; } return result; } diff --git a/packages/core/src/render3/debug.ts b/packages/core/src/render3/debug.ts index df5c37b117..8199947f95 100644 --- a/packages/core/src/render3/debug.ts +++ b/packages/core/src/render3/debug.ts @@ -70,7 +70,7 @@ class Render3DebugContext implements DebugContext { // TODO(vicb): add view providers when supported get providerTokens(): any[] { // TODO(vicb): why/when - const directiveDefs = this.view[TVIEW].directives; + const directiveDefs = this.view[TVIEW].data; if (this.nodeIndex === null || directiveDefs == null) { return []; } diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 568b0b6ad0..ad029a1195 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -20,10 +20,10 @@ import {getComponentDef, getDirectiveDef, getPipeDef} from './definition'; import {NG_ELEMENT_ID} from './fields'; import {_getViewData, assertPreviousIsParent, getPreviousOrParentTNode, resolveDirective, setEnvironment} from './instructions'; import {DirectiveDef} from './interfaces/definition'; -import {INJECTOR_SIZE, InjectorLocationFlags, PARENT_INJECTOR, TNODE,} from './interfaces/injector'; +import {InjectorLocationFlags, PARENT_INJECTOR, TNODE,} from './interfaces/injector'; import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; import {isProceduralRenderer} from './interfaces/renderer'; -import {DECLARATION_VIEW, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, PARENT, RENDERER, TData, TVIEW, TView} from './interfaces/view'; +import {DECLARATION_VIEW, HOST_NODE, INJECTOR, LViewData, PARENT, RENDERER, TData, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes} from './node_assert'; /** @@ -104,12 +104,10 @@ export function getOrCreateNodeInjectorForNode( const tView = hostView[TVIEW]; if (tView.firstTemplatePass) { - // TODO(kara): Store node injector with host bindings for that node (see VIEW_DATA.md) tNode.injectorIndex = hostView.length; setUpBloom(tView.data, tNode); // foundation for node bloom setUpBloom(hostView, null); // foundation for cumulative bloom setUpBloom(tView.blueprint, null); - tView.hostBindingStartIndex += INJECTOR_SIZE; } const parentLoc = getParentInjectorLocation(tNode, hostView); @@ -244,7 +242,9 @@ export function directiveInject(token: Type| InjectionToken): T; export function directiveInject(token: Type| InjectionToken, flags: InjectFlags): T; export function directiveInject( token: Type| InjectionToken, flags = InjectFlags.Default): T|null { - return getOrCreateInjectable(getOrCreateNodeInjector(), _getViewData(), token, flags); + const hostTNode = + getPreviousOrParentTNode() as TElementNode | TContainerNode | TElementContainerNode; + return getOrCreateInjectable(hostTNode, _getViewData(), token, flags); } export function injectRenderer2(): Renderer2 { @@ -320,8 +320,8 @@ function getOrCreateRenderer2(view: LViewData): Renderer2 { * @returns the value from the injector or `null` when not found */ export function getOrCreateInjectable( - startInjectorIndex: number, hostView: LViewData, token: Type| InjectionToken, - flags: InjectFlags = InjectFlags.Default): T|null { + hostTNode: TElementNode | TContainerNode | TElementContainerNode, hostView: LViewData, + token: Type| InjectionToken, flags: InjectFlags = InjectFlags.Default): T|null { const bloomHash = bloomHashBitOrFactory(token); // If the ID stored here is a function, this is a special object like ElementRef or TemplateRef // so just call the factory function to create it. @@ -330,13 +330,24 @@ export function getOrCreateInjectable( // If the token has a bloom hash, then it is a directive that is public to the injection system // (diPublic) otherwise fall back to the module injector. if (bloomHash != null) { + const startInjectorIndex = getInjectorIndex(hostTNode, hostView); + let injectorIndex = startInjectorIndex; let injectorView = hostView; + let parentLocation: number = -1; - if (flags & InjectFlags.SkipSelf) { - const parentLocation = injectorView[injectorIndex + PARENT_INJECTOR]; - injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask; - injectorView = getParentInjectorView(parentLocation, injectorView); + // If we should skip this injector or if an injector doesn't exist on this node (e.g. all + // directives on this node are private), start by searching the parent injector. + if (flags & InjectFlags.SkipSelf || injectorIndex === -1) { + parentLocation = injectorIndex === -1 ? getParentInjectorLocation(hostTNode, hostView) : + injectorView[injectorIndex + PARENT_INJECTOR]; + + if (shouldNotSearchParent(flags, parentLocation)) { + injectorIndex = -1; + } else { + injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask; + injectorView = getParentInjectorView(parentLocation, injectorView); + } } while (injectorIndex !== -1) { @@ -348,9 +359,8 @@ export function getOrCreateInjectable( break; } - if (flags & InjectFlags.Self || - flags & InjectFlags.Host && - !sameHostView(injectorView[injectorIndex + PARENT_INJECTOR])) { + parentLocation = injectorView[injectorIndex + PARENT_INJECTOR]; + if (shouldNotSearchParent(flags, parentLocation)) { injectorIndex = -1; break; } @@ -359,7 +369,6 @@ export function getOrCreateInjectable( // up to find the specific injector. If the ancestor bloom filter does not have the bit, we // can abort. if (injectorHasToken(bloomHash, injectorIndex, injectorView)) { - const parentLocation = injectorView[injectorIndex + PARENT_INJECTOR]; injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask; injectorView = getParentInjectorView(parentLocation, injectorView); } else { @@ -390,7 +399,6 @@ export function getOrCreateInjectable( // The def wasn't found anywhere on this node, so it was a false positive. // Traverse up the tree and continue searching. - const parentLocation = injectorView[injectorIndex + PARENT_INJECTOR]; injectorIndex = parentLocation & InjectorLocationFlags.InjectorIndexMask; injectorView = getParentInjectorView(parentLocation, injectorView); } @@ -411,7 +419,7 @@ function searchMatchesQueuedForCreation(token: any, hostTView: TView): T|null for (let i = 0; i < matches.length; i += 2) { const def = matches[i] as DirectiveDef; if (def.type === token) { - return resolveDirective(def, i + 1, matches, hostTView); + return resolveDirective(def, i + 1, matches); } } } @@ -427,14 +435,14 @@ function searchDirectivesOnInjector( if (count !== 0) { const start = nodeFlags >> TNodeFlags.DirectiveStartingIndexShift; const end = start + count; - const defs = injectorView[TVIEW].directives !; + const defs = injectorView[TVIEW].data; for (let i = start; i < end; i++) { // Get the definition for the directive at this index and, if it is injectable (diPublic), // and matches the given token, return the directive instance. const directiveDef = defs[i] as DirectiveDef; if (directiveDef.type === token && directiveDef.diPublic) { - return injectorView[DIRECTIVES] ![i]; + return injectorView[i]; } } } @@ -486,15 +494,10 @@ export function injectorHasToken( return !!(value & mask); } - -/** - * Checks whether the current injector and its parent are in the same host view. - * - * This is necessary to support @Host() decorators. If @Host() is set, we should stop searching once - * the injector and its parent view don't match because it means we'd cross the view boundary. - */ -function sameHostView(parentLocation: number): boolean { - return !!parentLocation && (parentLocation >> InjectorLocationFlags.ViewOffsetShift) === 0; +/** Returns true if flags prevent parent injector from being searched for tokens */ +function shouldNotSearchParent(flags: InjectFlags, parentLocation: number): boolean|number { + return flags & InjectFlags.Self || + (flags & InjectFlags.Host && (parentLocation >> InjectorLocationFlags.ViewOffsetShift) > 0); } export class NodeInjector implements Injector { @@ -512,7 +515,7 @@ export class NodeInjector implements Injector { } setEnvironment(this._tNode, this._hostView); - return getOrCreateInjectable(this._injectorIndex, this._hostView, token); + return getOrCreateInjectable(this._tNode, this._hostView, token); } } export function getFactoryOf(type: Type): ((type?: Type) => T)|null { diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index 5b49b24b78..cd6a44015f 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -9,7 +9,7 @@ import {assertEqual} from './assert'; import {DirectiveDef} from './interfaces/definition'; import {TNodeFlags} from './interfaces/node'; -import {DIRECTIVES, FLAGS, HookData, LViewData, LViewFlags, TView} from './interfaces/view'; +import {FLAGS, HookData, LViewData, LViewFlags, TView} from './interfaces/view'; /** @@ -20,7 +20,7 @@ import {DIRECTIVES, FLAGS, HookData, LViewData, LViewFlags, TView} from './inter * directive index), then saved in the even indices of the initHooks array. The odd indices * hold the hook functions themselves. * - * @param index The index of the directive in LViewData[DIRECTIVES] + * @param index The index of the directive in LViewData * @param hooks The static hooks map on the directive def * @param tView The current TView */ @@ -52,7 +52,7 @@ export function queueLifecycleHooks(flags: number, tView: TView): void { // directiveCreate) so we can preserve the current hook order. Content, view, and destroy // hooks for projected components and directives must be called *before* their hosts. for (let i = start; i < end; i++) { - const def: DirectiveDef = tView.directives ![i]; + const def = tView.data[i] as DirectiveDef; queueContentHooks(def, tView, i); queueViewHooks(def, tView, i); queueDestroyHooks(def, tView, i); @@ -99,7 +99,7 @@ function queueDestroyHooks(def: DirectiveDef, tView: TView, i: number): voi export function executeInitHooks( currentView: LViewData, tView: TView, creationMode: boolean): void { if (currentView[FLAGS] & LViewFlags.RunInit) { - executeHooks(currentView[DIRECTIVES] !, tView.initHooks, tView.checkHooks, creationMode); + executeHooks(currentView, tView.initHooks, tView.checkHooks, creationMode); currentView[FLAGS] &= ~LViewFlags.RunInit; } } @@ -110,7 +110,7 @@ export function executeInitHooks( * @param currentView The current view */ export function executeHooks( - data: any[], allHooks: HookData | null, checkHooks: HookData | null, + data: LViewData, allHooks: HookData | null, checkHooks: HookData | null, creationMode: boolean): void { const hooksToCall = creationMode ? allHooks : checkHooks; if (hooksToCall) { @@ -125,8 +125,8 @@ export function executeHooks( * @param currentView The current view * @param arr The array in which the hooks are found */ -export function callHooks(data: any[], arr: HookData): void { +export function callHooks(currentView: any[], arr: HookData): void { for (let i = 0; i < arr.length; i += 2) { - (arr[i + 1] as() => void).call(data[arr[i] as number]); + (arr[i + 1] as() => void).call(currentView[arr[i] as number]); } } diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index d828d69e12..453409ca54 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -61,7 +61,6 @@ export { listener, store, load, - loadDirective, namespaceHTML, namespaceMathML, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 5a5034fbbe..72c933511f 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -18,12 +18,13 @@ import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComp import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; +import {INJECTOR_SIZE} from './interfaces/injector'; import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode} from './interfaces/node'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; import {StylingContext} from './interfaces/styling'; -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, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view'; +import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, RootContextFlags, SANITIZER, TAIL, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {appendChild, appendProjectedNode, createTextNode, findComponentView, getHostElementNode, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; @@ -42,15 +43,6 @@ const _CLEAN_PROMISE = Promise.resolve(null); */ export type SanitizerFn = (value: any) => string; -/** - * 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. - * - * It's much faster to keep a blueprint of the pre-filled array and slice it - * than it is to create a new array and fill it each time a TView is created. - */ -const HEADER_FILLER = new Array(HEADER_OFFSET).fill(null); - /** * Token set in currentMatches while dependencies are being resolved. * @@ -230,14 +222,6 @@ export function _getViewData(): LViewData { */ let contextViewData: LViewData = null !; -/** - * An array of directive instances in the current view. - * - * These must be stored separately from LNodes because their presence is - * unknown at compile-time and thus space cannot be reserved in data[]. - */ -let directives: any[]|null; - function getCleanup(view: LViewData): any[] { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return view[CLEANUP] || (view[CLEANUP] = []); @@ -259,7 +243,7 @@ let firstTemplatePass = true; /** * The root index from which pure function instructions should calculate their binding * indices. In component views, this is TView.bindingStartIndex. In a host binding - * context, this is the TView.hostBindingStartIndex + any hostVars before the given dir. + * context, this is the TView.expandoStartIndex + any dirs/hostVars before the given dir. */ let bindingRootIndex: number = -1; @@ -268,6 +252,9 @@ export function getBindingRoot() { return bindingRootIndex; } +// Root component will always have an element index of 0 and an injector size of 1 +const ROOT_EXPANDO_INSTRUCTIONS = [0, 1]; + const enum BindingDirection { Input, Output, @@ -288,7 +275,6 @@ const enum BindingDirection { export function enterView( newView: LViewData, hostTNode: TElementNode | TViewNode | null): LViewData { const oldView: LViewData = viewData; - directives = newView && newView[DIRECTIVES]; tView = newView && newView[TVIEW]; creationMode = newView && (newView[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode; @@ -317,7 +303,7 @@ export function enterView( export function leaveView(newView: LViewData, creationOnly?: boolean): void { if (!creationOnly) { if (!checkNoChangesMode) { - executeHooks(directives !, tView.viewHooks, tView.viewCheckHooks, creationMode); + executeHooks(viewData, tView.viewHooks, tView.viewCheckHooks, creationMode); } // Views are clean and in update mode after being checked, so these bits are cleared viewData[FLAGS] &= ~(LViewFlags.CreationMode | LViewFlags.Dirty); @@ -334,7 +320,7 @@ export function leaveView(newView: LViewData, creationOnly?: boolean): void { * Note: view hooks are triggered later when leaving the view. */ function refreshDescendantViews() { - setHostBindings(tView.hostBindings); + setHostBindings(); const parentFirstTemplatePass = firstTemplatePass; // This needs to be set before children are processed to support recursive components @@ -349,7 +335,7 @@ function refreshDescendantViews() { refreshContentQueries(tView); if (!checkNoChangesMode) { - executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode); + executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode); } refreshChildComponents(tView.components, parentFirstTemplatePass); @@ -357,21 +343,38 @@ function refreshDescendantViews() { /** Sets the host bindings for the current view. */ -export function setHostBindings(bindings: number[] | null): void { - if (bindings != null) { - bindingRootIndex = viewData[BINDING_INDEX] = tView.hostBindingStartIndex; - const defs = tView.directives !; - for (let i = 0; i < bindings.length; i += 2) { - const dirIndex = bindings[i]; - const def = defs[dirIndex] as DirectiveDef; - if (firstTemplatePass) { - for (let i = 0; i < def.hostVars; i++) { - tView.blueprint.push(NO_CHANGE); - viewData.push(NO_CHANGE); +export function setHostBindings(): void { + if (tView.expandoInstructions) { + bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex; + let currentDirectiveIndex = -1; + let currentElementIndex = -1; + for (let i = 0; i < tView.expandoInstructions.length; i++) { + const instruction = tView.expandoInstructions[i]; + if (typeof instruction === 'number') { + if (instruction <= 0) { + // Negative numbers mean that we are starting new EXPANDO block and need to update + // the current element and directive index. + currentElementIndex = -instruction; + if (typeof viewData[bindingRootIndex] === 'number') { + // We've hit an injector. It may or may not exist depending on whether + // there is a public directive on this node. + bindingRootIndex += INJECTOR_SIZE; + } + currentDirectiveIndex = bindingRootIndex; + } else { + // This is either the injector size (so the binding root can skip over directives + // and get to the first set of host bindings on this node) or the host var count + // (to get to the next set of host bindings on this node). + bindingRootIndex += instruction; } + } else { + // If it's not a number, it's a host binding function that needs to be executed. + viewData[BINDING_INDEX] = bindingRootIndex; + // We must subtract the header offset because the load() instruction + // expects a raw, unadjusted index. + instruction(currentDirectiveIndex - HEADER_OFFSET, currentElementIndex); + currentDirectiveIndex++; } - def.hostBindings !(dirIndex, bindings[i + 1]); - bindingRootIndex = viewData[BINDING_INDEX] = bindingRootIndex + def.hostVars; } } } @@ -381,9 +384,10 @@ function refreshContentQueries(tView: TView): void { if (tView.contentQueries != null) { for (let i = 0; i < tView.contentQueries.length; i += 2) { const directiveDefIdx = tView.contentQueries[i]; - const directiveDef = tView.directives ![directiveDefIdx]; + const directiveDef = tView.data[directiveDefIdx] as DirectiveDef; - directiveDef.contentQueriesRefresh !(directiveDefIdx, tView.contentQueries[i + 1]); + directiveDef.contentQueriesRefresh !( + directiveDefIdx - HEADER_OFFSET, tView.contentQueries[i + 1]); } } } @@ -401,7 +405,7 @@ function refreshChildComponents( export function executeInitAndContentHooks(): void { if (!checkNoChangesMode) { executeInitHooks(viewData, tView, creationMode); - executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode); + executeHooks(viewData, tView.contentHooks, tView.contentCheckHooks, creationMode); } } @@ -538,7 +542,7 @@ export function createNodeAtIndex( export function adjustBlueprintForNewNode(view: LViewData) { const tView = view[TVIEW]; if (tView.firstTemplatePass) { - tView.hostBindingStartIndex++; + tView.expandoStartIndex++; tView.blueprint.push(null); view.push(null); } @@ -658,10 +662,14 @@ export function renderEmbeddedTemplate( if (rf & RenderFlags.Update) { refreshDescendantViews(); } else { + // This must be set to false immediately after the first creation run because in an + // ngFor loop, all the views will be created together before update mode runs and turns + // off firstTemplatePass. If we don't set it here, instances will perform directive + // matching, etc again and again. viewToRender[TVIEW].firstTemplatePass = firstTemplatePass = false; } } finally { - // renderEmbeddedTemplate() is called twice in fact, once for creation only and then once for + // renderEmbeddedTemplate() is called twice, once for creation only and then once for // update. When for creation only, leaveView() must not trigger view hooks, nor clean flags. const isCreationOnly = (rf & RenderFlags.Create) === RenderFlags.Create; leaveView(oldView !, isCreationOnly); @@ -702,7 +710,7 @@ export function renderComponentOrTemplate( // Element was stored at 0 in data and directive was stored at 0 in directives // in renderComponent() - setHostBindings(tView.hostBindings); + setHostBindings(); componentRefresh(HEADER_OFFSET, false); } } finally { @@ -905,15 +913,45 @@ function cacheMatchingDirectivesForNode( // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in tsickle. const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null; const matches = tView.currentMatches = findDirectiveMatches(tNode); + generateExpandoBlock(tNode, matches); + let totalHostVars = 0; if (matches) { for (let i = 0; i < matches.length; i += 2) { const def = matches[i] as DirectiveDef; const valueIndex = i + 1; - resolveDirective(def, valueIndex, matches, tView); + resolveDirective(def, valueIndex, matches); + totalHostVars += def.hostVars; saveNameToExportMap(matches[valueIndex] as number, def, exportsMap); } } if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap); + prefillHostVars(totalHostVars); +} + +/** + * Generates a new block in TView.expandoInstructions for this node. + * + * Each expando block starts with the element index (turned negative so we can distinguish + * it from the hostVar count) and the directive count. See more in VIEW_DATA.md. + */ +function generateExpandoBlock(tNode: TNode, matches: CurrentMatchesList | null): void { + const directiveCount = matches ? matches.length / 2 : 0; + const elementIndex = -(tNode.index - HEADER_OFFSET); + (tView.expandoInstructions || (tView.expandoInstructions = [ + ])).push(elementIndex, directiveCount); +} + +/** + * On the first template pass, we need to reserve space for host binding values + * after directives are matched (so all directives are saved, then bindings). + * Because we are updating the blueprint, we only need to do this once. + */ +export function prefillHostVars(totalHostVars: number): void { + for (let i = 0; i < totalHostVars; i++) { + viewData.push(NO_CHANGE); + tView.blueprint.push(NO_CHANGE); + tView.data.push(null); + } } /** Matches the current node against all available selectors. */ @@ -925,17 +963,16 @@ function findDirectiveMatches(tNode: TNode): CurrentMatchesList|null { const def = registry[i]; if (isNodeMatchingSelectorList(tNode, def.selectors !)) { matches || (matches = []); + if (def.diPublic) def.diPublic(def); + if ((def as ComponentDef).template) { if (tNode.flags & TNodeFlags.isComponent) throwMultipleComponentError(tNode); addComponentLogic(def as ComponentDef); - tNode.flags = TNodeFlags.isComponent; - // The component is always stored first with directives after. matches.unshift(def, null); } else { matches.push(def, null); } - if (def.diPublic) def.diPublic(def); } } } @@ -943,12 +980,11 @@ function findDirectiveMatches(tNode: TNode): CurrentMatchesList|null { } export function resolveDirective( - def: DirectiveDef, valueIndex: number, matches: CurrentMatchesList, tView: TView): any { + def: DirectiveDef, valueIndex: number, matches: CurrentMatchesList): any { if (matches[valueIndex] === null) { matches[valueIndex] = CIRCULAR; const instance = def.factory(); - (tView.directives || (tView.directives = [])).push(def); - return directiveCreate(matches[valueIndex] = tView.directives !.length - 1, instance, def); + return directiveCreate(matches[valueIndex] = viewData.length, instance, def); } else if (matches[valueIndex] === CIRCULAR) { // If we revisit this directive before it's resolved, we know it's circular throwCyclicDependencyError(def.type); @@ -965,13 +1001,11 @@ function queueComponentIndexForCheck(): void { /** Stores index of directive and host element so it will be queued for binding refresh during CD. */ -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). +export function queueHostBindingForCheck( + dirIndex: number, def: DirectiveDef| ComponentDef): void { ngDevMode && assertEqual(firstTemplatePass, true, 'Should only be called in first template pass.'); - (tView.hostBindings || (tView.hostBindings = [ - ])).push(dirIndex, previousOrParentTNode.index - HEADER_OFFSET); + tView.expandoInstructions !.push(def.hostBindings !, def.hostVars); } /** @@ -990,10 +1024,9 @@ function instantiateDirectivesDirectly() { if (count > 0) { const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift; const end = start + count; - const tDirectives = tView.directives !; for (let i = start; i < end; i++) { - const def: DirectiveDef = tDirectives[i]; + const def = tView.data[i] as DirectiveDef| ComponentDef; // Component view must be set on node before the factory is created so // ChangeDetectorRefs have a way to store component view on creation. @@ -1046,7 +1079,7 @@ function saveResolvedLocalsInData(localRefExtractor: LocalRefExtractor): void { let localIndex = previousOrParentTNode.index + 1; for (let i = 0; i < localNames.length; i += 2) { const index = localNames[i + 1] as number; - const value = index === -1 ? localRefExtractor(tNode, viewData) : directives ![index]; + const value = index === -1 ? localRefExtractor(tNode, viewData) : viewData[index]; viewData[localIndex++] = value; } } @@ -1108,8 +1141,9 @@ export function createTView( data: blueprint.slice(), // Fill in to match HEADER_OFFSET in LViewData childIndex: -1, // Children set in addToViewTree(), if any bindingStartIndex: bindingStartIndex, - hostBindingStartIndex: initialViewLength, + expandoStartIndex: initialViewLength, directives: null, + expandoInstructions: null, firstTemplatePass: true, initHooks: null, checkHooks: null, @@ -1120,7 +1154,6 @@ export function createTView( destroyHooks: null, pipeDestroyHooks: null, cleanup: null, - hostBindings: null, contentQueries: null, components: null, directiveRegistry: typeof directives === 'function' ? directives() : directives, @@ -1224,9 +1257,10 @@ export function hostElement( null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer)); if (firstTemplatePass) { - tNode.flags = TNodeFlags.isComponent; + tView.expandoInstructions = ROOT_EXPANDO_INSTRUCTIONS.slice(); if (def.diPublic) def.diPublic(def); - tView.directives = [def]; + tNode.flags = + viewData.length << TNodeFlags.DirectiveStartingIndexShift | TNodeFlags.isComponent; } return viewData[HEADER_OFFSET]; } @@ -1289,8 +1323,8 @@ export function listener( */ function createOutput(outputs: PropertyAliasValue, listener: Function): void { for (let i = 0; i < outputs.length; i += 2) { - ngDevMode && assertDataInRange(outputs[i] as number, directives !); - const subscription = directives ![outputs[i] as number][outputs[i + 1]].subscribe(listener); + ngDevMode && assertDataInRange(outputs[i] as number, viewData); + const subscription = viewData[outputs[i] as number][outputs[i + 1]].subscribe(listener); storeCleanupWithContext(viewData, subscription, subscription.unsubscribe); } } @@ -1498,8 +1532,8 @@ export function createTNode( */ function setInputsForProperty(inputs: PropertyAliasValue, value: any): void { for (let i = 0; i < inputs.length; i += 2) { - ngDevMode && assertDataInRange(inputs[i] as number, directives !); - directives ![inputs[i] as number][inputs[i + 1]] = value; + ngDevMode && assertDataInRange(inputs[i] as number, viewData); + viewData[inputs[i] as number][inputs[i + 1]] = value; } } @@ -1519,7 +1553,7 @@ function generatePropertyAliases( const start = tNodeFlags >> TNodeFlags.DirectiveStartingIndexShift; const end = start + count; const isInput = direction === BindingDirection.Input; - const defs = tView.directives !; + const defs = tView.data; for (let i = start; i < end; i++) { const directiveDef = defs[i] as DirectiveDef; @@ -1775,8 +1809,6 @@ export function directiveCreate( // Init hooks are queued now so ngOnInit is called in host components before // any projected components. queueInitHooks(directiveDefIdx, directiveDef.onInit, directiveDef.doCheck, tView); - - if (directiveDef.hostBindings) queueHostBindingForCheck(directiveDefIdx, directiveDef.hostVars); } ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode'); @@ -1810,7 +1842,11 @@ function addComponentLogic(def: ComponentDef): void { (hostNode as{data: LViewData}).data = componentView; (componentView as LViewData)[HOST_NODE] = previousOrParentTNode as TElementNode; - if (firstTemplatePass) queueComponentIndexForCheck(); + if (firstTemplatePass) { + queueComponentIndexForCheck(); + previousOrParentTNode.flags = + viewData.length << TNodeFlags.DirectiveStartingIndexShift | TNodeFlags.isComponent; + } } /** @@ -1832,14 +1868,11 @@ export function baseDirectiveCreate( attachPatchData(hostNode.native, viewData); } - if (directives == null) viewData[DIRECTIVES] = directives = []; - - ngDevMode && assertDataNext(index, directives); - directives[index] = directive; + viewData[index] = directive; if (firstTemplatePass) { const flags = previousOrParentTNode.flags; - if ((flags & TNodeFlags.DirectiveCountMask) === 0) { + if (flags === 0) { // When the first directive is created: // - save the index, // - set the number of directives to 1 @@ -1852,6 +1885,10 @@ export function baseDirectiveCreate( 'Reached the max number of directives'); previousOrParentTNode.flags++; } + + tView.data.push(directiveDef); + tView.blueprint.push(null); + if (directiveDef.hostBindings) queueHostBindingForCheck(index, directiveDef); } else { const diPublic = directiveDef !.diPublic; if (diPublic) diPublic(directiveDef !); @@ -2828,13 +2865,6 @@ function walkUpViews(nestingLevel: number, currentView: LViewData): LViewData { return currentView; } -/** Retrieves a value from the `directives` array. */ -export function loadDirective(index: number): T { - ngDevMode && assertDefined(directives, 'Directives array should be defined if reading a dir.'); - ngDevMode && assertDataInRange(index, directives !); - return directives ![index]; -} - export function loadQueryList(queryListIdx: number): QueryList { ngDevMode && assertDefined( viewData[CONTENT_QUERIES], @@ -2918,7 +2948,7 @@ export function registerContentQuery(queryList: QueryList): void { const savedContentQueriesLength = (viewData[CONTENT_QUERIES] || (viewData[CONTENT_QUERIES] = [])).push(queryList); if (firstTemplatePass) { - const currentDirectiveIndex = directives !.length - 1; + const currentDirectiveIndex = viewData.length - 1; const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []); const lastSavedDirectiveIndex = tView.contentQueries.length ? tView.contentQueries[tView.contentQueries.length - 2] : -1; diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 91d7ec568a..a061291397 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -145,7 +145,7 @@ export interface DirectiveDef extends BaseDef { hostVars: number; /** Refreshes host bindings on the associated directive. */ - hostBindings: ((directiveIndex: number, elementIndex: number) => void)|null; + hostBindings: HostBindingsFunction|null; /** * Static attributes to set on host element. @@ -330,6 +330,8 @@ export type DirectiveTypeList = (DirectiveDef| ComponentDef| Type/* Type as workaround for: Microsoft/TypeScript/issues/4881 */)[]; +export type HostBindingsFunction = (directiveIndex: number, elementIndex: number) => void; + /** * Type used for PipeDefs on component definition. * diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 3c3409f8d0..c1339379a9 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -12,13 +12,13 @@ import {Sanitizer} from '../../sanitization/security'; import {PlayerHandler} from '../interfaces/player'; import {LContainer} from './container'; -import {ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDef, PipeDefList} from './definition'; -import {LElementNode, LViewNode, TElementNode, TNode, TViewNode} from './node'; +import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList} from './definition'; +import {TElementNode, TNode, TViewNode} from './node'; import {LQueries} from './query'; import {Renderer3} from './renderer'; /** Size of LViewData's header. Necessary to adjust for it when setting slots. */ -export const HEADER_OFFSET = 17; +export const HEADER_OFFSET = 16; // Below are constants for LViewData indices to help us look up LViewData members // without having to remember the specific indices. @@ -30,16 +30,15 @@ export const QUERIES = 3; export const FLAGS = 4; export const HOST_NODE = 5; export const BINDING_INDEX = 6; -export const DIRECTIVES = 7; -export const CLEANUP = 8; -export const CONTEXT = 9; -export const INJECTOR = 10; -export const RENDERER = 11; -export const SANITIZER = 12; -export const TAIL = 13; -export const CONTAINER_INDEX = 14; -export const CONTENT_QUERIES = 15; -export const DECLARATION_VIEW = 16; +export const CLEANUP = 7; +export const CONTEXT = 8; +export const INJECTOR = 9; +export const RENDERER = 10; +export const SANITIZER = 11; +export const TAIL = 12; +export const CONTAINER_INDEX = 13; +export const CONTENT_QUERIES = 14; +export const DECLARATION_VIEW = 15; // This interface replaces the real LViewData interface if it is an arg or a // return value of a public instruction. This ensures we don't need to expose @@ -115,15 +114,6 @@ export interface LViewData extends Array { */ [BINDING_INDEX]: number; - /** - * An array of directive instances in the current view. - * - * These must be stored separately from LNodes because their presence is - * unknown at compile-time and thus space cannot be reserved in data[]. - */ - // TODO: flatten into LViewData[] - [DIRECTIVES]: any[]|null; - /** * When a view is destroyed, listeners need to be released and outputs need to be * unsubscribed. This context array stores both listener functions wrapped with @@ -307,11 +297,16 @@ export interface TView { bindingStartIndex: number; /** - * The index at which the data array begins to store host bindings for components - * or directives in its template. Saving this value ensures that we can set the - * binding root and binding index correctly before checking host bindings. + * The index where the "expando" section of `LViewData` begins. The expando + * section contains injectors, directive instances, and host binding values. + * Unlike the "consts" and "vars" sections of `LViewData`, the length of this + * section cannot be calculated at compile-time because directives are matched + * at runtime to preserve locality. + * + * We store this start index so we know where to start checking host bindings + * in `setHostBindings`. */ - hostBindingStartIndex: number; + expandoStartIndex: number; /** * Index of the host node of the first LView or LContainer beneath this LView in @@ -357,6 +352,13 @@ export interface TView { */ directives: DirectiveDefList|null; + /** + * Set of instructions used to process host bindings efficiently. + * + * See VIEW_DATA.md for more information. + */ + expandoInstructions: (number|HostBindingsFunction)[]|null; + /** * Full registry of directives and components that may be found in this view. * @@ -479,18 +481,6 @@ export interface TView { */ components: number[]|null; - /** - * A list of indices for child directives that have host bindings. - * - * Even indices: Directive indices - * Odd indices: Element indices - * - * Element indices are NOT adjusted for LViewData header offset because - * they will be fed into instructions that expect the raw index (e.g. elementProperty) - */ - hostBindings: number[]|null; - - /** * A list of indices for child directives that have content queries. * @@ -558,7 +548,7 @@ export type HookData = (number | (() => void))[]; * * Injector bloom filters are also stored here. */ -export type TData = (TNode | PipeDef| number | null)[]; +export type TData = (TNode | PipeDef| DirectiveDef| ComponentDef| number | null)[]; /** Type for TView.currentMatches */ export type CurrentMatchesList = [DirectiveDef, (string | number | null)]; diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 37db8319a1..3c1b88d01f 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -41,7 +41,6 @@ export const angularCoreEnv: {[name: string]: Function} = { 'ɵnextContext': r3.nextContext, 'ɵcontainerRefreshStart': r3.containerRefreshStart, 'ɵcontainerRefreshEnd': r3.containerRefreshEnd, - 'ɵloadDirective': r3.loadDirective, 'ɵloadQueryList': r3.loadQueryList, 'ɵnamespaceHTML': r3.namespaceHTML, 'ɵnamespaceMathML': r3.namespaceMathML, diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 2e9f432b41..607cc163ab 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -13,7 +13,7 @@ import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unuse import {LContainerNode, LElementContainerNode, LElementNode, LTextNode, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; -import {CLEANUP, CONTAINER_INDEX, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; +import {CLEANUP, CONTAINER_INDEX, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {assertNodeType} from './node_assert'; import {getLNode, stringify} from './util'; @@ -499,7 +499,7 @@ function executeOnDestroys(view: LViewData): void { const tView = view[TVIEW]; let destroyHooks: HookData|null; if (tView != null && (destroyHooks = tView.destroyHooks) != null) { - callHooks(view[DIRECTIVES] !, destroyHooks); + callHooks(view, destroyHooks); } } diff --git a/packages/core/src/render3/pure_function.ts b/packages/core/src/render3/pure_function.ts index a445da06b4..3e5bd288c4 100644 --- a/packages/core/src/render3/pure_function.ts +++ b/packages/core/src/render3/pure_function.ts @@ -11,17 +11,19 @@ import {bindingUpdated, bindingUpdated2, bindingUpdated4, updateBinding, getBind /** * Bindings for pure functions are stored after regular bindings. * - * |--------consts--------|----------------vars----------------|------ hostVars (dir1) ------| - * --------------------------------------------------------------------------------------------- - * | nodes / refs / pipes | bindings | pure function bindings | host bindings | host slots | - * --------------------------------------------------------------------------------------------- - * ^ ^ - * TView.bindingStartIndex TView.hostBindingStartIndex + * |------consts------|---------vars---------| |----- hostVars (dir1) ------| + * ------------------------------------------------------------------------------------------ + * | nodes/refs/pipes | bindings | fn slots | injector | dir1 | host bindings | host slots | + * ------------------------------------------------------------------------------------------ + * ^ ^ + * TView.bindingStartIndex TView.expandoStartIndex * * Pure function instructions are given an offset from the binding root. Adding the offset to the * binding root gives the first index where the bindings are stored. In component views, the binding - * root is the bindingStartIndex. In host bindings, the binding root is the hostBindingStartIndex + - * any hostVars in directives evaluated before it. + * root is the bindingStartIndex. In host bindings, the binding root is the expandoStartIndex + + * any directive instances + any hostVars in directives evaluated before it. + * + * See VIEW_DATA.md for more information about host binding resolution. */ /** diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 647e0196ea..921642a88c 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -24,7 +24,7 @@ import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfac import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {LQueries, QueryReadType, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; -import {DIRECTIVES, LViewData, TVIEW} from './interfaces/view'; +import {LViewData, TVIEW} from './interfaces/view'; import {flatten, isContentQueryHost} from './util'; import {createElementRef, createTemplateRef} from './view_engine_compatibility'; @@ -251,7 +251,7 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null { */ function getIdxOfMatchingDirective(tNode: TNode, currentView: LViewData, type: Type): number| null { - const defs = currentView[TVIEW].directives; + const defs = currentView[TVIEW].data; if (defs) { const flags = tNode.flags; const count = flags & TNodeFlags.DirectiveCountMask; @@ -275,7 +275,7 @@ function queryRead(tNode: TNode, currentView: LViewData, read: any): any { } else { const matchingIdx = getIdxOfMatchingDirective(tNode, currentView, read as Type); if (matchingIdx !== null) { - return currentView[DIRECTIVES] ![matchingIdx]; + return currentView[matchingIdx]; } } return null; @@ -314,7 +314,7 @@ function add( result = queryRead(tNode, currentView, predicate.read); } else { if (directiveIdx > -1) { - result = currentView[DIRECTIVES] ![directiveIdx]; + result = currentView[directiveIdx]; } else { // if read token and / or strategy is not specified, // detect it using appropriate tNode type diff --git a/packages/core/src/render3/view_engine_compatibility_prebound.ts b/packages/core/src/render3/view_engine_compatibility_prebound.ts index 79b30ab9be..56fe280f16 100644 --- a/packages/core/src/render3/view_engine_compatibility_prebound.ts +++ b/packages/core/src/render3/view_engine_compatibility_prebound.ts @@ -22,4 +22,4 @@ import {createTemplateRef} from './view_engine_compatibility'; */ export function templateRefExtractor(tNode: TNode, currentView: LViewData) { return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, currentView); -} \ No newline at end of file +} diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index f43247d074..0c9f38da10 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -13,7 +13,7 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, InternalViewRef as viewEn import {checkNoChanges, checkNoChangesInRootView, detectChanges, detectChangesInRootView, getRendererFactory, markViewDirty, storeCleanupFn, viewAttached} from './instructions'; import {TViewNode} from './interfaces/node'; -import {DIRECTIVES, FLAGS, LViewData, LViewFlags, PARENT} from './interfaces/view'; +import {FLAGS, LViewData, LViewFlags, PARENT} from './interfaces/view'; import {destroyLView} from './node_manipulation'; @@ -256,7 +256,7 @@ export class ViewRef implements viewEngine_EmbeddedViewRef, viewEngine_Int attachToAppRef(appRef: ApplicationRef) { this._appRef = appRef; } private _lookUpContext(): T { - return this._context = this._view[PARENT] ![DIRECTIVES] ![this._componentIndex] as T; + return this._context = this._view[PARENT] ![this._componentIndex] as T; } } diff --git a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json index 139fa30090..d4619211a1 100644 --- a/packages/core/test/bundling/animation_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animation_world/bundle.golden_symbols.json @@ -38,9 +38,6 @@ { "name": "DECLARATION_VIEW" }, - { - "name": "DIRECTIVES" - }, { "name": "DefaultIterableDiffer" }, @@ -164,6 +161,9 @@ { "name": "RENDER_PARENT" }, + { + "name": "ROOT_EXPANDO_INSTRUCTIONS" + }, { "name": "RecordViewTuple" }, @@ -527,6 +527,9 @@ { "name": "firstTemplatePass" }, + { + "name": "generateExpandoBlock" + }, { "name": "generateInitialInputs" }, @@ -833,6 +836,9 @@ { "name": "pointers" }, + { + "name": "prefillHostVars" + }, { "name": "prepareInitialFlag" }, @@ -905,9 +911,6 @@ { "name": "resolveDirective" }, - { - "name": "sameHostView" - }, { "name": "saveNameToExportMap" }, @@ -968,6 +971,9 @@ { "name": "setValue" }, + { + "name": "shouldNotSearchParent" + }, { "name": "storeCleanupFn" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index a664681e74..8a62ae6139 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -23,9 +23,6 @@ { "name": "DECLARATION_VIEW" }, - { - "name": "DIRECTIVES" - }, { "name": "EMPTY$1" }, @@ -95,6 +92,9 @@ { "name": "RENDER_PARENT" }, + { + "name": "ROOT_EXPANDO_INSTRUCTIONS" + }, { "name": "SANITIZER" }, @@ -302,6 +302,9 @@ { "name": "nextNgElementId" }, + { + "name": "prefillHostVars" + }, { "name": "queueHostBindingForCheck" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index e7b5340098..34f8d0de75 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -29,9 +29,6 @@ { "name": "DECLARATION_VIEW" }, - { - "name": "DIRECTIVES" - }, { "name": "DefaultIterableDiffer" }, @@ -155,6 +152,9 @@ { "name": "RENDER_PARENT" }, + { + "name": "ROOT_EXPANDO_INSTRUCTIONS" + }, { "name": "RecordViewTuple" }, @@ -578,6 +578,9 @@ { "name": "firstTemplatePass" }, + { + "name": "generateExpandoBlock" + }, { "name": "generateInitialInputs" }, @@ -854,6 +857,9 @@ { "name": "pointers" }, + { + "name": "prefillHostVars" + }, { "name": "prepareInitialFlag" }, @@ -932,9 +938,6 @@ { "name": "restoreView" }, - { - "name": "sameHostView" - }, { "name": "saveNameToExportMap" }, @@ -992,6 +995,9 @@ { "name": "setValue" }, + { + "name": "shouldNotSearchParent" + }, { "name": "storeCleanupFn" }, diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 61bfc73faa..b5ce8934da 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -191,9 +191,6 @@ { "name": "DIGIT_CHAR" }, - { - "name": "DIRECTIVES" - }, { "name": "DOCUMENT" }, @@ -692,6 +689,9 @@ { "name": "ROOT_CONTEXT" }, + { + "name": "ROOT_EXPANDO_INSTRUCTIONS" + }, { "name": "RecordViewTuple" }, @@ -1577,6 +1577,9 @@ { "name": "fromPromise" }, + { + "name": "generateExpandoBlock" + }, { "name": "generateInitialInputs" }, @@ -2165,6 +2168,9 @@ { "name": "pointers" }, + { + "name": "prefillHostVars" + }, { "name": "prepareInitialFlag" }, @@ -2273,9 +2279,6 @@ { "name": "rxSubscriber" }, - { - "name": "sameHostView" - }, { "name": "sanitizeSrcset" }, @@ -2357,6 +2360,9 @@ { "name": "shimHostAttribute" }, + { + "name": "shouldNotSearchParent" + }, { "name": "staticError" }, diff --git a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts index c6ce150465..ec73b8b15f 100644 --- a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts +++ b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts @@ -105,7 +105,7 @@ describe('components & directives', () => { hostVars: 1, hostBindings: function HostBindingDir_HostBindings(dirIndex: $number$, elIndex: $number$) { $r3$.ɵelementProperty( - elIndex, 'id', $r3$.ɵbind($r3$.ɵloadDirective(dirIndex).dirId)); + elIndex, 'id', $r3$.ɵbind($r3$.ɵload(dirIndex).dirId)); } }); // /NORMATIVE @@ -256,8 +256,7 @@ describe('components & directives', () => { hostVars: 1, hostBindings: function HostBindingDir_HostBindings(dirIndex: $number$, elIndex: $number$) { $r3$.ɵelementAttribute( - elIndex, 'aria-label', - $r3$.ɵbind($r3$.ɵloadDirective(dirIndex).label)); + elIndex, 'aria-label', $r3$.ɵbind($r3$.ɵload(dirIndex).label)); } }); // /NORMATIVE @@ -752,7 +751,7 @@ describe('components & directives', () => { selectors: [['my-app']], factory: function MyApp_Factory() { return new MyApp(); }, consts: 1, - vars: 10, + vars: 11, template: function MyApp_Template(rf: $RenderFlags$, c: $any$) { if (rf & 1) { $r3$.ɵelement(0, 'my-comp'); diff --git a/packages/core/test/render3/compiler_canonical/query_spec.ts b/packages/core/test/render3/compiler_canonical/query_spec.ts index 92065e04e5..6af35bf765 100644 --- a/packages/core/test/render3/compiler_canonical/query_spec.ts +++ b/packages/core/test/render3/compiler_canonical/query_spec.ts @@ -121,7 +121,7 @@ describe('queries', () => { contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh( dirIndex: $number$, queryStartIndex: $number$) { let $tmp$: any; - const $instance$ = $r3$.ɵloadDirective(dirIndex); + const $instance$ = $r3$.ɵload(dirIndex); $r3$.ɵqueryRefresh($tmp$ = $r3$.ɵloadQueryList(queryStartIndex)) && ($instance$.someDir = $tmp$.first); $r3$.ɵqueryRefresh($tmp$ = $r3$.ɵloadQueryList(queryStartIndex + 1)) && diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 98f4d293d8..d763f98673 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -10,10 +10,10 @@ import {Attribute, ChangeDetectorRef, ElementRef, Host, InjectFlags, Injector, O import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; -import {bloomAdd, bloomHashBitOrFactory as bloomHash, getOrCreateInjectable, getOrCreateNodeInjector, injectAttribute, injectorHasToken} from '../../src/render3/di'; +import {bloomAdd, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjector, injectAttribute, injectorHasToken} from '../../src/render3/di'; import {PublicFeature, defineDirective, directiveInject, elementProperty, injectRenderer2, load, templateRefExtractor} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, projection, projectionDef, reference, template, text, textBinding, loadDirective, elementContainerStart, elementContainerEnd, _getViewData, getTNode} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions'; import {isProceduralRenderer} from '../../src/render3/interfaces/renderer'; import {AttributeMarker, LContainerNode, LElementNode, TNodeType} from '../../src/render3/interfaces/node'; @@ -677,8 +677,7 @@ describe('di', () => { factory: () => hostBindingDir = new HostBindingDir(), hostVars: 1, hostBindings: (directiveIndex: number, elementIndex: number) => { - elementProperty( - elementIndex, 'id', bind(loadDirective(directiveIndex).id)); + elementProperty(elementIndex, 'id', bind(load(directiveIndex).id)); } }); } @@ -1298,7 +1297,8 @@ describe('di', () => { projectionDef(); projection(0); } - } + }, + features: [PublicFeature] }); } @@ -1323,7 +1323,8 @@ describe('di', () => { type: DirectiveSameInstance, selectors: [['', 'dirSame', '']], factory: () => dirSameInstance = - new DirectiveSameInstance(directiveInject(ChangeDetectorRef as any)) + new DirectiveSameInstance(directiveInject(ChangeDetectorRef as any)), + features: [PublicFeature] }); } @@ -1419,7 +1420,8 @@ describe('di', () => { textBinding(3, bind(tmp.value)); } }, - directives: directives + directives: directives, + features: [PublicFeature] }); } @@ -1563,6 +1565,7 @@ describe('di', () => { }); describe('@Attribute', () => { + let myDirectiveInstance !: MyDirective | null; class MyDirective { exists = 'wrong' as string | undefined; @@ -1577,10 +1580,13 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ type: MyDirective, selectors: [['', 'myDirective', '']], - factory: () => new MyDirective(injectAttribute('exist'), injectAttribute('myDirective')) + factory: () => myDirectiveInstance = + new MyDirective(injectAttribute('exist'), injectAttribute('myDirective')) }); } + beforeEach(() => myDirectiveInstance = null); + it('should inject attribute', () => { let exist = 'wrong' as string | undefined; let nonExist = 'wrong' as string | undefined; @@ -1843,7 +1849,7 @@ describe('di', () => { (parentTNode as{parent: any}).parent = undefined; const injector: any = getOrCreateNodeInjector(); // TODO: Review use of `any` here (#19904) - expect(injector).not.toBe(null); + expect(injector).not.toEqual(-1); } finally { leaveView(oldView); } diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index e16eb92630..2a4e73e088 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -11,10 +11,10 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {RendererStyleFlags2, RendererType2} from '../../src/render/api'; import {AttributeMarker, defineComponent, defineDirective} from '../../src/render3/index'; -import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, enableBindings, disableBindings, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, listener, load, loadDirective, projection, projectionDef, reference, text, textBinding, template} from '../../src/render3/instructions'; +import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, enableBindings, disableBindings, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template} from '../../src/render3/instructions'; import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3, RText, RComment, RNode, RendererStyleFlags3, ProceduralRenderer3} from '../../src/render3/interfaces/renderer'; -import {HEADER_OFFSET, CONTEXT, DIRECTIVES} from '../../src/render3/interfaces/view'; +import {HEADER_OFFSET, CONTEXT} from '../../src/render3/interfaces/view'; import {sanitizeUrl} from '../../src/sanitization/sanitization'; import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; @@ -449,8 +449,7 @@ describe('render3 integration test', () => { hostBindings: function(directiveIndex: number, elementIndex: number): void { // host bindings elementProperty( - elementIndex, 'title', - bind(loadDirective(directiveIndex).title)); + elementIndex, 'title', bind(load(directiveIndex).title)); } }); } @@ -750,7 +749,7 @@ describe('render3 integration test', () => { type: TestDirective, selectors: [['', 'testDirective', '']], factory: - () => new TestDirective( + () => testDirective = new TestDirective( directiveInject(TemplateRef as any), directiveInject(ViewContainerRef as any)), }); } @@ -777,9 +776,6 @@ describe('render3 integration test', () => { template( 0, embeddedTemplate, 2, 0, null, [AttributeMarker.SelectOnly, 'testDirective']); } - if (rf & RenderFlags.Update) { - testDirective = loadDirective(0); - } }, 1, 0, [TestDirective]); const fixture = new ComponentFixture(TestCmpt); @@ -845,6 +841,7 @@ describe('render3 integration test', () => { }); it('should render inside another ng-container at the root of a delayed view', () => { + let testDirective: TestDirective; class TestDirective { constructor(private _tplRef: TemplateRef, private _vcRef: ViewContainerRef) {} @@ -857,12 +854,11 @@ describe('render3 integration test', () => { type: TestDirective, selectors: [['', 'testDirective', '']], factory: - () => new TestDirective( + () => testDirective = new TestDirective( directiveInject(TemplateRef as any), directiveInject(ViewContainerRef as any)), }); } - let testDirective: TestDirective; function embeddedTemplate(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { @@ -895,9 +891,6 @@ describe('render3 integration test', () => { if (rf & RenderFlags.Create) { template(0, embeddedTemplate, 4, 0, null, [AttributeMarker.SelectOnly, 'testDirective']); } - if (rf & RenderFlags.Update) { - testDirective = loadDirective(0); - } }, 1, 0, [TestDirective]); function App() { element(0, 'test-cmpt'); } @@ -926,7 +919,7 @@ describe('render3 integration test', () => { static ngDirectiveDef = defineDirective({ type: Directive, selectors: [['', 'dir', '']], - factory: () => new Directive(directiveInject(ElementRef)), + factory: () => directive = new Directive(directiveInject(ElementRef)), }); } @@ -940,7 +933,6 @@ describe('render3 integration test', () => { { elementContainerStart(1, [AttributeMarker.SelectOnly, 'dir']); elementContainerEnd(); - directive = loadDirective(0); } elementEnd(); } @@ -1277,8 +1269,7 @@ describe('render3 integration test', () => { }, hostVars: 1, hostBindings: function HostBindingDir_HostBindings(dirIndex: number, elIndex: number) { - elementAttribute( - elIndex, 'aria-label', bind(loadDirective(dirIndex).label)); + elementAttribute(elIndex, 'aria-label', bind(load(dirIndex).label)); } }); } @@ -2112,11 +2103,10 @@ describe('render3 integration test', () => { const context = getContext(hostElm) !; const elementNode = context.lViewData[context.nodeIndex]; const elmData = elementNode.data !; - const dirs = elmData[DIRECTIVES]; - expect(dirs).toContain(myDir1Instance); - expect(dirs).toContain(myDir2Instance); - expect(dirs).toContain(myDir3Instance); + expect(elmData).toContain(myDir1Instance); + expect(elmData).toContain(myDir2Instance); + expect(elmData).toContain(myDir3Instance); expect(Array.isArray((myDir1Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy(); expect(Array.isArray((myDir2Instance as any)[MONKEY_PATCH_KEY_NAME])).toBeTruthy(); diff --git a/packages/core/test/render3/properties_spec.ts b/packages/core/test/render3/properties_spec.ts index 5ffbff23c7..dc46b317ef 100644 --- a/packages/core/test/render3/properties_spec.ts +++ b/packages/core/test/render3/properties_spec.ts @@ -8,12 +8,13 @@ import {EventEmitter} from '@angular/core'; -import {AttributeMarker, PublicFeature, defineComponent, defineDirective} from '../../src/render3/index'; -import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, loadDirective, reference, text, textBinding} from '../../src/render3/instructions'; +import {AttributeMarker, PublicFeature, defineComponent, template, defineDirective} from '../../src/render3/index'; +import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, load, reference, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {pureFunction1, pureFunction2} from '../../src/render3/pure_function'; -import {ComponentFixture, TemplateFixture, createComponent, renderToHtml} from './render_util'; +import {ComponentFixture, TemplateFixture, createComponent, renderToHtml, createDirective} from './render_util'; +import {NgForOf} from './common_with_def'; describe('elementProperty', () => { @@ -109,8 +110,7 @@ describe('elementProperty', () => { factory: () => directiveInstance = new Directive, hostVars: 1, hostBindings: (directiveIndex: number, elementIndex: number) => { - elementProperty( - elementIndex, 'className', bind(loadDirective(directiveIndex).klass)); + elementProperty(elementIndex, 'className', bind(load(directiveIndex).klass)); } }); } @@ -138,7 +138,7 @@ describe('elementProperty', () => { vars: 0, hostVars: 1, hostBindings: (dirIndex: number, elIndex: number) => { - const instance = loadDirective(dirIndex) as HostBindingComp; + const instance = load(dirIndex) as HostBindingComp; elementProperty(elIndex, 'id', bind(instance.id)); }, template: (rf: RenderFlags, ctx: HostBindingComp) => {} @@ -153,6 +153,71 @@ describe('elementProperty', () => { expect(fixture.hostElement.id).toBe('other-id'); }); + it('should support host bindings on multiple nodes', () => { + let hostBindingDir !: HostBindingDir; + + class HostBindingDir { + // @HostBinding() + id = 'foo'; + + static ngDirectiveDef = defineDirective({ + type: HostBindingDir, + selectors: [['', 'hostBindingDir', '']], + factory: () => hostBindingDir = new HostBindingDir(), + hostVars: 1, + hostBindings: (directiveIndex: number, elementIndex: number) => { + elementProperty(elementIndex, 'id', bind(load(directiveIndex).id)); + }, + features: [PublicFeature] + }); + } + + const SomeDir = createDirective('someDir'); + + class HostBindingComp { + // @HostBinding() + title = 'my-title'; + + static ngComponentDef = defineComponent({ + type: HostBindingComp, + selectors: [['host-binding-comp']], + factory: () => new HostBindingComp(), + consts: 0, + vars: 0, + hostVars: 1, + hostBindings: (dirIndex: number, elIndex: number) => { + const ctx = load(dirIndex) as HostBindingComp; + elementProperty(elIndex, 'title', bind(ctx.title)); + }, + template: (rf: RenderFlags, ctx: HostBindingComp) => {}, + features: [PublicFeature] + }); + } + + /** + *
+ *
+ * + */ + const App = createComponent('app', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + element(0, 'div', ['hostBindingDir', '']); + element(1, 'div', ['someDir', '']); + element(2, 'host-binding-comp'); + } + }, 3, 0, [HostBindingDir, SomeDir, HostBindingComp]); + + const fixture = new ComponentFixture(App); + const hostBindingDiv = fixture.hostElement.querySelector('div') as HTMLElement; + const hostBindingComp = fixture.hostElement.querySelector('host-binding-comp') as HTMLElement; + expect(hostBindingDiv.id).toEqual('foo'); + expect(hostBindingComp.title).toEqual('my-title'); + + hostBindingDir.id = 'bar'; + fixture.update(); + expect(hostBindingDiv.id).toEqual('bar'); + }); + it('should support host bindings on second template pass', () => { class HostBindingDir { // @HostBinding() @@ -164,8 +229,7 @@ describe('elementProperty', () => { factory: () => new HostBindingDir(), hostVars: 1, hostBindings: (directiveIndex: number, elementIndex: number) => { - elementProperty( - elementIndex, 'id', bind(loadDirective(directiveIndex).id)); + elementProperty(elementIndex, 'id', bind(load(directiveIndex).id)); }, features: [PublicFeature] }); @@ -195,6 +259,55 @@ describe('elementProperty', () => { expect(divs[1].id).toEqual('foo'); }); + it('should support host bindings in for loop', () => { + class HostBindingDir { + // @HostBinding() + id = 'foo'; + + static ngDirectiveDef = defineDirective({ + type: HostBindingDir, + selectors: [['', 'hostBindingDir', '']], + factory: () => new HostBindingDir(), + hostVars: 1, + hostBindings: (directiveIndex: number, elementIndex: number) => { + elementProperty(elementIndex, 'id', bind(load(directiveIndex).id)); + }, + features: [PublicFeature] + }); + } + + function NgForTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div'); + { element(1, 'p', ['hostBindingDir', '']); } + elementEnd(); + } + } + + /** + *
+ *

+ *
+ */ + const App = createComponent('parent', (rf: RenderFlags, ctx: any) => { + if (rf & RenderFlags.Create) { + template(0, NgForTemplate, 2, 0, null, ['ngForOf', '']); + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'ngForOf', bind(ctx.rows)); + } + }, 1, 1, [HostBindingDir, NgForOf]); + + const fixture = new ComponentFixture(App); + fixture.component.rows = [1, 2, 3]; + fixture.update(); + + const paragraphs = fixture.hostElement.querySelectorAll('p'); + expect(paragraphs[0].id).toEqual('foo'); + expect(paragraphs[1].id).toEqual('foo'); + expect(paragraphs[2].id).toEqual('foo'); + }); + it('should support component with host bindings and array literals', () => { const ff = (v: any) => ['Nancy', v, 'Ned']; @@ -210,7 +323,7 @@ describe('elementProperty', () => { vars: 0, hostVars: 1, hostBindings: (dirIndex: number, elIndex: number) => { - const ctx = loadDirective(dirIndex) as HostBindingComp; + const ctx = load(dirIndex) as HostBindingComp; elementProperty(elIndex, 'id', bind(ctx.id)); }, template: (rf: RenderFlags, ctx: HostBindingComp) => {} @@ -283,7 +396,7 @@ describe('elementProperty', () => { vars: 0, hostVars: 8, hostBindings: (dirIndex: number, elIndex: number) => { - const ctx = loadDirective(dirIndex) as HostBindingComp; + const ctx = load(dirIndex) as HostBindingComp; // LViewData: [..., id, dir, title, ctx.id, pf1, ctx.title, ctx.otherTitle, pf2] elementProperty(elIndex, 'id', bind(pureFunction1(3, ff, ctx.id))); elementProperty(elIndex, 'dir', bind(ctx.dir)); @@ -359,7 +472,7 @@ describe('elementProperty', () => { hostVars: 3, hostBindings: (dirIndex: number, elIndex: number) => { // LViewData: [..., id, ctx.id, pf1] - const ctx = loadDirective(dirIndex) as HostBindingComp; + const ctx = load(dirIndex) as HostBindingComp; elementProperty(elIndex, 'id', bind(pureFunction1(1, ff, ctx.id))); }, template: (rf: RenderFlags, ctx: HostBindingComp) => {} @@ -387,7 +500,7 @@ describe('elementProperty', () => { hostVars: 3, hostBindings: (dirIndex: number, elIndex: number) => { // LViewData [..., title, ctx.title, pf1] - const ctx = loadDirective(dirIndex) as HostBindingDir; + const ctx = load(dirIndex) as HostBindingDir; elementProperty(elIndex, 'title', bind(pureFunction1(1, ff1, ctx.title))); } }); @@ -448,7 +561,7 @@ describe('elementProperty', () => { hostVars: 6, hostBindings: (dirIndex: number, elIndex: number) => { // LViewData: [..., id, title, ctx.id, pf1, ctx.title, pf1] - const ctx = loadDirective(dirIndex) as HostBindingComp; + const ctx = load(dirIndex) as HostBindingComp; elementProperty( elIndex, 'id', bind(ctx.condition ? pureFunction1(2, ff, ctx.id) : 'green')); elementProperty( diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index 9c15697b88..950dedc07d 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -14,7 +14,7 @@ import {directiveInject} from '../../src/render3/di'; import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, loadElement, loadQueryList, reference, registerContentQuery, template} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadElement, loadQueryList, reference, registerContentQuery, template} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {query, queryRefresh} from '../../src/render3/query'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; @@ -1914,7 +1914,7 @@ describe('query', () => { contentQueries: () => { registerContentQuery(query(null, ['foo'], true)); }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; - withContentInstance = loadDirective(dirIndex); + withContentInstance = load(dirIndex); queryRefresh(tmp = loadQueryList(queryStartIdx)) && (withContentInstance.foos = tmp); } @@ -1935,7 +1935,7 @@ describe('query', () => { contentQueries: () => { registerContentQuery(query(null, ['foo'], false)); }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; - shallowCompInstance = loadDirective(dirIndex); + shallowCompInstance = load(dirIndex); queryRefresh(tmp = loadQueryList(queryStartIdx)) && (shallowCompInstance.foos = tmp); } @@ -2116,7 +2116,7 @@ describe('query', () => { }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; - const instance = loadDirective(dirIndex); + const instance = load(dirIndex); queryRefresh(tmp = loadQueryList(queryStartIdx)) && (instance.fooBars = tmp); }, @@ -2180,7 +2180,7 @@ describe('query', () => { }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; - const instance = loadDirective(dirIndex); + const instance = load(dirIndex); queryRefresh(tmp = loadQueryList(queryStartIdx)) && (instance.fooBars = tmp); }, @@ -2233,7 +2233,7 @@ describe('query', () => { }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; - const instance = loadDirective(dirIndex); + const instance = load(dirIndex); queryRefresh(tmp = loadQueryList(queryStartIdx)) && (instance.foos = tmp); }, @@ -2253,7 +2253,7 @@ describe('query', () => { }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { let tmp: any; - const instance = loadDirective(dirIndex); + const instance = load(dirIndex); queryRefresh(tmp = loadQueryList(queryStartIdx)) && (instance.foos = tmp); }, diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 99114210f0..7bcf6102fe 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -9,9 +9,9 @@ import {Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; import {ViewEncapsulation} from '../../src/metadata'; import {directiveInject} from '../../src/render3/di'; -import {AttributeMarker, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver} from '../../src/render3/index'; +import {AttributeMarker, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, load} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, loadDirective, nextContext, projection, projectionDef, reference, template, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; import {NgModuleFactory} from '../../src/render3/ng_module_ref'; @@ -1728,7 +1728,7 @@ describe('ViewContainerRef', () => { hostVars: 1, attributes: ['id', 'attribute'], hostBindings: function(dirIndex, elIndex) { - const cmptInstance = loadDirective(dirIndex); + const cmptInstance = load(dirIndex); elementProperty(elIndex, 'title', bind(cmptInstance.title)); }, });