diff --git a/modules/angular2/src/render/dom/compiler/text_interpolation_parser.ts b/modules/angular2/src/render/dom/compiler/text_interpolation_parser.ts index 43437e6388..42f69e882b 100644 --- a/modules/angular2/src/render/dom/compiler/text_interpolation_parser.ts +++ b/modules/angular2/src/render/dom/compiler/text_interpolation_parser.ts @@ -26,7 +26,7 @@ export class TextInterpolationParser implements CompileStep { var expr = this._parser.parseInterpolation(text, current.elementDescription); if (isPresent(expr)) { DOM.setText(node, ' '); - current.bindElement().bindText(i, expr); + current.bindElement().bindText(node, expr); } } } diff --git a/modules/angular2/src/render/dom/view/proto_view.ts b/modules/angular2/src/render/dom/view/proto_view.ts index bdc0ca37a9..ae1a338426 100644 --- a/modules/angular2/src/render/dom/view/proto_view.ts +++ b/modules/angular2/src/render/dom/view/proto_view.ts @@ -26,17 +26,14 @@ export class DomProtoView { boundTextNodeCount: number; rootNodeCount: number; - constructor({elementBinders, element, transitiveContentTagCount}) { + constructor({elementBinders, element, transitiveContentTagCount, boundTextNodeCount}) { this.element = element; this.elementBinders = elementBinders; this.transitiveContentTagCount = transitiveContentTagCount; this.isTemplateElement = DOM.isTemplateElement(this.element); this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0; - this.boundTextNodeCount = - ListWrapper.reduce(elementBinders, (prevCount: number, elementBinder: ElementBinder) => - prevCount + elementBinder.textNodeIndices.length, - 0); + this.boundTextNodeCount = boundTextNodeCount; this.rootNodeCount = this.isTemplateElement ? DOM.childNodes(DOM.content(this.element)).length : 1; } diff --git a/modules/angular2/src/render/dom/view/proto_view_builder.ts b/modules/angular2/src/render/dom/view/proto_view_builder.ts index 716720b3a3..61b7e10da0 100644 --- a/modules/angular2/src/render/dom/view/proto_view_builder.ts +++ b/modules/angular2/src/render/dom/view/proto_view_builder.ts @@ -48,6 +48,7 @@ export class ProtoViewBuilder { var apiElementBinders = []; var transitiveContentTagCount = 0; + var boundTextNodeCount = 0; ListWrapper.forEach(this.elements, (ebb: ElementBinderBuilder) => { var propertySetters = new Map(); var hostActions = new Map(); @@ -103,9 +104,10 @@ export class ProtoViewBuilder { textBindings: ebb.textBindings, readAttributes: ebb.readAttributes })); - var elementIsEmpty = this._isEmptyElement(ebb.element); + var childNodeInfo = this._analyzeChildNodes(ebb.element, ebb.textBindingNodes); + boundTextNodeCount += ebb.textBindingNodes.length; renderElementBinders.push(new ElementBinder({ - textNodeIndices: ebb.textBindingIndices, + textNodeIndices: childNodeInfo.boundTextNodeIndices, contentTagSelector: ebb.contentTagSelector, parentIndex: parentIndex, distanceToParent: ebb.distanceToParent, @@ -117,14 +119,15 @@ export class ProtoViewBuilder { globalEvents: ebb.eventBuilder.buildGlobalEvents(), hostActions: hostActions, propertySetters: propertySetters, - elementIsEmpty: elementIsEmpty + elementIsEmpty: childNodeInfo.elementIsEmpty })); }); return new api.ProtoViewDto({ render: new DomProtoViewRef(new DomProtoView({ element: this.rootElement, elementBinders: renderElementBinders, - transitiveContentTagCount: transitiveContentTagCount + transitiveContentTagCount: transitiveContentTagCount, + boundTextNodeCount: boundTextNodeCount })), type: this.type, elementBinders: apiElementBinders, @@ -132,19 +135,34 @@ export class ProtoViewBuilder { }); } - _isEmptyElement(el) { - var childNodes = DOM.childNodes(el); + // Note: We need to calculate the next node indices not until the compilation is complete, + // as the compiler might change the order of elements. + private _analyzeChildNodes(parentElement: /*element*/ any, + boundTextNodes: List): _ChildNodesInfo { + var childNodes = DOM.childNodes(DOM.templateAwareRoot(parentElement)); + var boundTextNodeIndices = []; + var indexInBoundTextNodes = 0; + var elementIsEmpty = true; for (var i = 0; i < childNodes.length; i++) { var node = childNodes[i]; - if ((DOM.isTextNode(node) && DOM.getText(node).trim().length > 0) || - (DOM.isElementNode(node))) { - return false; + if (indexInBoundTextNodes < boundTextNodes.length && + node === boundTextNodes[indexInBoundTextNodes]) { + boundTextNodeIndices.push(i); + indexInBoundTextNodes++; + elementIsEmpty = false; + } else if ((DOM.isTextNode(node) && DOM.getText(node).trim().length > 0) || + (DOM.isElementNode(node))) { + elementIsEmpty = false; } } - return true; + return new _ChildNodesInfo(boundTextNodeIndices, elementIsEmpty); } } +class _ChildNodesInfo { + constructor(public boundTextNodeIndices: List, public elementIsEmpty: boolean) {} +} + export class ElementBinderBuilder { parent: ElementBinderBuilder = null; distanceToParent: number = 0; @@ -154,7 +172,7 @@ export class ElementBinderBuilder { variableBindings: Map = new Map(); eventBindings: List = []; eventBuilder: EventBuilder = new EventBuilder(); - textBindingIndices: List = []; + textBindingNodes: List = []; textBindings: List = []; contentTagSelector: string = null; readAttributes: Map = new Map(); @@ -214,8 +232,8 @@ export class ElementBinderBuilder { this.eventBindings.push(this.eventBuilder.add(name, expression, target)); } - bindText(index, expression) { - this.textBindingIndices.push(index); + bindText(textNode, expression) { + this.textBindingNodes.push(textNode); this.textBindings.push(expression); } diff --git a/modules/angular2/test/render/dom/compiler/text_interpolation_parser_spec.ts b/modules/angular2/test/render/dom/compiler/text_interpolation_parser_spec.ts index ba9f9e1f48..d83e17bae0 100644 --- a/modules/angular2/test/render/dom/compiler/text_interpolation_parser_spec.ts +++ b/modules/angular2/test/render/dom/compiler/text_interpolation_parser_spec.ts @@ -5,6 +5,7 @@ import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {Lexer, Parser} from 'angular2/change_detection'; import {IgnoreChildrenStep} from './pipeline_spec'; import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder'; +import {DOM} from 'angular2/src/dom/dom_adapter'; export function main() { describe('TextInterpolationParser', () => { @@ -20,7 +21,8 @@ export function main() { function assertTextBinding(elementBinder, bindingIndex, nodeIndex, expression) { expect(elementBinder.textBindings[bindingIndex].source).toEqual(expression); - expect(elementBinder.textBindingIndices[bindingIndex]).toEqual(nodeIndex); + expect(elementBinder.textBindingNodes[bindingIndex]) + .toEqual(DOM.childNodes(DOM.templateAwareRoot(elementBinder.element))[nodeIndex]); } it('should find text interpolation in normal elements', () => { diff --git a/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.ts b/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.ts index a711c24dbc..658acf3d9f 100644 --- a/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.ts +++ b/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.ts @@ -36,15 +36,16 @@ import {DomTestbed} from './dom_testbed'; export function main() { describe('ShadowDom integration tests', function() { + var styleHost; var strategies = { "scoped": bind(ShadowDomStrategy) .toFactory((styleInliner, styleUrlResolver) => new EmulatedScopedShadowDomStrategy( - styleInliner, styleUrlResolver, null), + styleInliner, styleUrlResolver, styleHost), [StyleInliner, StyleUrlResolver]), "unscoped": bind(ShadowDomStrategy) - .toFactory((styleUrlResolver) => - new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, null), + .toFactory((styleUrlResolver) => new EmulatedUnscopedShadowDomStrategy( + styleUrlResolver, styleHost), [StyleUrlResolver]) }; if (DOM.supportsNativeShadowDOM()) { @@ -54,12 +55,15 @@ export function main() { .toFactory((styleUrlResolver) => new NativeShadowDomStrategy(styleUrlResolver), [StyleUrlResolver])); } + beforeEach(() => { styleHost = el('
'); }); StringMapWrapper.forEach(strategies, (strategyBinding, name) => { describe(`${name} shadow dom strategy`, () => { beforeEachBindings(() => { return [strategyBinding, DomTestbed]; }); // GH-2095 - https://github.com/angular/angular/issues/2095 + // important as we are adding a content end element during compilation, + // which could skrew up text node indices. it('should support text nodes after content tags', inject([DomTestbed, AsyncTestCompleter], (tb, async) => { tb.compileAll([ @@ -80,6 +84,28 @@ export function main() { }); })); + // important as we are moving style tags around during compilation, + // which could skrew up text node indices. + it('should support text nodes after style tags', + inject([DomTestbed, AsyncTestCompleter], (tb, async) => { + tb.compileAll([ + simple, + new ViewDefinition({ + componentId: 'simple', + template: '

P,

{{a}}', + directives: [] + }) + ]) + .then((protoViewDtos) => { + var rootView = tb.createRootView(protoViewDtos[0]); + var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]); + + tb.renderer.setText(cmpView.viewRef, 0, 'text'); + expect(tb.rootEl).toHaveText('P,text'); + async.done(); + }); + })); + it('should support simple components', inject([AsyncTestCompleter, DomTestbed], (async, tb) => { tb.compileAll([ @@ -102,6 +128,29 @@ export function main() { }); })); + it('should support simple components with text interpolation as direct children', + inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ + componentId: 'main', + template: '' + + '{{text}}' + + '', + directives: [simple] + }), + simpleTemplate + ]) + .then((protoViews) => { + var cmpView = tb.createRootViews(protoViews)[1]; + tb.renderer.setText(cmpView.viewRef, 0, 'A'); + + expect(tb.rootEl).toHaveText('SIMPLE(A)'); + + async.done(); + }); + })); + it('should not show the light dom even if there is not content tag', inject([AsyncTestCompleter, DomTestbed], (async, tb) => { tb.compileAll([ diff --git a/modules/angular2/test/render/dom/view/view_spec.ts b/modules/angular2/test/render/dom/view/view_spec.ts index c953024644..b037a04db3 100644 --- a/modules/angular2/test/render/dom/view/view_spec.ts +++ b/modules/angular2/test/render/dom/view/view_spec.ts @@ -30,8 +30,12 @@ export function main() { binders = []; } var rootEl = el('
'); - return new DomProtoView( - {element: rootEl, elementBinders: binders, transitiveContentTagCount: 0}); + return new DomProtoView({ + element: rootEl, + elementBinders: binders, + transitiveContentTagCount: 0, + boundTextNodeCount: 0 + }); } function createView(pv = null, boundElementCount = 0) {