fix(ivy): sync view with blueprint when necessary (#26263)
PR Close #26263
This commit is contained in:
parent
fdaf573073
commit
51dfdd5dd1
|
@ -106,8 +106,9 @@ export function getOrCreateNodeInjectorForNode(
|
||||||
if (tView.firstTemplatePass) {
|
if (tView.firstTemplatePass) {
|
||||||
// TODO(kara): Store node injector with host bindings for that node (see VIEW_DATA.md)
|
// TODO(kara): Store node injector with host bindings for that node (see VIEW_DATA.md)
|
||||||
tNode.injectorIndex = hostView.length;
|
tNode.injectorIndex = hostView.length;
|
||||||
tView.blueprint.push(0, 0, 0, 0, 0, 0, 0, 0, null); // foundation for cumulative bloom
|
setUpBloom(tView.data, tNode); // foundation for node bloom
|
||||||
tView.data.push(0, 0, 0, 0, 0, 0, 0, 0, tNode); // foundation for node bloom
|
setUpBloom(hostView, null); // foundation for cumulative bloom
|
||||||
|
setUpBloom(tView.blueprint, null);
|
||||||
tView.hostBindingStartIndex += INJECTOR_SIZE;
|
tView.hostBindingStartIndex += INJECTOR_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,16 +119,25 @@ export function getOrCreateNodeInjectorForNode(
|
||||||
const parentData = parentView[TVIEW].data as any;
|
const parentData = parentView[TVIEW].data as any;
|
||||||
const injectorIndex = tNode.injectorIndex;
|
const injectorIndex = tNode.injectorIndex;
|
||||||
|
|
||||||
|
// If a parent injector can't be found, its location is set to -1.
|
||||||
|
// In that case, we don't need to set up a cumulative bloom
|
||||||
|
if (parentLoc !== -1) {
|
||||||
for (let i = 0; i < PARENT_INJECTOR; i++) {
|
for (let i = 0; i < PARENT_INJECTOR; i++) {
|
||||||
const bloomIndex = parentIndex + i;
|
const bloomIndex = parentIndex + i;
|
||||||
hostView[injectorIndex + i] =
|
// Creates a cumulative bloom filter that merges the parent's bloom filter
|
||||||
parentLoc === -1 ? 0 : parentView[bloomIndex] | parentData[bloomIndex];
|
// and its own cumulative bloom (which contains tokens for all ancestors)
|
||||||
|
hostView[injectorIndex + i] = parentView[bloomIndex] | parentData[bloomIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hostView[injectorIndex + PARENT_INJECTOR] = parentLoc;
|
hostView[injectorIndex + PARENT_INJECTOR] = parentLoc;
|
||||||
return injectorIndex;
|
return injectorIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setUpBloom(arr: any[], footer: TNode | null) {
|
||||||
|
arr.push(0, 0, 0, 0, 0, 0, 0, 0, footer);
|
||||||
|
}
|
||||||
|
|
||||||
export function getInjectorIndex(tNode: TNode, hostView: LViewData): number {
|
export function getInjectorIndex(tNode: TNode, hostView: LViewData): number {
|
||||||
if (tNode.injectorIndex === -1 ||
|
if (tNode.injectorIndex === -1 ||
|
||||||
// If the injector index is the same as its parent's injector index, then the index has been
|
// If the injector index is the same as its parent's injector index, then the index has been
|
||||||
|
|
|
@ -335,6 +335,7 @@ export function leaveView(newView: LViewData, creationOnly?: boolean): void {
|
||||||
*/
|
*/
|
||||||
function refreshDescendantViews() {
|
function refreshDescendantViews() {
|
||||||
setHostBindings(tView.hostBindings);
|
setHostBindings(tView.hostBindings);
|
||||||
|
const parentFirstTemplatePass = firstTemplatePass;
|
||||||
|
|
||||||
// This needs to be set before children are processed to support recursive components
|
// This needs to be set before children are processed to support recursive components
|
||||||
tView.firstTemplatePass = firstTemplatePass = false;
|
tView.firstTemplatePass = firstTemplatePass = false;
|
||||||
|
@ -351,7 +352,7 @@ function refreshDescendantViews() {
|
||||||
executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode);
|
executeHooks(directives !, tView.contentHooks, tView.contentCheckHooks, creationMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshChildComponents(tView.components);
|
refreshChildComponents(tView.components, parentFirstTemplatePass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -388,10 +389,11 @@ function refreshContentQueries(tView: TView): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Refreshes child components in the current view. */
|
/** Refreshes child components in the current view. */
|
||||||
function refreshChildComponents(components: number[] | null): void {
|
function refreshChildComponents(
|
||||||
|
components: number[] | null, parentFirstTemplatePass: boolean): void {
|
||||||
if (components != null) {
|
if (components != null) {
|
||||||
for (let i = 0; i < components.length; i++) {
|
for (let i = 0; i < components.length; i++) {
|
||||||
componentRefresh(components[i]);
|
componentRefresh(components[i], parentFirstTemplatePass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -701,7 +703,7 @@ export function renderComponentOrTemplate<T>(
|
||||||
// Element was stored at 0 in data and directive was stored at 0 in directives
|
// Element was stored at 0 in data and directive was stored at 0 in directives
|
||||||
// in renderComponent()
|
// in renderComponent()
|
||||||
setHostBindings(tView.hostBindings);
|
setHostBindings(tView.hostBindings);
|
||||||
componentRefresh(HEADER_OFFSET);
|
componentRefresh(HEADER_OFFSET, false);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (rendererFactory.end) {
|
if (rendererFactory.end) {
|
||||||
|
@ -2220,7 +2222,8 @@ export function embeddedViewEnd(): void {
|
||||||
*
|
*
|
||||||
* @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET)
|
* @param adjustedElementIndex Element index in LViewData[] (adjusted for HEADER_OFFSET)
|
||||||
*/
|
*/
|
||||||
export function componentRefresh<T>(adjustedElementIndex: number): void {
|
export function componentRefresh<T>(
|
||||||
|
adjustedElementIndex: number, parentFirstTemplatePass: boolean): void {
|
||||||
ngDevMode && assertDataInRange(adjustedElementIndex);
|
ngDevMode && assertDataInRange(adjustedElementIndex);
|
||||||
const element = readElementValue(viewData[adjustedElementIndex]) as LElementNode;
|
const element = readElementValue(viewData[adjustedElementIndex]) as LElementNode;
|
||||||
ngDevMode && assertNodeType(tView.data[adjustedElementIndex] as TNode, TNodeType.Element);
|
ngDevMode && assertNodeType(tView.data[adjustedElementIndex] as TNode, TNodeType.Element);
|
||||||
|
@ -2230,10 +2233,44 @@ export function componentRefresh<T>(adjustedElementIndex: number): void {
|
||||||
|
|
||||||
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
|
// Only attached CheckAlways components or attached, dirty OnPush components should be checked
|
||||||
if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
if (viewAttached(hostView) && hostView[FLAGS] & (LViewFlags.CheckAlways | LViewFlags.Dirty)) {
|
||||||
|
parentFirstTemplatePass && syncViewWithBlueprint(hostView);
|
||||||
detectChangesInternal(hostView, hostView[CONTEXT]);
|
detectChangesInternal(hostView, hostView[CONTEXT]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs an LViewData instance with its blueprint if they have gotten out of sync.
|
||||||
|
*
|
||||||
|
* Typically, blueprints and their view instances should always be in sync, so the loop here
|
||||||
|
* will be skipped. However, consider this case of two components side-by-side:
|
||||||
|
*
|
||||||
|
* App template:
|
||||||
|
* ```
|
||||||
|
* <comp></comp>
|
||||||
|
* <comp></comp>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The following will happen:
|
||||||
|
* 1. App template begins processing.
|
||||||
|
* 2. First <comp> is matched as a component and its LViewData is created.
|
||||||
|
* 3. Second <comp> is matched as a component and its LViewData is created.
|
||||||
|
* 4. App template completes processing, so it's time to check child templates.
|
||||||
|
* 5. First <comp> template is checked. It has a directive, so its def is pushed to blueprint.
|
||||||
|
* 6. Second <comp> template is checked. Its blueprint has been updated by the first
|
||||||
|
* <comp> template, but its LViewData was created before this update, so it is out of sync.
|
||||||
|
*
|
||||||
|
* Note that embedded views inside ngFor loops will never be out of sync because these views
|
||||||
|
* are processed as soon as they are created.
|
||||||
|
*
|
||||||
|
* @param componentView The view to sync
|
||||||
|
*/
|
||||||
|
function syncViewWithBlueprint(componentView: LViewData) {
|
||||||
|
const componentTView = componentView[TVIEW];
|
||||||
|
for (let i = componentView.length; i < componentTView.blueprint.length; i++) {
|
||||||
|
componentView[i] = componentTView.blueprint[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns a boolean for whether the view is attached */
|
/** Returns a boolean for whether the view is attached */
|
||||||
export function viewAttached(view: LViewData): boolean {
|
export function viewAttached(view: LViewData): boolean {
|
||||||
return (view[FLAGS] & LViewFlags.Attached) === LViewFlags.Attached;
|
return (view[FLAGS] & LViewFlags.Attached) === LViewFlags.Attached;
|
||||||
|
|
|
@ -965,6 +965,9 @@
|
||||||
{
|
{
|
||||||
"name": "setUpAttributes"
|
"name": "setUpAttributes"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "setUpBloom"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "setValue"
|
"name": "setValue"
|
||||||
},
|
},
|
||||||
|
@ -983,6 +986,9 @@
|
||||||
{
|
{
|
||||||
"name": "swapMultiContextEntries"
|
"name": "swapMultiContextEntries"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "syncViewWithBlueprint"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "template"
|
"name": "template"
|
||||||
},
|
},
|
||||||
|
|
|
@ -344,9 +344,15 @@
|
||||||
{
|
{
|
||||||
"name": "setUpAttributes"
|
"name": "setUpAttributes"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "setUpBloom"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "stringify$2"
|
"name": "stringify$2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "syncViewWithBlueprint"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "text"
|
"name": "text"
|
||||||
},
|
},
|
||||||
|
|
|
@ -986,6 +986,9 @@
|
||||||
{
|
{
|
||||||
"name": "setUpAttributes"
|
"name": "setUpAttributes"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "setUpBloom"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "setValue"
|
"name": "setValue"
|
||||||
},
|
},
|
||||||
|
@ -1001,6 +1004,9 @@
|
||||||
{
|
{
|
||||||
"name": "stringify$2"
|
"name": "stringify$2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "syncViewWithBlueprint"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "template"
|
"name": "template"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2339,6 +2339,9 @@
|
||||||
{
|
{
|
||||||
"name": "setUpAttributes"
|
"name": "setUpAttributes"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "setUpBloom"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "setValue"
|
"name": "setValue"
|
||||||
},
|
},
|
||||||
|
@ -2402,6 +2405,9 @@
|
||||||
{
|
{
|
||||||
"name": "symbolNames"
|
"name": "symbolNames"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "syncViewWithBlueprint"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "tagSet"
|
"name": "tagSet"
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {EventEmitter} from '@angular/core';
|
import {EventEmitter} from '@angular/core';
|
||||||
|
|
||||||
import {AttributeMarker, defineComponent, defineDirective, tick} from '../../src/render3/index';
|
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 {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, loadDirective, reference, text, textBinding} from '../../src/render3/instructions';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
import {pureFunction1, pureFunction2} from '../../src/render3/pure_function';
|
import {pureFunction1, pureFunction2} from '../../src/render3/pure_function';
|
||||||
|
@ -153,6 +153,48 @@ describe('elementProperty', () => {
|
||||||
expect(fixture.hostElement.id).toBe('other-id');
|
expect(fixture.hostElement.id).toBe('other-id');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support host bindings on second template pass', () => {
|
||||||
|
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(loadDirective<HostBindingDir>(directiveIndex).id));
|
||||||
|
},
|
||||||
|
features: [PublicFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <div hostBindingDir></div> */
|
||||||
|
const Parent = createComponent('parent', (rf: RenderFlags, ctx: any) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
element(0, 'div', ['hostBindingDir', '']);
|
||||||
|
}
|
||||||
|
}, 1, 0, [HostBindingDir]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <parent></parent>
|
||||||
|
* <parent></parent>
|
||||||
|
*/
|
||||||
|
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
element(0, 'parent');
|
||||||
|
element(1, 'parent');
|
||||||
|
}
|
||||||
|
}, 2, 0, [Parent]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
const divs = fixture.hostElement.querySelectorAll('div');
|
||||||
|
expect(divs[0].id).toEqual('foo');
|
||||||
|
expect(divs[1].id).toEqual('foo');
|
||||||
|
});
|
||||||
|
|
||||||
it('should support component with host bindings and array literals', () => {
|
it('should support component with host bindings and array literals', () => {
|
||||||
const ff = (v: any) => ['Nancy', v, 'Ned'];
|
const ff = (v: any) => ['Nancy', v, 'Ned'];
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue