diff --git a/modules/@angular/compiler/src/html_parser.ts b/modules/@angular/compiler/src/html_parser.ts
index 04c795a75e..092232c1e0 100644
--- a/modules/@angular/compiler/src/html_parser.ts
+++ b/modules/@angular/compiler/src/html_parser.ts
@@ -1,11 +1,7 @@
import {Injectable} from '@angular/core';
-
import {isPresent, isBlank,} from '../src/facade/lang';
-
import {ListWrapper} from '../src/facade/collection';
-
import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst} from './html_ast';
-
import {HtmlToken, HtmlTokenType, tokenizeHtml} from './html_lexer';
import {ParseError, ParseSourceSpan} from './parse_util';
import {getHtmlTagDefinition, getNsPrefix, mergeNsAndName} from './html_tags';
@@ -267,18 +263,17 @@ class TreeBuilder {
}
}
- var tagDef = getHtmlTagDefinition(el.name);
- var parentEl = this._getParentElement();
- if (tagDef.requireExtraParent(isPresent(parentEl) ? parentEl.name : null)) {
+ const tagDef = getHtmlTagDefinition(el.name);
+ const {parent, container} = this._getParentElementSkippingContainers();
+
+ if (tagDef.requireExtraParent(isPresent(parent) ? parent.name : null)) {
var newParent = new HtmlElementAst(
- tagDef.parentToAdd, [], [el], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
- this._addToParent(newParent);
- this.elementStack.push(newParent);
- this.elementStack.push(el);
- } else {
- this._addToParent(el);
- this.elementStack.push(el);
+ tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
+ this._insertBeforeContainer(parent, container, newParent);
}
+
+ this._addToParent(el);
+ this.elementStack.push(el);
}
private _consumeEndTag(endTagToken: HtmlToken) {
@@ -330,6 +325,25 @@ class TreeBuilder {
return this.elementStack.length > 0 ? ListWrapper.last(this.elementStack) : null;
}
+ /**
+ * Returns the parent in the DOM and the container.
+ *
+ * `` elements are skipped as they are not rendered as DOM element.
+ */
+ private _getParentElementSkippingContainers():
+ {parent: HtmlElementAst, container: HtmlElementAst} {
+ let container: HtmlElementAst = null;
+
+ for (let i = this.elementStack.length - 1; i >= 0; i--) {
+ if (this.elementStack[i].name !== 'ng-container') {
+ return { parent: this.elementStack[i], container }
+ }
+ container = this.elementStack[i];
+ }
+
+ return {parent: ListWrapper.last(this.elementStack), container};
+ }
+
private _addToParent(node: HtmlAst) {
var parent = this._getParentElement();
if (isPresent(parent)) {
@@ -338,6 +352,31 @@ class TreeBuilder {
this.rootNodes.push(node);
}
}
+
+ /**
+ * Insert a node between the parent and the container.
+ * When no container is given, the node is appended as a child of the parent.
+ * Also updates the element stack accordingly.
+ *
+ * @internal
+ */
+ private _insertBeforeContainer(
+ parent: HtmlElementAst, container: HtmlElementAst, node: HtmlElementAst) {
+ if (!container) {
+ this._addToParent(node);
+ this.elementStack.push(node);
+ } else {
+ if (parent) {
+ // replace the container with the new node in the children
+ let index = parent.children.indexOf(container);
+ parent.children[index] = node;
+ } else {
+ this.rootNodes.push(node);
+ }
+ node.children.push(container);
+ this.elementStack.splice(this.elementStack.indexOf(container), 0, node);
+ }
+ }
}
function getElementFullName(
diff --git a/modules/@angular/compiler/src/util.ts b/modules/@angular/compiler/src/util.ts
index 56bee5731a..2efbef948d 100644
--- a/modules/@angular/compiler/src/util.ts
+++ b/modules/@angular/compiler/src/util.ts
@@ -1,21 +1,15 @@
import {StringMapWrapper} from './facade/collection';
-import {IS_DART, Math, StringWrapper, isArray, isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
+import {IS_DART, StringWrapper, isArray, isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
export var MODULE_SUFFIX = IS_DART ? '.dart' : '';
var CAMEL_CASE_REGEXP = /([A-Z])/g;
-var DASH_CASE_REGEXP = /-([a-z])/g;
export function camelCaseToDashCase(input: string): string {
return StringWrapper.replaceAllMapped(
input, CAMEL_CASE_REGEXP, (m: string[]) => { return '-' + m[1].toLowerCase(); });
}
-export function dashCaseToCamelCase(input: string): string {
- return StringWrapper.replaceAllMapped(
- input, DASH_CASE_REGEXP, (m: string[]) => { return m[1].toUpperCase(); });
-}
-
export function splitAtColon(input: string, defaultValues: string[]): string[] {
var parts = StringWrapper.split(input.trim(), /\s*:\s*/g);
if (parts.length > 1) {
diff --git a/modules/@angular/compiler/test/html_ast_spec_utils.ts b/modules/@angular/compiler/test/html_ast_spec_utils.ts
index b5fbb59ec5..901a75d9f4 100644
--- a/modules/@angular/compiler/test/html_ast_spec_utils.ts
+++ b/modules/@angular/compiler/test/html_ast_spec_utils.ts
@@ -1,6 +1,6 @@
import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '@angular/compiler/src/html_ast';
-import {HtmlParseTreeResult, HtmlParser, HtmlTreeError} from '@angular/compiler/src/html_parser';
-import {ParseError, ParseLocation} from '@angular/compiler/src/parse_util';
+import {HtmlParseTreeResult} from '@angular/compiler/src/html_parser';
+import {ParseLocation} from '@angular/compiler/src/parse_util';
import {BaseException} from '../src/facade/exceptions';
diff --git a/modules/@angular/compiler/test/html_parser_spec.ts b/modules/@angular/compiler/test/html_parser_spec.ts
index 248b6cf180..e2ca0d6987 100644
--- a/modules/@angular/compiler/test/html_parser_spec.ts
+++ b/modules/@angular/compiler/test/html_parser_spec.ts
@@ -2,6 +2,7 @@ import {HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpan
import {HtmlTokenType} from '@angular/compiler/src/html_lexer';
import {HtmlParseTreeResult, HtmlParser, HtmlTreeError} from '@angular/compiler/src/html_parser';
import {ParseError} from '@angular/compiler/src/parse_util';
+import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './html_ast_spec_utils';
@@ -111,12 +112,42 @@ export function main() {
'',
'TestComp')))
.toEqual([
- [HtmlElementAst, 'table', 0], [HtmlElementAst, 'thead', 1],
- [HtmlElementAst, 'tr', 2], [HtmlAttrAst, 'head', ''], [HtmlElementAst, 'tbody', 1],
- [HtmlElementAst, 'tr', 2], [HtmlAttrAst, 'noparent', ''],
- [HtmlElementAst, 'tbody', 1], [HtmlElementAst, 'tr', 2], [HtmlAttrAst, 'body', ''],
- [HtmlElementAst, 'tfoot', 1], [HtmlElementAst, 'tr', 2],
- [HtmlAttrAst, 'foot', '']
+ [HtmlElementAst, 'table', 0],
+ [HtmlElementAst, 'thead', 1],
+ [HtmlElementAst, 'tr', 2],
+ [HtmlAttrAst, 'head', ''],
+ [HtmlElementAst, 'tbody', 1],
+ [HtmlElementAst, 'tr', 2],
+ [HtmlAttrAst, 'noparent', ''],
+ [HtmlElementAst, 'tbody', 1],
+ [HtmlElementAst, 'tr', 2],
+ [HtmlAttrAst, 'body', ''],
+ [HtmlElementAst, 'tfoot', 1],
+ [HtmlElementAst, 'tr', 2],
+ [HtmlAttrAst, 'foot', ''],
+ ]);
+ });
+
+ it('should append the required parent considering ng-container', () => {
+ expect(humanizeDom(parser.parse(
+ '', 'TestComp')))
+ .toEqual([
+ [HtmlElementAst, 'table', 0],
+ [HtmlElementAst, 'tbody', 1],
+ [HtmlElementAst, 'ng-container', 2],
+ [HtmlElementAst, 'tr', 3],
+ ]);
+ });
+
+ it('should special case ng-container when adding a required parent', () => {
+ expect(humanizeDom(parser.parse(
+ '',
+ 'TestComp')))
+ .toEqual([
+ [HtmlElementAst, 'table', 0],
+ [HtmlElementAst, 'thead', 1],
+ [HtmlElementAst, 'ng-container', 2],
+ [HtmlElementAst, 'tr', 3],
]);
});
diff --git a/modules/@angular/core/testing/testing.ts b/modules/@angular/core/testing/testing.ts
index 1b4677972d..b062b25f62 100644
--- a/modules/@angular/core/testing/testing.ts
+++ b/modules/@angular/core/testing/testing.ts
@@ -8,7 +8,7 @@ import {TestInjector, async, getTestInjector, inject, injectAsync} from './test_
export {async, inject, injectAsync} from './test_injector';
-declare var global: any /** TODO #9100 */;
+declare var global: any;
var _global = (typeof window === 'undefined' ? global : window);