fix(compiler): apply element bindings before host bindings (#14823)

This commit is contained in:
Tobias Bosch 2017-03-01 12:17:43 -08:00 committed by Igor Minar
parent 9402df92de
commit 79fc1e3959
2 changed files with 84 additions and 55 deletions

View File

@ -33,7 +33,7 @@ export class ViewCompileResult {
@CompilerInjectable() @CompilerInjectable()
export class ViewCompiler { export class ViewCompiler {
constructor( constructor(
private _genConfigNext: CompilerConfig, private _schemaRegistryNext: ElementSchemaRegistry) {} private _genConfigNext: CompilerConfig, private _schemaRegistry: ElementSchemaRegistry) {}
compileComponent( compileComponent(
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
@ -288,24 +288,23 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
elName = null; elName = null;
} }
const {flags, usedEvents, queryMatchesExpr, hostBindings, hostEvents} = const {flags, usedEvents, queryMatchesExpr, hostBindings: dirHostBindings, hostEvents} =
this._visitElementOrTemplate(nodeIndex, ast); this._visitElementOrTemplate(nodeIndex, ast);
let inputDefs: o.Expression[] = []; let inputDefs: o.Expression[] = [];
let outputDefs: o.Expression[] = []; let outputDefs: o.Expression[] = [];
if (elName) { if (elName) {
ast.inputs.forEach( const hostBindings = ast.inputs
(inputAst) => hostBindings.push({context: COMP_VAR, value: inputAst.value})); .map((inputAst) => ({
context: COMP_VAR as o.Expression,
value: inputAst.value,
bindingDef: elementBindingDef(inputAst, null),
}))
.concat(dirHostBindings);
if (hostBindings.length) { if (hostBindings.length) {
this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions); this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions);
inputDefs = hostBindings.map(entry => entry.bindingDef);
} }
// Note: inputDefs have to be in the same order as hostBindings:
// - first the entries from the directives, then the ones from the element.
ast.directives.forEach(
(dirAst, dirIndex) =>
inputDefs.push(...elementBindingDefs(dirAst.hostProperties, dirAst)));
inputDefs.push(...elementBindingDefs(ast.inputs, null));
outputDefs = usedEvents.map( outputDefs = usedEvents.map(
([target, eventName]) => o.literalArr([o.literal(target), o.literal(eventName)])); ([target, eventName]) => o.literalArr([o.literal(target), o.literal(eventName)]));
} }
@ -355,7 +354,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
flags: NodeFlags, flags: NodeFlags,
usedEvents: [string, string][], usedEvents: [string, string][],
queryMatchesExpr: o.Expression, queryMatchesExpr: o.Expression,
hostBindings: {value: AST, context: o.Expression}[], hostBindings: {value: AST, context: o.Expression, bindingDef: o.Expression}[],
hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[], hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[],
} { } {
let flags = NodeFlags.None; let flags = NodeFlags.None;
@ -373,7 +372,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
usedEvents.set(elementEventFullName(target, name), [target, name]); usedEvents.set(elementEventFullName(target, name), [target, name]);
}); });
}); });
const hostBindings: {value: AST, context: o.Expression}[] = []; const hostBindings: {value: AST, context: o.Expression, bindingDef: o.Expression}[] = [];
const hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[] = []; const hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[] = [];
const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives); const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives);
if (componentFactoryResolverProvider) { if (componentFactoryResolverProvider) {
@ -443,7 +442,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
providerAst: ProviderAst, dirAst: DirectiveAst, directiveIndex: number, providerAst: ProviderAst, dirAst: DirectiveAst, directiveIndex: number,
elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[], elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[],
usedEvents: Map<string, any>, queryIds: StaticAndDynamicQueryIds): { usedEvents: Map<string, any>, queryIds: StaticAndDynamicQueryIds): {
hostBindings: {value: AST, context: o.Expression}[], hostBindings: {value: AST, context: o.Expression, bindingDef: o.Expression}[],
hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[] hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[]
} { } {
const nodeIndex = this.nodeDefs.length; const nodeIndex = this.nodeDefs.length;
@ -513,14 +512,16 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([ const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
VIEW_VAR, o.literal(nodeIndex) VIEW_VAR, o.literal(nodeIndex)
]); ]);
const hostBindings = dirAst.hostProperties.map((hostBindingAst) => { const hostBindings =
return { dirAst.hostProperties.map((hostBindingAst) => ({
value: (<ASTWithSource>hostBindingAst.value).ast, value: (<ASTWithSource>hostBindingAst.value).ast,
context: dirContextExpr, context: dirContextExpr,
}; bindingDef: elementBindingDef(hostBindingAst, dirAst),
}); }));
const hostEvents = dirAst.hostEvents.map( const hostEvents = dirAst.hostEvents.map((hostEventAst) => ({
(hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst, dirAst}; }); context: dirContextExpr,
eventAst: hostEventAst, dirAst,
}));
// directiveDef( // directiveDef(
@ -906,9 +907,7 @@ function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): NodeFlags {
return nodeFlag; return nodeFlag;
} }
function elementBindingDefs( function elementBindingDef(inputAst: BoundElementPropertyAst, dirAst: DirectiveAst): o.Expression {
inputAsts: BoundElementPropertyAst[], dirAst: DirectiveAst): o.Expression[] {
return inputAsts.map((inputAst) => {
switch (inputAst.type) { switch (inputAst.type) {
case PropertyBindingType.Attribute: case PropertyBindingType.Attribute:
return o.literalArr([ return o.literalArr([
@ -925,8 +924,7 @@ function elementBindingDefs(
BindingType.ComponentHostProperty : BindingType.ComponentHostProperty :
BindingType.ElementProperty; BindingType.ElementProperty;
return o.literalArr([ return o.literalArr([
o.literal(bindingType), o.literal('@' + inputAst.name), o.literal(bindingType), o.literal('@' + inputAst.name), o.literal(inputAst.securityContext)
o.literal(inputAst.securityContext)
]); ]);
case PropertyBindingType.Class: case PropertyBindingType.Class:
return o.literalArr([o.literal(BindingType.ElementClass), o.literal(inputAst.name)]); return o.literalArr([o.literal(BindingType.ElementClass), o.literal(inputAst.name)]);
@ -935,7 +933,6 @@ function elementBindingDefs(
o.literal(BindingType.ElementStyle), o.literal(inputAst.name), o.literal(inputAst.unit) o.literal(BindingType.ElementStyle), o.literal(inputAst.name), o.literal(inputAst.unit)
]); ]);
} }
});
} }

View File

@ -8,11 +8,13 @@
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry'; import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings'; import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RendererFactoryV2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core'; import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, HostBinding, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RendererFactoryV2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing'; import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by'; import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers';
import {DomElementSchemaRegistry} from '../../../compiler/index';
import {MockSchemaRegistry} from '../../../compiler/testing/index'; import {MockSchemaRegistry} from '../../../compiler/testing/index';
import {EventEmitter} from '../../src/facade/async'; import {EventEmitter} from '../../src/facade/async';
@ -1241,6 +1243,36 @@ export function main() {
expect(renderLog.loggedValues).toEqual(['Tom']); expect(renderLog.loggedValues).toEqual(['Tom']);
}); });
}); });
describe('class binding', () => {
it('should coordinate class attribute and class host binding', () => {
@Component({template: `<div class="{{initClasses}}" someDir></div>`})
class Comp {
initClasses = 'init';
}
@Directive({selector: '[someDir]'})
class SomeDir {
@HostBinding('class.foo')
fooClass = true;
}
const ctx =
TestBed
.configureCompiler({
providers:
[{provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry}]
})
.configureTestingModule({declarations: [Comp, SomeDir]})
.createComponent(Comp);
ctx.detectChanges();
const divEl = ctx.debugElement.children[0];
expect(divEl.nativeElement).toHaveCssClass('init');
expect(divEl.nativeElement).toHaveCssClass('foo');
});
});
}); });
} }
@ -1285,13 +1317,13 @@ function patchLoggingRendererV2(rendererFactory: RendererFactoryV2, log: RenderL
const origSetValue = renderer.setValue; const origSetValue = renderer.setValue;
renderer.setProperty = function(el: any, name: string, value: any): void { renderer.setProperty = function(el: any, name: string, value: any): void {
log.setElementProperty(el, name, value); log.setElementProperty(el, name, value);
origSetProperty.call(this, el, name, value); origSetProperty.call(renderer, el, name, value);
}; };
renderer.setValue = function(node: any, value: string): void { renderer.setValue = function(node: any, value: string): void {
if (getDOM().isTextNode(node)) { if (getDOM().isTextNode(node)) {
log.setText(node, value); log.setText(node, value);
} }
origSetValue.call(this, node, value); origSetValue.call(renderer, node, value);
}; };
return renderer; return renderer;
}; };