fix(ivy): host bindings after dirs without host bindings should work (#26801)

PR Close #26801
This commit is contained in:
Kara Erickson 2018-10-27 15:25:25 -07:00
parent 18b6d580c5
commit 3b9bc73db4
7 changed files with 101 additions and 109 deletions

View File

@ -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>(

View File

@ -938,6 +938,9 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "noop"
},
{ {
"name": "pointers" "name": "pointers"
}, },

View File

@ -362,6 +362,9 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "noop"
},
{ {
"name": "postProcessBaseDirective" "name": "postProcessBaseDirective"
}, },

View File

@ -4016,6 +4016,9 @@
{ {
"name": "nodeValue" "name": "nodeValue"
}, },
{
"name": "noop"
},
{ {
"name": "noop$1" "name": "noop$1"
}, },

View File

@ -956,6 +956,9 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "noop"
},
{ {
"name": "pointers" "name": "pointers"
}, },

View File

@ -2240,6 +2240,9 @@
{ {
"name": "noSideEffects" "name": "noSideEffects"
}, },
{
"name": "noop"
},
{ {
"name": "noop$1" "name": "noop$1"
}, },

View File

@ -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 host bindings on second template pass', () => { it('should support dirs with host bindings on the same node as dirs without host bindings',
class HostBindingDir { () => {
// @HostBinding() const SomeDir = createDirective('someDir');
id = 'foo';
static ngDirectiveDef = defineDirective({ /** <div someDir hostBindingDir></div> */
type: HostBindingDir, const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
selectors: [['', 'hostBindingDir', '']], if (rf & RenderFlags.Create) {
factory: () => new HostBindingDir(), element(0, 'div', ['someDir', '', 'hostBindingDir', '']);
hostVars: 1,
hostBindings: (directiveIndex: number, elementIndex: number) => {
elementProperty(elementIndex, 'id', bind(load<HostBindingDir>(directiveIndex).id));
} }
}, 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', () => {
/** <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';