diff --git a/modules/change_detection/src/parser/parser.js b/modules/change_detection/src/parser/parser.js index 475fe0e2dc..e60460e49d 100644 --- a/modules/change_detection/src/parser/parser.js +++ b/modules/change_detection/src/parser/parser.js @@ -420,20 +420,39 @@ class _ParseAST { return positionals; } + /** + * An identifier, a keyword, a string with an optional `-` inbetween. + */ + expectTemplateBindingKey() { + var result = ''; + var operatorFound = false; + do { + result += this.expectIdentifierOrKeywordOrString(); + operatorFound = this.optionalOperator('-'); + if (operatorFound) { + result += '-'; + } + } while (operatorFound); + + return result.toString(); + } + parseTemplateBindings() { var bindings = []; while (this.index < this.tokens.length) { - var key = this.expectIdentifierOrKeywordOrString(); + var key = this.expectTemplateBindingKey(); this.optionalCharacter($COLON); var name = null; var expression = null; - if (this.optionalOperator("#")) { - name = this.expectIdentifierOrKeyword(); - } else { - var start = this.inputIndex; - var ast = this.parseExpression(); - var source = this.input.substring(start, this.inputIndex); - expression = new ASTWithSource(ast, source); + if (this.next !== EOF) { + if (this.optionalOperator("#")) { + name = this.expectIdentifierOrKeyword(); + } else { + var start = this.inputIndex; + var ast = this.parseExpression(); + var source = this.input.substring(start, this.inputIndex); + expression = new ASTWithSource(ast, source); + } } ListWrapper.push(bindings, new TemplateBinding(key, name, expression)); if (!this.optionalCharacter($SEMICOLON)) { diff --git a/modules/change_detection/test/parser/parser_spec.js b/modules/change_detection/test/parser/parser_spec.js index a4a1a555f2..9160157e9d 100644 --- a/modules/change_detection/test/parser/parser_spec.js +++ b/modules/change_detection/test/parser/parser_spec.js @@ -417,7 +417,12 @@ export function main() { expect(bindings).toEqual([]); }); - it('should only allow identifier, string, or keyword as keys', () => { + it('should parse a string without a value', () => { + var bindings = parseTemplateBindings('a'); + expect(keys(bindings)).toEqual(['a']); + }); + + it('should only allow identifier, string, or keyword including dashes as keys', () => { var bindings = parseTemplateBindings("a:'b'"); expect(keys(bindings)).toEqual(['a']); @@ -427,6 +432,9 @@ export function main() { bindings = parseTemplateBindings("\"a\":'b'"); expect(keys(bindings)).toEqual(['a']); + bindings = parseTemplateBindings("a-b:'c'"); + expect(keys(bindings)).toEqual(['a-b']); + expect( () => { parseTemplateBindings('(:0'); }).toThrowError(new RegExp('expected identifier, keyword, or string')); diff --git a/modules/core/src/compiler/pipeline/view_splitter.js b/modules/core/src/compiler/pipeline/view_splitter.js index c0be92ff39..ba3fa1b360 100644 --- a/modules/core/src/compiler/pipeline/view_splitter.js +++ b/modules/core/src/compiler/pipeline/view_splitter.js @@ -79,8 +79,10 @@ export class ViewSplitter extends CompileStep { var binding = bindings[i]; if (isPresent(binding.name)) { compileElement.addVariableBinding(binding.key, binding.name); - } else { + } else if (isPresent(binding.expression)) { compileElement.addPropertyBinding(binding.key, binding.expression); + } else { + compileElement.element.setAttribute(binding.key, ''); } } } diff --git a/modules/core/test/compiler/pipeline/view_splitter_spec.js b/modules/core/test/compiler/pipeline/view_splitter_spec.js index 3aea463728..a8818f1853 100644 --- a/modules/core/test/compiler/pipeline/view_splitter_spec.js +++ b/modules/core/test/compiler/pipeline/view_splitter_spec.js @@ -72,6 +72,14 @@ export function main() { expect(results[1].variableBindings).toEqual(MapWrapper.createFromStringMap({'varName': 'mapName'})); }); + it('should add entries without value as attribute to the element', () => { + var rootElement = createElement('