fix(ivy): proper accounting of host vars in case of inherited Directives (#27392)
Prior to this change, the number of host vars stored for directives with `hostBindings` in expando block was incorrect for inherited directives (in case both parent and child directive have `hostBindings` defined). Now if we identify that we already added a `hostBinding` into expando block, we just increase the corresponding number of host binding vars PR Close #27392
This commit is contained in:
parent
931a636bcc
commit
6f5c124fe9
@ -1624,10 +1624,15 @@ function queueHostBindingForCheck(
|
|||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.');
|
assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.');
|
||||||
const expando = tView.expandoInstructions !;
|
const expando = tView.expandoInstructions !;
|
||||||
// check whether a given `hostBindings` function already exists in expandoInstructions,
|
const length = expando.length;
|
||||||
|
// Check whether a given `hostBindings` function already exists in expandoInstructions,
|
||||||
// which can happen in case directive definition was extended from base definition (as a part of
|
// which can happen in case directive definition was extended from base definition (as a part of
|
||||||
// the `InheritDefinitionFeature` logic)
|
// the `InheritDefinitionFeature` logic). If we found the same `hostBindings` function in the
|
||||||
if (expando.length < 2 || expando[expando.length - 2] !== def.hostBindings) {
|
// list, we just increase the number of host vars associated with that function, but do not add it
|
||||||
|
// into the list again.
|
||||||
|
if (length >= 2 && expando[length - 2] === def.hostBindings) {
|
||||||
|
expando[length - 1] = (expando[length - 1] as number) + hostVars;
|
||||||
|
} else {
|
||||||
expando.push(def.hostBindings !, hostVars);
|
expando.push(def.hostBindings !, hostVars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import {ElementRef, EventEmitter} from '@angular/core';
|
import {ElementRef, EventEmitter} from '@angular/core';
|
||||||
|
|
||||||
import {AttributeMarker, defineComponent, template, defineDirective, ProvidersFeature, NgOnChangesFeature, QueryList} from '../../src/render3/index';
|
import {AttributeMarker, defineComponent, template, defineDirective, InheritDefinitionFeature, ProvidersFeature, NgOnChangesFeature, QueryList} from '../../src/render3/index';
|
||||||
import {allocHostVars, bind, directiveInject, element, elementEnd, elementProperty, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
|
import {allocHostVars, bind, directiveInject, element, elementEnd, elementProperty, elementStart, listener, load, text, textBinding, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
|
||||||
import {query, queryRefresh} from '../../src/render3/query';
|
import {query, queryRefresh} from '../../src/render3/query';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
@ -780,6 +780,82 @@ describe('host bindings', () => {
|
|||||||
expect(hostElement.title).toBe('other title');
|
expect(hostElement.title).toBe('other title');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should work correctly with inherited directives with hostBindings', () => {
|
||||||
|
let subDir !: SubDirective;
|
||||||
|
let superDir !: SuperDirective;
|
||||||
|
|
||||||
|
class SuperDirective {
|
||||||
|
id = 'my-id';
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SuperDirective,
|
||||||
|
selectors: [['', 'superDir', '']],
|
||||||
|
hostBindings: (rf: RenderFlags, ctx: SuperDirective, elementIndex: number) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
allocHostVars(1);
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
elementProperty(elementIndex, 'id', bind(ctx.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
factory: () => superDir = new SuperDirective(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubDirective extends SuperDirective {
|
||||||
|
title = 'my-title';
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: SubDirective,
|
||||||
|
selectors: [['', 'subDir', '']],
|
||||||
|
hostBindings: (rf: RenderFlags, ctx: SubDirective, elementIndex: number) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
allocHostVars(1);
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
elementProperty(elementIndex, 'title', bind(ctx.title));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
factory: () => subDir = new SubDirective(),
|
||||||
|
features: [InheritDefinitionFeature]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
element(0, 'div', ['subDir', '']);
|
||||||
|
element(1, 'div', ['superDir', '']);
|
||||||
|
}
|
||||||
|
}, 2, 0, [SubDirective, SuperDirective]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
const els = fixture.hostElement.querySelectorAll('div') as NodeListOf<HTMLElement>;
|
||||||
|
|
||||||
|
const firstDivEl = els[0];
|
||||||
|
const secondDivEl = els[1];
|
||||||
|
|
||||||
|
// checking first div element with inherited directive
|
||||||
|
expect(firstDivEl.id).toEqual('my-id');
|
||||||
|
expect(firstDivEl.title).toEqual('my-title');
|
||||||
|
|
||||||
|
subDir.title = 'new-title';
|
||||||
|
fixture.update();
|
||||||
|
expect(firstDivEl.id).toEqual('my-id');
|
||||||
|
expect(firstDivEl.title).toEqual('new-title');
|
||||||
|
|
||||||
|
subDir.id = 'new-id';
|
||||||
|
fixture.update();
|
||||||
|
expect(firstDivEl.id).toEqual('new-id');
|
||||||
|
expect(firstDivEl.title).toEqual('new-title');
|
||||||
|
|
||||||
|
// checking second div element with simple directive
|
||||||
|
expect(secondDivEl.id).toEqual('my-id');
|
||||||
|
|
||||||
|
superDir.id = 'new-id';
|
||||||
|
fixture.update();
|
||||||
|
expect(secondDivEl.id).toEqual('new-id');
|
||||||
|
});
|
||||||
|
|
||||||
it('should support host attributes', () => {
|
it('should support host attributes', () => {
|
||||||
// host: {
|
// host: {
|
||||||
// 'role': 'listbox'
|
// 'role': 'listbox'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user