fix(ivy): host bindings after dirs without host bindings should work (#26801)
PR Close #26801
This commit is contained in:
parent
18b6d580c5
commit
3b9bc73db4
|
@ -14,6 +14,7 @@ import {QueryList} from '../linker';
|
||||||
import {Sanitizer} from '../sanitization/security';
|
import {Sanitizer} from '../sanitization/security';
|
||||||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
|
import {noop} from '../util/noop';
|
||||||
|
|
||||||
import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert';
|
import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert';
|
||||||
import {attachPatchData, getComponentViewByInstance} from './context_discovery';
|
import {attachPatchData, getComponentViewByInstance} from './context_discovery';
|
||||||
|
@ -41,7 +42,6 @@ import {NO_CHANGE} from './tokens';
|
||||||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util';
|
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A permanent marker promise which signifies that the current CD tree is
|
* A permanent marker promise which signifies that the current CD tree is
|
||||||
* clean.
|
* clean.
|
||||||
|
@ -1490,7 +1490,8 @@ export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void
|
||||||
function queueHostBindingForCheck(tView: TView, def: DirectiveDef<any>| ComponentDef<any>): void {
|
function queueHostBindingForCheck(tView: TView, def: DirectiveDef<any>| ComponentDef<any>): void {
|
||||||
ngDevMode &&
|
ngDevMode &&
|
||||||
assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.');
|
assertEqual(getFirstTemplatePass(), true, 'Should only be called in first template pass.');
|
||||||
tView.expandoInstructions !.push(def.hostBindings !, def.hostVars);
|
tView.expandoInstructions !.push(def.hostBindings || noop);
|
||||||
|
if (def.hostVars) tView.expandoInstructions !.push(def.hostVars);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Caches local names and their matching directive indices for query and template lookups. */
|
/** Caches local names and their matching directive indices for query and template lookups. */
|
||||||
|
@ -1552,7 +1553,7 @@ function baseResolveDirective<T>(
|
||||||
tView.blueprint.push(nodeInjectorFactory);
|
tView.blueprint.push(nodeInjectorFactory);
|
||||||
viewData.push(nodeInjectorFactory);
|
viewData.push(nodeInjectorFactory);
|
||||||
|
|
||||||
if (def.hostBindings) queueHostBindingForCheck(tView, def);
|
queueHostBindingForCheck(tView, def);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addComponentLogic<T>(
|
function addComponentLogic<T>(
|
||||||
|
|
|
@ -938,6 +938,9 @@
|
||||||
{
|
{
|
||||||
"name": "noSideEffects"
|
"name": "noSideEffects"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "noop"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "pointers"
|
"name": "pointers"
|
||||||
},
|
},
|
||||||
|
|
|
@ -362,6 +362,9 @@
|
||||||
{
|
{
|
||||||
"name": "noSideEffects"
|
"name": "noSideEffects"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "noop"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "postProcessBaseDirective"
|
"name": "postProcessBaseDirective"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4016,6 +4016,9 @@
|
||||||
{
|
{
|
||||||
"name": "nodeValue"
|
"name": "nodeValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "noop"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "noop$1"
|
"name": "noop$1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -956,6 +956,9 @@
|
||||||
{
|
{
|
||||||
"name": "noSideEffects"
|
"name": "noSideEffects"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "noop"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "pointers"
|
"name": "pointers"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2240,6 +2240,9 @@
|
||||||
{
|
{
|
||||||
"name": "noSideEffects"
|
"name": "noSideEffects"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "noop"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "noop$1"
|
"name": "noop$1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,8 +16,14 @@ import {pureFunction1, pureFunction2} from '../../src/render3/pure_function';
|
||||||
import {ComponentFixture, TemplateFixture, createComponent, createDirective} from './render_util';
|
import {ComponentFixture, TemplateFixture, createComponent, createDirective} from './render_util';
|
||||||
import {NgForOf} from './common_with_def';
|
import {NgForOf} from './common_with_def';
|
||||||
|
|
||||||
describe('host', () => {
|
describe('host bindings', () => {
|
||||||
let nameComp !: NameComp;
|
let nameComp: NameComp|null;
|
||||||
|
let hostBindingDir: HostBindingDir|null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
nameComp = null;
|
||||||
|
hostBindingDir = null;
|
||||||
|
});
|
||||||
|
|
||||||
class NameComp {
|
class NameComp {
|
||||||
names !: string[];
|
names !: string[];
|
||||||
|
@ -33,6 +39,40 @@ describe('host', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<HostBindingDir>(directiveIndex).id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class HostBindingComp {
|
||||||
|
// @HostBinding()
|
||||||
|
id = 'my-id';
|
||||||
|
|
||||||
|
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, 'id', bind(ctx.id));
|
||||||
|
},
|
||||||
|
template: (rf: RenderFlags, ctx: HostBindingComp) => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it('should support host bindings in directives', () => {
|
it('should support host bindings in directives', () => {
|
||||||
let directiveInstance: Directive|undefined;
|
let directiveInstance: Directive|undefined;
|
||||||
|
|
||||||
|
@ -62,25 +102,6 @@ describe('host', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support host bindings on root component', () => {
|
it('should support host bindings on root component', () => {
|
||||||
class HostBindingComp {
|
|
||||||
// @HostBinding()
|
|
||||||
id = 'my-id';
|
|
||||||
|
|
||||||
static ngComponentDef = defineComponent({
|
|
||||||
type: HostBindingComp,
|
|
||||||
selectors: [['host-binding-comp']],
|
|
||||||
factory: () => new HostBindingComp(),
|
|
||||||
consts: 0,
|
|
||||||
vars: 0,
|
|
||||||
hostVars: 1,
|
|
||||||
hostBindings: (dirIndex: number, elIndex: number) => {
|
|
||||||
const instance = load(dirIndex) as HostBindingComp;
|
|
||||||
elementProperty(elIndex, 'id', bind(instance.id));
|
|
||||||
},
|
|
||||||
template: (rf: RenderFlags, ctx: HostBindingComp) => {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const fixture = new ComponentFixture(HostBindingComp);
|
const fixture = new ComponentFixture(HostBindingComp);
|
||||||
expect(fixture.hostElement.id).toBe('my-id');
|
expect(fixture.hostElement.id).toBe('my-id');
|
||||||
|
|
||||||
|
@ -132,84 +153,73 @@ describe('host', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support host bindings on multiple nodes', () => {
|
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<HostBindingDir>(directiveIndex).id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const SomeDir = createDirective('someDir');
|
const SomeDir = createDirective('someDir');
|
||||||
|
|
||||||
class HostBindingComp {
|
class HostTitleComp {
|
||||||
// @HostBinding()
|
// @HostBinding()
|
||||||
title = 'my-title';
|
title = 'my-title';
|
||||||
|
|
||||||
static ngComponentDef = defineComponent({
|
static ngComponentDef = defineComponent({
|
||||||
type: HostBindingComp,
|
type: HostTitleComp,
|
||||||
selectors: [['host-binding-comp']],
|
selectors: [['host-title-comp']],
|
||||||
factory: () => new HostBindingComp(),
|
factory: () => new HostTitleComp(),
|
||||||
consts: 0,
|
consts: 0,
|
||||||
vars: 0,
|
vars: 0,
|
||||||
hostVars: 1,
|
hostVars: 1,
|
||||||
hostBindings: (dirIndex: number, elIndex: number) => {
|
hostBindings: (dirIndex: number, elIndex: number) => {
|
||||||
const ctx = load(dirIndex) as HostBindingComp;
|
const ctx = load(dirIndex) as HostTitleComp;
|
||||||
elementProperty(elIndex, 'title', bind(ctx.title));
|
elementProperty(elIndex, 'title', bind(ctx.title));
|
||||||
},
|
},
|
||||||
template: (rf: RenderFlags, ctx: HostBindingComp) => {}
|
template: (rf: RenderFlags, ctx: HostTitleComp) => {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <div hostBindingDir></div>
|
* <div hostBindingDir></div>
|
||||||
* <div someDir></div>
|
* <div someDir></div>
|
||||||
* <host-binding-comp></host-binding-comp>
|
* <host-title-comp></host-title-comp>
|
||||||
*/
|
*/
|
||||||
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
element(0, 'div', ['hostBindingDir', '']);
|
element(0, 'div', ['hostBindingDir', '']);
|
||||||
element(1, 'div', ['someDir', '']);
|
element(1, 'div', ['someDir', '']);
|
||||||
element(2, 'host-binding-comp');
|
element(2, 'host-title-comp');
|
||||||
}
|
}
|
||||||
}, 3, 0, [HostBindingDir, SomeDir, HostBindingComp]);
|
}, 3, 0, [HostBindingDir, SomeDir, HostTitleComp]);
|
||||||
|
|
||||||
const fixture = new ComponentFixture(App);
|
const fixture = new ComponentFixture(App);
|
||||||
const hostBindingDiv = fixture.hostElement.querySelector('div') as HTMLElement;
|
const hostBindingDiv = fixture.hostElement.querySelector('div') as HTMLElement;
|
||||||
const hostBindingComp = fixture.hostElement.querySelector('host-binding-comp') as HTMLElement;
|
const hostTitleComp = fixture.hostElement.querySelector('host-title-comp') as HTMLElement;
|
||||||
expect(hostBindingDiv.id).toEqual('foo');
|
expect(hostBindingDiv.id).toEqual('foo');
|
||||||
expect(hostBindingComp.title).toEqual('my-title');
|
expect(hostTitleComp.title).toEqual('my-title');
|
||||||
|
|
||||||
hostBindingDir.id = 'bar';
|
hostBindingDir !.id = 'bar';
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(hostBindingDiv.id).toEqual('bar');
|
expect(hostBindingDiv.id).toEqual('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support dirs with host bindings on the same node as dirs without host bindings',
|
||||||
|
() => {
|
||||||
|
const SomeDir = createDirective('someDir');
|
||||||
|
|
||||||
|
/** <div someDir hostBindingDir></div> */
|
||||||
|
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
element(0, 'div', ['someDir', '', 'hostBindingDir', '']);
|
||||||
|
}
|
||||||
|
}, 1, 0, [SomeDir, HostBindingDir]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
const hostBindingDiv = fixture.hostElement.querySelector('div') as HTMLElement;
|
||||||
|
expect(hostBindingDiv.id).toEqual('foo');
|
||||||
|
|
||||||
|
hostBindingDir !.id = 'bar';
|
||||||
|
fixture.update();
|
||||||
|
expect(hostBindingDiv.id).toEqual('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should support host bindings on second template pass', () => {
|
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(load<HostBindingDir>(directiveIndex).id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** <div hostBindingDir></div> */
|
/** <div hostBindingDir></div> */
|
||||||
const Parent = createComponent('parent', (rf: RenderFlags, ctx: any) => {
|
const Parent = createComponent('parent', (rf: RenderFlags, ctx: any) => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
|
@ -235,21 +245,6 @@ describe('host', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support host bindings in for loop', () => {
|
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<HostBindingDir>(directiveIndex).id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function NgForTemplate(rf: RenderFlags, ctx: any) {
|
function NgForTemplate(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
elementStart(0, 'div');
|
elementStart(0, 'div');
|
||||||
|
@ -285,25 +280,6 @@ describe('host', () => {
|
||||||
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'];
|
||||||
|
|
||||||
class HostBindingComp {
|
|
||||||
// @HostBinding()
|
|
||||||
id = 'my-id';
|
|
||||||
|
|
||||||
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, 'id', bind(ctx.id));
|
|
||||||
},
|
|
||||||
template: (rf: RenderFlags, ctx: HostBindingComp) => {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <name-comp [names]="['Nancy', name, 'Ned']"></name-comp>
|
* <name-comp [names]="['Nancy', name, 'Ned']"></name-comp>
|
||||||
* <host-binding-comp></host-binding-comp>
|
* <host-binding-comp></host-binding-comp>
|
||||||
|
@ -323,16 +299,16 @@ describe('host', () => {
|
||||||
fixture.component.name = 'Betty';
|
fixture.component.name = 'Betty';
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(hostBindingEl.id).toBe('my-id');
|
expect(hostBindingEl.id).toBe('my-id');
|
||||||
expect(nameComp.names).toEqual(['Nancy', 'Betty', 'Ned']);
|
expect(nameComp !.names).toEqual(['Nancy', 'Betty', 'Ned']);
|
||||||
|
|
||||||
const firstArray = nameComp.names;
|
const firstArray = nameComp !.names;
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(firstArray).toBe(nameComp.names);
|
expect(firstArray).toBe(nameComp !.names);
|
||||||
|
|
||||||
fixture.component.name = 'my-id';
|
fixture.component.name = 'my-id';
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(hostBindingEl.id).toBe('my-id');
|
expect(hostBindingEl.id).toBe('my-id');
|
||||||
expect(nameComp.names).toEqual(['Nancy', 'my-id', 'Ned']);
|
expect(nameComp !.names).toEqual(['Nancy', 'my-id', 'Ned']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: This is a contrived example. For feature parity with render2, we should make sure it
|
// Note: This is a contrived example. For feature parity with render2, we should make sure it
|
||||||
|
@ -403,11 +379,11 @@ describe('host', () => {
|
||||||
expect(hostBindingEl.id).toBe('red,blue');
|
expect(hostBindingEl.id).toBe('red,blue');
|
||||||
expect(hostBindingEl.dir).toBe('ltr');
|
expect(hostBindingEl.dir).toBe('ltr');
|
||||||
expect(hostBindingEl.title).toBe('my title,other title');
|
expect(hostBindingEl.title).toBe('my title,other title');
|
||||||
expect(nameComp.names).toEqual(['Frank', 'Nancy', 'Joe']);
|
expect(nameComp !.names).toEqual(['Frank', 'Nancy', 'Joe']);
|
||||||
|
|
||||||
const firstArray = nameComp.names;
|
const firstArray = nameComp !.names;
|
||||||
fixture.update();
|
fixture.update();
|
||||||
expect(firstArray).toBe(nameComp.names);
|
expect(firstArray).toBe(nameComp !.names);
|
||||||
|
|
||||||
hostBindingComp.id = 'green';
|
hostBindingComp.id = 'green';
|
||||||
hostBindingComp.dir = 'rtl';
|
hostBindingComp.dir = 'rtl';
|
||||||
|
|
Loading…
Reference in New Issue