fix(compiler): apply element bindings before host bindings (#14823)
This commit is contained in:
parent
9402df92de
commit
79fc1e3959
|
@ -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)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue