Repository: prettier/prettier (JavaScript)
Date: 2018-12-18 14:52:56+00:00
Task type: Bug Fix

Issue:
HTML Angular parser removes necessary parentheses causing early block closing
**Prettier 1.15.3**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEAeADgPgDpQAQF7DC6Fl4DkAJnADZzwWnksA+eMATgIZQDOtbvCREA7gAshIgBQUEMAJaK4fCnnZdeAoXBHBIAV1gjunHgE8AdPSgBzGOIC+ASkfMCjt1FQB6LCAAaEAh0RWg+ZFBTTghRAAVTBAiUbgA3CAUqQJAAIx4wAGsGAGV0bjAFO2QAM25aPjgg8RgAW1oAdXElFTKwOGKkpQVUpXNkcD4IoMqGzhg4nlsW7hq6hqCAKz4ADwAhfKKYYu4WuAAZSrhV+saQLe3iytt6AEUDCHhr9ZAyzlnx3i2AyCTjZdCcSowdqZBzIAAcAAYguCIA12jx0ONwSo4JxUlcgpw4ABHAwKIkLbhLFZIWo3IINFoKZBcAy3PhPV7vT60ta3GDcHLQqiwpAAJiCmgUtCeAGEIC1luMoNACSADA0ACqC5J0hqeIA)
```sh
--no-bracket-spacing
--parser angular
```

**Input:**
```html
<p>
    {{
        'delete'
            | translate: {what: ('entities' | translate: {count: array.length})}
    }}
</p>
```

**Output:**
```html
<p>
  {{
    "delete" | translate: {what: "entities" | translate: {count: array.length}}
  }}
</p>

```

**Expected behavior:**
The parentheses should be kept to prevent angular from parsing the two closing curly brackets as the end of the block


Issue discussion:
Angular doesn't parse `} }` as `}}`. There is a white space in between.
Yes, my bad, I forgot to add the --no-bracket-spacing option. I've updated the original comment and the playground link accordingly.
It looks like we need to force `bracketSpacing: true` for interpolations.

Solution patch:
diff --git a/src/common/fast-path.js b/src/common/fast-path.js
index eb678a9a10d4..4c152b2f8db5 100644
--- a/src/common/fast-path.js
+++ b/src/common/fast-path.js
@@ -29,17 +29,18 @@ FastPath.prototype.getValue = function getValue() {
 };
 
 function getNodeHelper(path, count) {
-  const s = path.stack;
-
-  for (let i = s.length - 1; i >= 0; i -= 2) {
-    const value = s[i];
+  const stackIndex = getNodeStackIndexHelper(path.stack, count);
+  return stackIndex === -1 ? null : path.stack[stackIndex];
+}
 
+function getNodeStackIndexHelper(stack, count) {
+  for (let i = stack.length - 1; i >= 0; i -= 2) {
+    const value = stack[i];
     if (value && !Array.isArray(value) && --count < 0) {
-      return value;
+      return i;
     }
   }
-
-  return null;
+  return -1;
 }
 
 FastPath.prototype.getNode = function getNode(count) {
@@ -70,6 +71,14 @@ FastPath.prototype.call = function call(callback /*, name1, name2, ... */) {
   return result;
 };
 
+FastPath.prototype.callParent = function callParent(callback, count) {
+  const stackIndex = getNodeStackIndexHelper(this.stack, ~~count + 1);
+  const parentValues = this.stack.splice(stackIndex + 1);
+  const result = callback(this);
+  Array.prototype.push.apply(this.stack, parentValues);
+  return result;
+};
+
 // Similar to FastPath.prototype.call, except that the value obtained by
 // accessing this.getValue()[name1][name2]... should be array-like. The
 // callback will be called with a reference to this path object for each
diff --git a/src/language-html/printer-html.js b/src/language-html/printer-html.js
index c9bcada1a821..896b54a3715a 100644
--- a/src/language-html/printer-html.js
+++ b/src/language-html/printer-html.js
@@ -85,11 +85,16 @@ function embed(path, print, textToDoc, options) {
               line,
               textToDoc(
                 node.value,
-                options.parser === "angular"
-                  ? { parser: "__ng_interpolation", trailingComma: "none" }
-                  : options.parser === "vue"
-                  ? { parser: "__vue_expression" }
-                  : { parser: "__js_expression" }
+                Object.assign(
+                  {
+                    __isInHtmlInterpolation: true // to avoid unexpected `}}`
+                  },
+                  options.parser === "angular"
+                    ? { parser: "__ng_interpolation", trailingComma: "none" }
+                    : options.parser === "vue"
+                    ? { parser: "__vue_expression" }
+                    : { parser: "__js_expression" }
+                )
               )
             ])
           ),
@@ -1043,7 +1048,10 @@ function printEmbeddedAttributeValue(node, originalTextToDoc, options) {
                   indent(
                     concat([
                       line,
-                      ngTextToDoc(part, { parser: "__ng_interpolation" })
+                      ngTextToDoc(part, {
+                        parser: "__ng_interpolation",
+                        __isInHtmlInterpolation: true // to avoid unexpected `}}`
+                      })
                     ])
                   ),
                   line,
diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js
index 68a92cced7b0..402fee361926 100644
--- a/src/language-js/needs-parens.js
+++ b/src/language-js/needs-parens.js
@@ -57,6 +57,16 @@ function needsParens(path, options) {
     return false;
   }
 
+  // to avoid unexpected `}}` in HTML interpolations
+  if (
+    options.__isInHtmlInterpolation &&
+    !options.bracketSpacing &&
+    endsWithRightBracket(node) &&
+    isFollowedByRightBracket(path)
+  ) {
+    return true;
+  }
+
   // Only statements don't need parentheses.
   if (isStatement(node)) {
     return false;
@@ -688,4 +698,55 @@ function isStatement(node) {
   );
 }
 
+function endsWithRightBracket(node) {
+  switch (node.type) {
+    case "ObjectExpression":
+      return true;
+    default:
+      return false;
+  }
+}
+
+function isFollowedByRightBracket(path) {
+  const node = path.getValue();
+  const parent = path.getParentNode();
+  const name = path.getName();
+  switch (parent.type) {
+    case "NGPipeExpression":
+      if (
+        typeof name === "number" &&
+        parent.arguments[name] === node &&
+        parent.arguments.length - 1 === name
+      ) {
+        return path.callParent(isFollowedByRightBracket);
+      }
+      break;
+    case "ObjectProperty":
+      if (name === "value") {
+        const parentParent = path.getParentNode(1);
+        return (
+          parentParent.properties[parentParent.properties.length - 1] === parent
+        );
+      }
+      break;
+    case "BinaryExpression":
+    case "LogicalExpression":
+      if (name === "right") {
+        return path.callParent(isFollowedByRightBracket);
+      }
+      break;
+    case "ConditionalExpression":
+      if (name === "alternate") {
+        return path.callParent(isFollowedByRightBracket);
+      }
+      break;
+    case "UnaryExpression":
+      if (parent.prefix) {
+        return path.callParent(isFollowedByRightBracket);
+      }
+      break;
+  }
+  return false;
+}
+
 module.exports = needsParens;


Test patch:
diff --git a/tests/html_angular/__snapshots__/jsfmt.spec.js.snap b/tests/html_angular/__snapshots__/jsfmt.spec.js.snap
index 2bfac28a7985..ec5d2dd291b4 100644
--- a/tests/html_angular/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/html_angular/__snapshots__/jsfmt.spec.js.snap
@@ -61,6 +61,21 @@ printWidth: 80
 ================================================================================
 `;
 
+exports[`attr-name.component.html 5`] = `
+====================================options=====================================
+bracketSpacing: false
+parsers: ["angular"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<div someDirective itemType="x"></div>
+
+=====================================output=====================================
+<div someDirective itemType="x"></div>
+
+================================================================================
+`;
+
 exports[`attributes.component.html 1`] = `
 ====================================options=====================================
 parsers: ["angular"]
@@ -1229,6 +1244,240 @@ printWidth: 80
 ================================================================================
 `;
 
+exports[`attributes.component.html 5`] = `
+====================================options=====================================
+bracketSpacing: false
+parsers: ["angular"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<div
+    bindon-target=" a | b : c "
+    [(target)]=" a | b : c "
+    bind-target=" a | b : c "
+    [target]=" a | b : c "
+    [target]=" 0 - 1 "
+    [target]=" - 1 "
+    [target]=" a ? 1 : 2 "
+    [target]=" "
+    [target]=" a ( 1 ) ( 2 ) "
+    [target]=" a [ b ] "
+    [target]=" [ 1 ] "
+    [target]=" { 'a' : 1 } "
+    [target]=" { a : 1 } "
+    [target]=" { 
+      trailingComma : 'notAllowed'
+    }"
+    [target]=" [ 
+      longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong
+    ]"
+    [target]=" true "
+    [target]=" undefined "
+    [target]=" null "
+    [target]=" ( 1 ) "
+    [target]=" 1 "
+    [target]="  'hello' "
+    [target]=" a ( 1 , 2 ) "
+    [target]=" a . b ( 1 , 2 ) "
+    [target]=" x ! "
+    [target]=" ! x "
+    [target]=" ( ( a ) ) "
+    [target]=" a "
+    [target]=" a // hello "
+    [target]=" a . b "
+    [target]=" javascript : 'hello world' "
+    [target]=" a ?. b ( ) "
+    [target]=" a ?. b "
+    [target]=" this . a "
+    on-target=" a = 1 "
+    (target)=" a = 1 "
+    (target)=" a . b = 1 "
+    (target)=" a [ b ] = 1 "
+    (target)=" a // hello "
+    (target)=" a ; b "
+    (event)="  0  "
+    (event)="  a.b  "
+    (event)="  hello  "
+    (event)="  hello()  "
+    (event)="  a && b  "
+    (event)="  a && b()  "
+    (event)="  foo = $event  "
+    (event)="  foo == $event  "
+    *ngIf=" something?true:false    "
+    *ngFor="  let     hero     of     heroes"
+    *ngFor="  let    hero     of[1,2,3,666666666666666666666666666666666666]; let i=index"
+    *ngFor="     let hero     of     heroes;     trackBy    :    trackByHeroes     "
+    *ngFor=" let     item   of   items   ;    index    as   i  ; trackBy  :     trackByFn"
+    *ngFor="  let    hero     of heroes; let i  =  index"
+    *ngFor="  let    hero     of heroes; value     myValue"
+    *ngIf=" condition  ;  else  elseBlock "
+    *ngIf=" condition  ;  then  thenBlock  else  elseBlock "
+    *ngIf=" condition  as  value  ;  else  elseBlock "
+    *directive=" let hero "
+    *directive=" let hero = hello "
+    *directive=" let hero of heroes "
+    *directive=" let hero ; of : heroes "
+    *directive=" a "
+    *directive=" a as b "
+    *directive=" a , b "
+    *directive=" a ; b "
+    *directive=" a ; b c "
+    *directive=" a ; b : c "
+    *directive=" a ; b : c as d "
+    *directive=" a ; b as c "
+    *ngIf="listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3"
+    *ngIf="listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3; else hello"
+    (target)="listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3"
+    [target]="listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3"
+    [target]="{ longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong: true }"
+    [error]="'We couldn\\\\\\'t find anything with that name.'"
+    *ngIf="form.controls.details?.controls.amount?.errors.min"
+    [ngClass]="
+    {'btn-success': (dialog$ | async).level === dialogLevelEnum.SUCCESS,
+      'btn-warning': (dialog$ | async).level === dialogLevelEnum.WARNING,
+      'btn-svg': (dialog$ | async).level === dialogLevelEnum.DANGER}"
+    [stickout]="+toolAssembly.stickoutMm"
+    test='{{ "test" | translate }}'
+    test='foo {{       invalid invalid       }} bar {{       valid       }} baz'
+    test='foo
+    
+    {{       invalid
+             invalid       }} 
+    
+    bar 
+    
+          {{       valid       }} 
+    
+    baz'
+></div>
+
+=====================================output=====================================
+<div
+  bindon-target="a | b: c"
+  [(target)]="a | b: c"
+  bind-target="a | b: c"
+  [target]="a | b: c"
+  [target]="0 - 1"
+  [target]="-1"
+  [target]="a ? 1 : 2"
+  [target]=""
+  [target]="a(1)(2)"
+  [target]="a[b]"
+  [target]="[1]"
+  [target]="{a: 1}"
+  [target]="{a: 1}"
+  [target]="{
+    trailingComma: 'notAllowed'
+  }"
+  [target]="[
+    longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong
+  ]"
+  [target]="true"
+  [target]="undefined"
+  [target]="null"
+  [target]="1"
+  [target]="1"
+  [target]="'hello'"
+  [target]="a(1, 2)"
+  [target]="a.b(1, 2)"
+  [target]="x!"
+  [target]="!x"
+  [target]="a"
+  [target]="a"
+  [target]="a // hello"
+  [target]="a.b"
+  [target]="javascript: 'hello world' "
+  [target]="a?.b()"
+  [target]="a?.b"
+  [target]="this.a"
+  on-target="a = 1"
+  (target)="a = 1"
+  (target)="a.b = 1"
+  (target)="a[b] = 1"
+  (target)="(a) // hello"
+  (target)="(a); (b)"
+  (event)="(0)"
+  (event)="(a.b)"
+  (event)="(hello)"
+  (event)="hello()"
+  (event)="(a && b)"
+  (event)="a && b()"
+  (event)="foo = $event"
+  (event)="(foo == $event)"
+  *ngIf="something ? true : false"
+  *ngFor="let hero of heroes"
+  *ngFor="
+    let hero of [1, 2, 3, 666666666666666666666666666666666666];
+    let i = index
+  "
+  *ngFor="let hero of heroes; trackBy: trackByHeroes"
+  *ngFor="let item of items; index as i; trackBy: trackByFn"
+  *ngFor="let hero of heroes; let i = index"
+  *ngFor="let hero of heroes; value: myValue"
+  *ngIf="condition; else elseBlock"
+  *ngIf="condition; then thenBlock; else elseBlock"
+  *ngIf="condition as value; else elseBlock"
+  *directive="let hero"
+  *directive="let hero = hello"
+  *directive="let hero of heroes"
+  *directive="let hero of heroes"
+  *directive="a"
+  *directive="a as b"
+  *directive="a; b"
+  *directive="a; b"
+  *directive="a; b: c"
+  *directive="a; b: c"
+  *directive="a; b: c as d"
+  *directive="a; b as c"
+  *ngIf="
+    listRow.NextScheduledSendStatus == 1 ||
+    listRow.NextScheduledSendStatus == 2 ||
+    listRow.NextScheduledSendStatus == 3
+  "
+  *ngIf="
+    listRow.NextScheduledSendStatus == 1 ||
+      listRow.NextScheduledSendStatus == 2 ||
+      listRow.NextScheduledSendStatus == 3;
+    else hello
+  "
+  (target)="
+    (listRow.NextScheduledSendStatus == 1 ||
+      listRow.NextScheduledSendStatus == 2 ||
+      listRow.NextScheduledSendStatus == 3)
+  "
+  [target]="
+    listRow.NextScheduledSendStatus == 1 ||
+    listRow.NextScheduledSendStatus == 2 ||
+    listRow.NextScheduledSendStatus == 3
+  "
+  [target]="{
+    longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong: true
+  }"
+  [error]="'We couldn\\\\\\'t find anything with that name.'"
+  *ngIf="form.controls.details?.controls.amount?.errors.min"
+  [ngClass]="{
+    'btn-success': (dialog$ | async).level === dialogLevelEnum.SUCCESS,
+    'btn-warning': (dialog$ | async).level === dialogLevelEnum.WARNING,
+    'btn-svg': (dialog$ | async).level === dialogLevelEnum.DANGER
+  }"
+  [stickout]="+toolAssembly.stickoutMm"
+  test="{{ 'test' | translate }}"
+  test="foo {{       invalid invalid       }} bar {{ valid }} baz"
+  test="foo
+    
+    {{       invalid
+             invalid       }} 
+    
+    bar 
+    
+          {{ valid }} 
+    
+    baz"
+></div>
+
+================================================================================
+`;
+
 exports[`first-lf.component.html 1`] = `
 ====================================options=====================================
 parsers: ["angular"]
@@ -1679,24 +1928,117 @@ printWidth: 80
 ================================================================================
 `;
 
-exports[`ignore-attribute.component.html 1`] = `
+exports[`first-lf.component.html 5`] = `
 ====================================options=====================================
+bracketSpacing: false
 parsers: ["angular"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
-<div
-    bindon-target=" a | b : c "
-    [(target)]=" a | b : c "
-    bind-target=" a | b : c "
-></div>
-<!--  prettier-ignore-attribute -->
-<div
-    bindon-target=" a | b : c "
-    [(target)]=" a | b : c "
-    bind-target=" a | b : c "
-></div>
-<!--  prettier-ignore-attribute bind-target -->
+<textarea>{{ generatedDiscountCodes }}</textarea>
+<textarea>
+{{ generatedDiscountCodes }}</textarea>
+<textarea>
+    {{ generatedDiscountCodes }}</textarea>
+<textarea>  
+{{ generatedDiscountCodes }}</textarea>
+<textarea>  
+    {{ generatedDiscountCodes }}</textarea>
+<textarea>{{ generatedDiscountCodes }}123</textarea>
+<textarea>
+{{ generatedDiscountCodes }}123</textarea>
+<textarea>
+    {{ generatedDiscountCodes }}123</textarea>
+<textarea>  
+{{ generatedDiscountCodes }}123</textarea>
+<textarea>  
+    {{ generatedDiscountCodes }}123</textarea>
+<textarea type="text">{{ generatedDiscountCodes }}</textarea>
+<textarea type="text">
+{{ generatedDiscountCodes }}</textarea>
+<textarea type="text">
+    {{ generatedDiscountCodes }}</textarea>
+<textarea type="text">  
+{{ generatedDiscountCodes }}</textarea>
+<textarea type="text">  
+    {{ generatedDiscountCodes }}</textarea>
+<textarea type="text">{{ generatedDiscountCodes }}123</textarea>
+<textarea type="text">
+{{ generatedDiscountCodes }}123</textarea>
+<textarea type="text">
+    {{ generatedDiscountCodes }}123</textarea>
+<textarea type="text">  
+{{ generatedDiscountCodes }}123</textarea>
+<textarea type="text">  
+    {{ generatedDiscountCodes }}123</textarea>
+
+=====================================output=====================================
+<textarea>{{ generatedDiscountCodes }}</textarea>
+<textarea>{{ generatedDiscountCodes }}</textarea>
+<textarea>    {{ generatedDiscountCodes }}</textarea>
+<textarea>
+  
+{{ generatedDiscountCodes }}</textarea
+>
+<textarea>
+  
+    {{ generatedDiscountCodes }}</textarea
+>
+<textarea>{{ generatedDiscountCodes }}123</textarea>
+<textarea>{{ generatedDiscountCodes }}123</textarea>
+<textarea>    {{ generatedDiscountCodes }}123</textarea>
+<textarea>
+  
+{{ generatedDiscountCodes }}123</textarea
+>
+<textarea>
+  
+    {{ generatedDiscountCodes }}123</textarea
+>
+<textarea type="text">{{ generatedDiscountCodes }}</textarea>
+<textarea type="text">{{ generatedDiscountCodes }}</textarea>
+<textarea type="text">    {{ generatedDiscountCodes }}</textarea>
+<textarea type="text">
+  
+{{ generatedDiscountCodes }}</textarea
+>
+<textarea type="text">
+  
+    {{ generatedDiscountCodes }}</textarea
+>
+<textarea type="text">{{ generatedDiscountCodes }}123</textarea>
+<textarea type="text">{{ generatedDiscountCodes }}123</textarea>
+<textarea type="text">    {{ generatedDiscountCodes }}123</textarea>
+<textarea type="text">
+  
+{{ generatedDiscountCodes }}123</textarea
+>
+<textarea type="text">
+  
+    {{ generatedDiscountCodes }}123</textarea
+>
+
+================================================================================
+`;
+
+exports[`ignore-attribute.component.html 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<div
+    bindon-target=" a | b : c "
+    [(target)]=" a | b : c "
+    bind-target=" a | b : c "
+></div>
+<!--  prettier-ignore-attribute -->
+<div
+    bindon-target=" a | b : c "
+    [(target)]=" a | b : c "
+    bind-target=" a | b : c "
+></div>
+<!--  prettier-ignore-attribute bind-target -->
 <div
     bindon-target=" a | b : c "
     [(target)]=" a | b : c "
@@ -1937,6 +2279,65 @@ printWidth: 80
 ================================================================================
 `;
 
+exports[`ignore-attribute.component.html 5`] = `
+====================================options=====================================
+bracketSpacing: false
+parsers: ["angular"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<div
+    bindon-target=" a | b : c "
+    [(target)]=" a | b : c "
+    bind-target=" a | b : c "
+></div>
+<!--  prettier-ignore-attribute -->
+<div
+    bindon-target=" a | b : c "
+    [(target)]=" a | b : c "
+    bind-target=" a | b : c "
+></div>
+<!--  prettier-ignore-attribute bind-target -->
+<div
+    bindon-target=" a | b : c "
+    [(target)]=" a | b : c "
+    bind-target=" a | b : c "
+></div>
+<!--  prettier-ignore-attribute [(target)] bind-target -->
+<div
+    bindon-target=" a | b : c "
+    [(target)]=" a | b : c "
+    bind-target=" a | b : c "
+></div>
+
+=====================================output=====================================
+<div
+  bindon-target="a | b: c"
+  [(target)]="a | b: c"
+  bind-target="a | b: c"
+></div>
+<!--  prettier-ignore-attribute -->
+<div
+  bindon-target=" a | b : c "
+  [(target)]=" a | b : c "
+  bind-target=" a | b : c "
+></div>
+<!--  prettier-ignore-attribute bind-target -->
+<div
+  bindon-target="a | b: c"
+  [(target)]="a | b: c"
+  bind-target=" a | b : c "
+></div>
+<!--  prettier-ignore-attribute [(target)] bind-target -->
+<div
+  bindon-target="a | b: c"
+  [(target)]=" a | b : c "
+  bind-target=" a | b : c "
+></div>
+
+================================================================================
+`;
+
 exports[`interpolation.component.html 1`] = `
 ====================================options=====================================
 parsers: ["angular"]
@@ -1999,6 +2400,16 @@ printWidth: 80
   {{ aNormalValue | aPipe }}:
   <strong>{{ aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis }}</strong>
 </span>
+<p>
+  {{
+      'delete'
+          | translate: {what: ('entities' | translate: {count: array.length})}
+  }}
+</p>
+<p>{{ {a:1+{} } }}</p>
+<p>{{ {a:a==={} } }}</p>
+<p>{{ {a:!{} } }}</p>
+<p>{{ {a:a?b:{} } }}</p>
 
 =====================================output=====================================
 <div>{{ a | b: c }}</div>
@@ -2077,6 +2488,16 @@ printWidth: 80
     aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis
   }}</strong>
 </span>
+<p>
+  {{
+    "delete"
+      | translate: { what: "entities" | translate: { count: array.length } }
+  }}
+</p>
+<p>{{ { a: 1 + {} } }}</p>
+<p>{{ { a: a === {} } }}</p>
+<p>{{ { a: !{} } }}</p>
+<p>{{ { a: a ? b : {} } }}</p>
 
 ================================================================================
 `;
@@ -2144,6 +2565,16 @@ trailingComma: "es5"
   {{ aNormalValue | aPipe }}:
   <strong>{{ aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis }}</strong>
 </span>
+<p>
+  {{
+      'delete'
+          | translate: {what: ('entities' | translate: {count: array.length})}
+  }}
+</p>
+<p>{{ {a:1+{} } }}</p>
+<p>{{ {a:a==={} } }}</p>
+<p>{{ {a:!{} } }}</p>
+<p>{{ {a:a?b:{} } }}</p>
 
 =====================================output=====================================
 <div>{{ a | b: c }}</div>
@@ -2222,6 +2653,16 @@ trailingComma: "es5"
     aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis
   }}</strong>
 </span>
+<p>
+  {{
+    "delete"
+      | translate: { what: "entities" | translate: { count: array.length } }
+  }}
+</p>
+<p>{{ { a: 1 + {} } }}</p>
+<p>{{ { a: a === {} } }}</p>
+<p>{{ { a: !{} } }}</p>
+<p>{{ { a: a ? b : {} } }}</p>
 
 ================================================================================
 `;
@@ -2288,6 +2729,16 @@ printWidth: 1
   {{ aNormalValue | aPipe }}:
   <strong>{{ aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis }}</strong>
 </span>
+<p>
+  {{
+      'delete'
+          | translate: {what: ('entities' | translate: {count: array.length})}
+  }}
+</p>
+<p>{{ {a:1+{} } }}</p>
+<p>{{ {a:a==={} } }}</p>
+<p>{{ {a:!{} } }}</p>
+<p>{{ {a:a?b:{} } }}</p>
 
 =====================================output=====================================
 <div>
@@ -2528,6 +2979,55 @@ printWidth: 1
     }}</strong
   >
 </span>
+<p>
+  {{
+    "delete"
+      | translate
+        : {
+            what:
+              "entities"
+              | translate
+                : {
+                    count:
+                      array.length
+                  }
+          }
+  }}
+</p>
+<p>
+  {{
+    {
+      a:
+        1 +
+        {}
+    }
+  }}
+</p>
+<p>
+  {{
+    {
+      a:
+        a ===
+        {}
+    }
+  }}
+</p>
+<p>
+  {{
+    {
+      a: !{}
+    }
+  }}
+</p>
+<p>
+  {{
+    {
+      a: a
+        ? b
+        : {}
+    }
+  }}
+</p>
 
 ================================================================================
 `;
@@ -2595,6 +3095,16 @@ printWidth: 80
   {{ aNormalValue | aPipe }}:
   <strong>{{ aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis }}</strong>
 </span>
+<p>
+  {{
+      'delete'
+          | translate: {what: ('entities' | translate: {count: array.length})}
+  }}
+</p>
+<p>{{ {a:1+{} } }}</p>
+<p>{{ {a:a==={} } }}</p>
+<p>{{ {a:!{} } }}</p>
+<p>{{ {a:a?b:{} } }}</p>
 
 =====================================output=====================================
 <div>{{ a | b: c }}</div>
@@ -2678,52 +3188,227 @@ printWidth: 80
     {{ aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis }}
   </strong>
 </span>
+<p>
+  {{
+    "delete"
+      | translate: { what: "entities" | translate: { count: array.length } }
+  }}
+</p>
+<p>{{ { a: 1 + {} } }}</p>
+<p>{{ { a: a === {} } }}</p>
+<p>{{ { a: !{} } }}</p>
+<p>{{ { a: a ? b : {} } }}</p>
 
 ================================================================================
 `;
 
-exports[`real-world.component.html 1`] = `
+exports[`interpolation.component.html 5`] = `
 ====================================options=====================================
+bracketSpacing: false
 parsers: ["angular"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
-<!-- copied from: https://stackblitz.com/angular/ymdjlgmlavo -->
-
-<a id="toc"></a>
-<h1>Template Syntax</h1>
-<a href="#interpolation">Interpolation</a><br>
-<a href="#expression-context">Expression context</a><br>
-<a href="#statement-context">Statement context</a><br>
-<a href="#mental-model">Mental Model</a><br>
-<a href="#buttons">Buttons</a><br>
-<a href="#prop-vs-attrib">Properties vs. Attributes</a><br>
-<br>
-<a href="#property-binding">Property Binding</a><br>
-<div style="margin-left:8px">
-  <a href="#attribute-binding">Attribute Binding</a><br>
-  <a href="#class-binding">Class Binding</a><br>
-  <a href="#style-binding">Style Binding</a><br>
-</div>
-<br>
-<a href="#event-binding">Event Binding</a><br>
-<a href="#two-way">Two-way Binding</a><br>
-<br>
-<div>Directives</div>
-<div style="margin-left:8px">
-  <a href="#ngModel">NgModel (two-way) Binding</a><br>
-  <a href="#ngClass">NgClass Binding</a><br>
-  <a href="#ngStyle">NgStyle Binding</a><br>
-  <a href="#ngIf">NgIf</a><br>
-  <a href="#ngFor">NgFor</a><br>
-  <div style="margin-left:8px">
-    <a href="#ngFor-index">NgFor with index</a><br>
-    <a href="#ngFor-trackBy">NgFor with trackBy</a><br>
-  </div>
-  <a href="#ngSwitch">NgSwitch</a><br>
-</div>
-<br>
-<a href="#ref-vars">Template reference variables</a><br>
+<div>{{ a | b : c }}</div>
+<div>{{ 0 - 1 }}</div>
+<div>{{ - 1 }}</div>
+<div>{{ a ? 1 : 2 }}</div>
+<div>{{ a ( 1 ) ( 2 ) }}</div>
+<div>{{ a [ b ] }}</div>
+<div>{{ [ 1 ] }}</div>
+<div>{{ { 'a' : 1 } }}</div>
+<div>{{ { a : 1 } }}</div>
+<div>{{ true }}</div>
+<div>{{ undefined }}</div>
+<div>{{ null }}</div>
+<div>{{ ( 1 ) }}</div>
+<div>{{ 1 }}</div>
+<div>{{  'hello' }}</div>
+<div>{{ a ( 1 , 2 ) }}</div>
+<div>{{ a . b ( 1 , 2 ) }}</div>
+<div>{{ x ! }}</div>
+<div>{{ ! x }}</div>
+<div>{{ ( ( a ) ) }}</div>
+<div>{{ a }}</div>
+<div>{{ a // hello }}</div>
+<div>{{ a . b }}</div>
+<div>{{ a ?. b ( ) }}</div>
+<div>{{ a ?. b }}</div>
+<div>{{  a // hello  }}</div>
+<label for="transmissionLayoutRadioButton">{{
+"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
+}}</label>
+<label for="transmissionLayoutRadioButtontransmissionLayoutRadioButtontransmissionLayoutRadioButtontransmissionLayoutRadioButton">{{
+"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
+}}</label>
+<label for="transmissionLayoutRadioButton">{{
+"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
+}} </label>
+<label for="transmissionLayoutRadioButton"> {{
+"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
+}}</label>
+<label for="transmissionLayoutRadioButton"> {{
+"SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection
+}} </label>
+<div class="Nemo possimus non voluptates dicta accusamus id quia">{{copyTypes[options.copyType]}}</div>
+{{listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3}}
+<span
+><!--
+--><span
+  >{{a}}</span
+><!--
+--><span
+  >{{b}}</span
+><!--
+--></span>
+<span>
+  {{ aNormalValue | aPipe }}:
+  <strong>{{ aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis }}</strong>
+</span>
+<p>
+  {{
+      'delete'
+          | translate: {what: ('entities' | translate: {count: array.length})}
+  }}
+</p>
+<p>{{ {a:1+{} } }}</p>
+<p>{{ {a:a==={} } }}</p>
+<p>{{ {a:!{} } }}</p>
+<p>{{ {a:a?b:{} } }}</p>
+
+=====================================output=====================================
+<div>{{ a | b: c }}</div>
+<div>{{ 0 - 1 }}</div>
+<div>{{ -1 }}</div>
+<div>{{ a ? 1 : 2 }}</div>
+<div>{{ a(1)(2) }}</div>
+<div>{{ a[b] }}</div>
+<div>{{ [1] }}</div>
+<div>{{ {a: 1} }}</div>
+<div>{{ {a: 1} }}</div>
+<div>{{ true }}</div>
+<div>{{ undefined }}</div>
+<div>{{ null }}</div>
+<div>{{ 1 }}</div>
+<div>{{ 1 }}</div>
+<div>{{ "hello" }}</div>
+<div>{{ a(1, 2) }}</div>
+<div>{{ a.b(1, 2) }}</div>
+<div>{{ x! }}</div>
+<div>{{ !x }}</div>
+<div>{{ a }}</div>
+<div>{{ a }}</div>
+<div>{{ a // hello }}</div>
+<div>{{ a.b }}</div>
+<div>{{ a?.b() }}</div>
+<div>{{ a?.b }}</div>
+<div>{{ a // hello }}</div>
+<label for="transmissionLayoutRadioButton">{{
+  "SearchSelection.transmissionLayoutRadioButton"
+    | localize: localizationSection
+}}</label>
+<label
+  for="transmissionLayoutRadioButtontransmissionLayoutRadioButtontransmissionLayoutRadioButtontransmissionLayoutRadioButton"
+  >{{
+    "SearchSelection.transmissionLayoutRadioButton"
+      | localize: localizationSection
+  }}</label
+>
+<label for="transmissionLayoutRadioButton"
+  >{{
+    "SearchSelection.transmissionLayoutRadioButton"
+      | localize: localizationSection
+  }}
+</label>
+<label for="transmissionLayoutRadioButton">
+  {{
+    "SearchSelection.transmissionLayoutRadioButton"
+      | localize: localizationSection
+  }}</label
+>
+<label for="transmissionLayoutRadioButton">
+  {{
+    "SearchSelection.transmissionLayoutRadioButton"
+      | localize: localizationSection
+  }}
+</label>
+<div class="Nemo possimus non voluptates dicta accusamus id quia">
+  {{ copyTypes[options.copyType] }}
+</div>
+{{
+  listRow.NextScheduledSendStatus == 1 ||
+    listRow.NextScheduledSendStatus == 2 ||
+    listRow.NextScheduledSendStatus == 3
+}}
+<span
+  ><!--
+--><span>{{ a }}</span
+  ><!--
+--><span>{{ b }}</span
+  ><!--
+--></span>
+<span>
+  {{ aNormalValue | aPipe }}:
+  <strong>{{
+    aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis
+  }}</strong>
+</span>
+<p>
+  {{
+    "delete"
+      | translate: {what: "entities" | translate: ({count: array.length})}
+  }}
+</p>
+<p>{{ {a: 1 + ({})} }}</p>
+<p>{{ {a: a === ({})} }}</p>
+<p>{{ {a: !({})} }}</p>
+<p>{{ {a: a ? b : ({})} }}</p>
+
+================================================================================
+`;
+
+exports[`real-world.component.html 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<!-- copied from: https://stackblitz.com/angular/ymdjlgmlavo -->
+
+<a id="toc"></a>
+<h1>Template Syntax</h1>
+<a href="#interpolation">Interpolation</a><br>
+<a href="#expression-context">Expression context</a><br>
+<a href="#statement-context">Statement context</a><br>
+<a href="#mental-model">Mental Model</a><br>
+<a href="#buttons">Buttons</a><br>
+<a href="#prop-vs-attrib">Properties vs. Attributes</a><br>
+<br>
+<a href="#property-binding">Property Binding</a><br>
+<div style="margin-left:8px">
+  <a href="#attribute-binding">Attribute Binding</a><br>
+  <a href="#class-binding">Class Binding</a><br>
+  <a href="#style-binding">Style Binding</a><br>
+</div>
+<br>
+<a href="#event-binding">Event Binding</a><br>
+<a href="#two-way">Two-way Binding</a><br>
+<br>
+<div>Directives</div>
+<div style="margin-left:8px">
+  <a href="#ngModel">NgModel (two-way) Binding</a><br>
+  <a href="#ngClass">NgClass Binding</a><br>
+  <a href="#ngStyle">NgStyle Binding</a><br>
+  <a href="#ngIf">NgIf</a><br>
+  <a href="#ngFor">NgFor</a><br>
+  <div style="margin-left:8px">
+    <a href="#ngFor-index">NgFor with index</a><br>
+    <a href="#ngFor-trackBy">NgFor with trackBy</a><br>
+  </div>
+  <a href="#ngSwitch">NgSwitch</a><br>
+</div>
+<br>
+<a href="#ref-vars">Template reference variables</a><br>
 <a href="#inputs-and-outputs">Inputs and outputs</a><br>
 <a href="#pipes">Pipes</a><br>
 <a href="#safe-navigation-operator">Safe navigation operator <i>?.</i></a><br>
@@ -10809,38 +11494,1567 @@ can be found in the LICENSE file at http://angular.io/license
 ================================================================================
 `;
 
-exports[`tag-name.component.html 1`] = `
+exports[`real-world.component.html 5`] = `
 ====================================options=====================================
+bracketSpacing: false
 parsers: ["angular"]
 printWidth: 80
                                                                                 | printWidth
 =====================================input======================================
-<Table></Table>
+<!-- copied from: https://stackblitz.com/angular/ymdjlgmlavo -->
 
-=====================================output=====================================
-<Table></Table>
+<a id="toc"></a>
+<h1>Template Syntax</h1>
+<a href="#interpolation">Interpolation</a><br>
+<a href="#expression-context">Expression context</a><br>
+<a href="#statement-context">Statement context</a><br>
+<a href="#mental-model">Mental Model</a><br>
+<a href="#buttons">Buttons</a><br>
+<a href="#prop-vs-attrib">Properties vs. Attributes</a><br>
+<br>
+<a href="#property-binding">Property Binding</a><br>
+<div style="margin-left:8px">
+  <a href="#attribute-binding">Attribute Binding</a><br>
+  <a href="#class-binding">Class Binding</a><br>
+  <a href="#style-binding">Style Binding</a><br>
+</div>
+<br>
+<a href="#event-binding">Event Binding</a><br>
+<a href="#two-way">Two-way Binding</a><br>
+<br>
+<div>Directives</div>
+<div style="margin-left:8px">
+  <a href="#ngModel">NgModel (two-way) Binding</a><br>
+  <a href="#ngClass">NgClass Binding</a><br>
+  <a href="#ngStyle">NgStyle Binding</a><br>
+  <a href="#ngIf">NgIf</a><br>
+  <a href="#ngFor">NgFor</a><br>
+  <div style="margin-left:8px">
+    <a href="#ngFor-index">NgFor with index</a><br>
+    <a href="#ngFor-trackBy">NgFor with trackBy</a><br>
+  </div>
+  <a href="#ngSwitch">NgSwitch</a><br>
+</div>
+<br>
+<a href="#ref-vars">Template reference variables</a><br>
+<a href="#inputs-and-outputs">Inputs and outputs</a><br>
+<a href="#pipes">Pipes</a><br>
+<a href="#safe-navigation-operator">Safe navigation operator <i>?.</i></a><br>
+<a href="#non-null-assertion-operator">Non-null assertion operator <i>!.</i></a><br>
+<a href="#enums">Enums</a><br>
 
-================================================================================
-`;
+<!-- Interpolation and expressions -->
+<hr><h2 id="interpolation">Interpolation</h2>
 
-exports[`tag-name.component.html 2`] = `
-====================================options=====================================
-parsers: ["angular"]
-printWidth: 80
-trailingComma: "es5"
-                                                                                | printWidth
-=====================================input======================================
-<Table></Table>
+<p>My current hero is {{currentHero.name}}</p>
 
-=====================================output=====================================
-<Table></Table>
+<h3>
+  {{title}}
+  <img src="{{heroImageUrl}}" style="height:30px">
+</h3>
 
-================================================================================
-`;
+<!-- "The sum of 1 + 1 is 2" -->
+<p>The sum of 1 + 1 is {{1 + 1}}</p>
 
-exports[`tag-name.component.html 3`] = `
-====================================options=====================================
-parsers: ["angular"]
+<!-- "The sum of 1 + 1 is not 4" -->
+<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
+
+<a class="to-toc" href="#toc">top</a>
+
+<hr><h2 id="expression-context">Expression context</h2>
+
+<p>Component expression context (&#123;&#123;title&#125;&#125;, [hidden]="isUnchanged")</p>
+<div class="context">
+  {{title}}
+  <span [hidden]="isUnchanged">changed</span>
+</div>
+
+
+<p>Template input variable expression context (let hero)</p>
+<!-- template hides the following; plenty of examples later -->
+<ng-template>
+  <div *ngFor="let hero of heroes">{{hero.name}}</div>
+</ng-template>
+
+<p>Template reference variable expression context (#heroInput)</p>
+<div (keyup)="0" class="context">
+  Type something:
+  <input #heroInput> {{heroInput.value}}
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<hr><h2 id="statement-context">Statement context</h2>
+
+<p>Component statement context ( (click)="onSave() )
+<div class="context">
+  <button (click)="deleteHero()">Delete hero</button>
+</div>
+
+<p>Template $event statement context</p>
+<div class="context">
+  <button (click)="onSave($event)">Save</button>
+</div>
+
+<p>Template input variable statement context (let hero)</p>
+<!-- template hides the following; plenty of examples later -->
+<div class="context">
+  <button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
+</div>
+
+<p>Template reference variable statement context (#heroForm)</p>
+<div class="context">
+  <form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- New Mental Model -->
+<hr><h2 id="mental-model">New Mental Model</h2>
+
+<!--<img src="http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png">-->
+<!-- Public Domain terms of use: http://www.wpclipart.com/terms.html -->
+<div class="special">Mental Model</div>
+<img src="assets/images/hero.png">
+<button disabled>Save</button>
+<br><br>
+
+<div>
+  <!-- Normal HTML -->
+  <div class="special">Mental Model</div>
+  <!-- Wow! A new element! -->
+  <app-hero-detail></app-hero-detail>
+</div>
+<br><br>
+
+<div>
+  <!-- Bind button disabled state to \`isUnchanged\` property -->
+  <button [disabled]="isUnchanged">Save</button>
+</div>
+<br><br>
+
+<div>
+  <img [src]="heroImageUrl">
+  <app-hero-detail [hero]="currentHero"></app-hero-detail>
+  <div [ngClass]="{'special': isSpecial}"></div>
+</div>
+<br><br>
+
+<button (click)="onSave()">Save</button>
+<app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>
+<div (myClick)="clicked=$event" clickable>click me</div>
+{{clicked}}
+<br><br>
+
+<div>
+  Hero Name:
+  <input [(ngModel)]="name">
+  {{name}}
+</div>
+<br><br>
+
+<button [attr.aria-label]="help">help</button>
+<br><br>
+
+<div [class.special]="isSpecial">Special</div>
+<br><br>
+
+<button [style.color]="isSpecial ? 'red' : 'green'">
+button</button>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- property vs. attribute -->
+<hr><h2 id="prop-vs-attrib">Property vs. Attribute (img examples)</h2>
+<!-- examine the following <img> tag in the browser tools -->
+<img src="images/ng-logo.png"
+    [src]="heroImageUrl">
+
+<br><br>
+
+<img [src]="iconUrl"/>
+<img bind-src="heroImageUrl"/>
+<img [attr.src]="villainImageUrl"/>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- buttons -->
+<hr><h2 id="buttons">Buttons</h2>
+
+<button>Enabled (but does nothing)</button>
+<button disabled>Disabled</button>
+<button disabled=false>Still disabled</button>
+<br><br>
+<button disabled>disabled by attribute</button>
+<button [disabled]="isUnchanged">disabled by property binding</button>
+<br><br>
+<button bind-disabled="isUnchanged" on-click="onSave($event)">Disabled Cancel</button>
+<button [disabled]="!canSave" (click)="onSave($event)">Enabled Save</button>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- property binding -->
+<hr><h2 id="property-binding">Property Binding</h2>
+
+<img [src]="heroImageUrl">
+<button [disabled]="isUnchanged">Cancel is disabled</button>
+<div [ngClass]="classes">[ngClass] binding to the classes property</div>
+<app-hero-detail [hero]="currentHero"></app-hero-detail>
+<img bind-src="heroImageUrl">
+
+<!-- ERROR: HeroDetailComponent.hero expects a
+     Hero object, not the string "currentHero" -->
+<div *ngIf="false">
+  <app-hero-detail hero="currentHero"></app-hero-detail>
+</div>
+<app-hero-detail prefix="You are my" [hero]="currentHero"></app-hero-detail>
+
+<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
+<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
+
+<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
+<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
+
+<!--
+  Angular generates warnings for these two lines as it sanitizes them
+  WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
+ -->
+<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
+<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- attribute binding -->
+<hr><h2 id="attribute-binding">Attribute Binding</h2>
+
+<!--  create and set a colspan attribute -->
+<table border=1>
+  <!--  expression calculates colspan=2 -->
+  <tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
+
+  <!-- ERROR: There is no \`colspan\` property to set!
+    <tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
+  -->
+
+  <tr><td>Five</td><td>Six</td></tr>
+</table>
+
+<br>
+<!-- create and set an aria attribute for assistive technology -->
+<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
+<br><br>
+
+<!-- The following effects are not discussed in the chapter -->
+<div>
+  <!-- any use of [attr.disabled] creates the disabled attribute -->
+  <button [attr.disabled]="isUnchanged">Disabled</button>
+
+  <button [attr.disabled]="!isUnchanged">Disabled as well</button>
+
+  <!-- we'd have to remove it with property binding -->
+  <button disabled [disabled]="false">Enabled (but inert)</button>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- class binding -->
+<hr><h2 id="class-binding">Class Binding</h2>
+
+<!-- standard class attribute setting  -->
+<div class="bad curly special">Bad curly special</div>
+
+<!-- reset/override all class names with a binding  -->
+<div class="bad curly special"
+     [class]="badCurly">Bad curly</div>
+
+<!-- toggle the "special" class on/off with a property -->
+<div [class.special]="isSpecial">The class binding is special</div>
+
+<!-- binding to \`class.special\` trumps the class attribute -->
+<div class="special"
+     [class.special]="!isSpecial">This one is not so special</div>
+
+<div bind-class.special="isSpecial">This class binding is special too</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!--style binding -->
+<hr><h2 id="style-binding">Style Binding</h2>
+
+<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
+<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
+
+<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
+<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- event binding -->
+<hr><h2 id="event-binding">Event Binding</h2>
+
+<button (click)="onSave()">Save</button>
+
+<button on-click="onSave()">On Save</button>
+
+<div>
+<!-- \`myClick\` is an event on the custom \`ClickDirective\` -->
+<div (myClick)="clickMessage=$event" clickable>click with myClick</div>
+{{clickMessage}}
+</div>
+
+
+<!-- binding to a nested component -->
+<app-hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></app-hero-detail>
+<br>
+
+<app-big-hero-detail
+    (deleteRequest)="deleteHero($event)"
+    [hero]="currentHero">
+</app-big-hero-detail>
+
+<div class="parent-div" (click)="onClickMe($event)" clickable>Click me
+  <div class="child-div">Click me too!</div>
+</div>
+
+<!-- Will save only once -->
+<div (click)="onSave()" clickable>
+  <button (click)="onSave($event)">Save, no propagation</button>
+</div>
+
+<!-- Will save twice -->
+<div (click)="onSave()" clickable>
+  <button (click)="onSave()">Save w/ propagation</button>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<hr><h2 id="two-way">Two-way Binding</h2>
+<div id="two-way-1">
+  <app-sizer [(size)]="fontSizePx"></app-sizer>
+  <div [style.font-size.px]="fontSizePx">Resizable Text</div>
+  <label>FontSize (px): <input [(ngModel)]="fontSizePx"></label>
+</div>
+<br>
+<div id="two-way-2">
+  <h3>De-sugared two-way binding</h3>
+  <app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- Two way data binding unwound;
+    passing the changed display value to the event handler via \`$event\` -->
+<hr><h2 id="ngModel">NgModel (two-way) Binding</h2>
+
+<h3>Result: {{currentHero.name}}</h3>
+
+<input [value]="currentHero.name"
+       (input)="currentHero.name=$event.target.value" >
+without NgModel
+<br>
+<input [(ngModel)]="currentHero.name">
+[(ngModel)]
+<br>
+<input bindon-ngModel="currentHero.name">
+bindon-ngModel
+<br>
+<input
+  [ngModel]="currentHero.name"
+  (ngModelChange)="currentHero.name=$event">
+(ngModelChange)="...name=$event"
+<br>
+<input
+  [ngModel]="currentHero.name"
+  (ngModelChange)="setUppercaseName($event)">
+(ngModelChange)="setUppercaseName($event)"
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgClass binding -->
+<hr><h2 id="ngClass">NgClass Binding</h2>
+
+<p>currentClasses is {{currentClasses | json}}</p>
+<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
+
+<!-- not used in chapter -->
+<br>
+<label>saveable   <input type="checkbox" [(ngModel)]="canSave"></label> |
+<label>modified:  <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged"></label> |
+<label>special:   <input type="checkbox" [(ngModel)]="isSpecial"></label>
+<button (click)="setCurrentClasses()">Refresh currentClasses</button>
+<br><br>
+<div [ngClass]="currentClasses">
+  This div should be {{ canSave ? "": "not"}} saveable,
+                  {{ isUnchanged ? "unchanged" : "modified" }} and,
+                  {{ isSpecial ? "": "not"}} special after clicking "Refresh".</div>
+<br><br>
+
+<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
+
+<div class="bad curly special">Bad curly special</div>
+<div [ngClass]="{'bad':false, 'curly':true, 'special':true}">Curly special</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgStyle binding -->
+<hr><h2 id="ngStyle">NgStyle Binding</h2>
+
+<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
+  This div is x-large or smaller.
+</div>
+
+<h3>[ngStyle] binding to currentStyles - CSS property names</h3>
+<p>currentStyles is {{currentStyles | json}}</p>
+<div [ngStyle]="currentStyles">
+  This div is initially italic, normal weight, and extra large (24px).
+</div>
+
+<!-- not used in chapter -->
+<br>
+<label>italic: <input type="checkbox" [(ngModel)]="canSave"></label> |
+<label>normal: <input type="checkbox" [(ngModel)]="isUnchanged"></label> |
+<label>xlarge: <input type="checkbox" [(ngModel)]="isSpecial"></label>
+<button (click)="setCurrentStyles()">Refresh currentStyles</button>
+<br><br>
+<div [ngStyle]="currentStyles">
+  This div should be {{ canSave ? "italic": "plain"}},
+                  {{ isUnchanged ? "normal weight" : "bold" }} and,
+                  {{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgIf binding -->
+<hr><h2 id="ngIf">NgIf Binding</h2>
+
+<app-hero-detail *ngIf="isActive"></app-hero-detail>
+
+<div *ngIf="currentHero">Hello, {{currentHero.name}}</div>
+<div *ngIf="nullHero">Hello, {{nullHero.name}}</div>
+
+<!-- NgIf binding with template (no *) -->
+
+<ng-template [ngIf]="currentHero">Add {{currentHero.name}} with template</ng-template>
+
+<!-- Does not show because isActive is false! -->
+<div>Hero Detail removed from DOM (via template) because isActive is false</div>
+<ng-template [ngIf]="isActive">
+  <app-hero-detail></app-hero-detail>
+</ng-template>
+
+<!-- isSpecial is true -->
+<div [class.hidden]="!isSpecial">Show with class</div>
+<div [class.hidden]="isSpecial">Hide with class</div>
+
+<!-- HeroDetail is in the DOM but hidden -->
+<app-hero-detail [class.hidden]="isSpecial"></app-hero-detail>
+
+<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
+<div [style.display]="isSpecial ? 'none'  : 'block'">Hide with style</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgFor binding -->
+<hr><h2 id="ngFor">NgFor Binding</h2>
+
+<div class="box">
+  <div *ngFor="let hero of heroes">{{hero.name}}</div>
+</div>
+<br>
+
+<div class="box">
+  <!-- *ngFor w/ hero-detail Component -->
+  <app-hero-detail *ngFor="let hero of heroes" [hero]="hero"></app-hero-detail>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<h4 id="ngFor-index">*ngFor with index</h4>
+<p>with <i>semi-colon</i> separator</p>
+<div class="box">
+  <div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>
+</div>
+
+<p>with <i>comma</i> separator</p>
+<div class="box">
+  <!-- Ex: "1 - Hercules" -->
+  <div *ngFor="let hero of heroes, let i=index">{{i + 1}} - {{hero.name}}</div>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<h4 id="ngFor-trackBy">*ngFor trackBy</h4>
+<button (click)="resetHeroes()">Reset heroes</button>
+<button (click)="changeIds()">Change ids</button>
+<button (click)="clearTrackByCounts()">Clear counts</button>
+
+<p><i>without</i> trackBy</p>
+<div class="box">
+  <div #noTrackBy *ngFor="let hero of heroes">({{hero.id}}) {{hero.name}}</div>
+
+  <div id="noTrackByCnt" *ngIf="heroesNoTrackByCount" >
+    Hero DOM elements change #{{heroesNoTrackByCount}} without trackBy
+  </div>
+</div>
+
+<p>with trackBy</p>
+<div class="box">
+  <div #withTrackBy *ngFor="let hero of heroes; trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
+
+  <div id="withTrackByCnt" *ngIf="heroesWithTrackByCount">
+    Hero DOM elements change #{{heroesWithTrackByCount}} with trackBy
+  </div>
+</div>
+
+<br><br><br>
+
+<p>with trackBy and <i>semi-colon</i> separator</p>
+<div class="box">
+  <div *ngFor="let hero of heroes; trackBy: trackByHeroes">
+    ({{hero.id}}) {{hero.name}}
+  </div>
+</div>
+
+<p>with trackBy and <i>comma</i> separator</p>
+<div class="box">
+  <div *ngFor="let hero of heroes, trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
+</div>
+
+<p>with trackBy and <i>space</i> separator</p>
+<div class="box">
+  <div *ngFor="let hero of heroes trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
+</div>
+
+<p>with <i>generic</i> trackById function</p>
+<div class="box">
+  <div *ngFor="let hero of heroes, trackBy: trackById">({{hero.id}}) {{hero.name}}</div>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgSwitch binding -->
+<hr><h2 id="ngSwitch">NgSwitch Binding</h2>
+
+<p>Pick your favorite hero</p>
+<div>
+  <label *ngFor="let h of heroes">
+    <input type="radio" name="heroes" [(ngModel)]="currentHero" [value]="h">{{h.name}}
+  </label>
+</div>
+
+<div [ngSwitch]="currentHero.emotion">
+  <app-happy-hero    *ngSwitchCase="'happy'"    [hero]="currentHero"></app-happy-hero>
+  <app-sad-hero      *ngSwitchCase="'sad'"      [hero]="currentHero"></app-sad-hero>
+  <app-confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></app-confused-hero>
+  <div *ngSwitchCase="'confused'">Are you as confused as {{currentHero.name}}?</div>
+  <app-unknown-hero  *ngSwitchDefault           [hero]="currentHero"></app-unknown-hero>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- template reference variable -->
+<hr><h2 id="ref-vars">Template reference variables</h2>
+
+<input #phone placeholder="phone number">
+
+<!-- lots of other elements -->
+
+<!-- phone refers to the input element; pass its \`value\` to an event handler -->
+<button (click)="callPhone(phone.value)">Call</button>
+
+<input ref-fax placeholder="fax number">
+<button (click)="callFax(fax.value)">Fax</button>
+
+<!-- btn refers to the button element; show its disabled state -->
+<button #btn disabled [innerHTML]="'disabled by attribute: '+btn.disabled"></button>
+
+<h4>Example Form</h4>
+<app-hero-form [hero]="currentHero"></app-hero-form>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- inputs and output -->
+<hr><h2 id="inputs-and-outputs">Inputs and Outputs</h2>
+
+<img [src]="iconUrl"/>
+<button (click)="onSave()">Save</button>
+
+<app-hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
+</app-hero-detail>
+
+<div (myClick)="clickMessage2=$event" clickable>myClick2</div>
+{{clickMessage2}}
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- Pipes -->
+<hr><h2 id="pipes">Pipes</h2>
+
+<div>Title through uppercase pipe: {{title | uppercase}}</div>
+
+<!-- Pipe chaining: convert title to uppercase, then to lowercase -->
+<div>
+  Title through a pipe chain:
+  {{title | uppercase | lowercase}}
+</div>
+
+<!-- pipe with configuration argument => "February 25, 1970" -->
+<div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>
+
+<div>{{currentHero | json}}</div>
+
+<div>Birthdate: {{(currentHero?.birthdate | date:'longDate') | uppercase}}</div>
+
+<div>
+  <!-- pipe price to USD and display the $ symbol -->
+  <label>Price: </label>{{product.price | currency:'USD':true}}
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- Null values and the safe navigation operator -->
+<hr><h2 id="safe-navigation-operator">Safe navigation operator <i>?.</i></h2>
+
+<div>
+  The title is {{title}}
+</div>
+
+<div>
+  The current hero's name is {{currentHero?.name}}
+</div>
+
+<div>
+  The current hero's name is {{currentHero.name}}
+</div>
+
+
+<!--
+The null hero's name is {{nullHero.name}}
+
+See console log:
+  TypeError: Cannot read property 'name' of null in [null]
+-->
+
+<!--No hero, div not displayed, no error -->
+<div *ngIf="nullHero">The null hero's name is {{nullHero.name}}</div>
+
+<div>
+The null hero's name is {{nullHero && nullHero.name}}
+</div>
+
+<div>
+  <!-- No hero, no problem! -->
+  The null hero's name is {{nullHero?.name}}
+</div>
+
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- non-null assertion operator -->
+<hr><h2 id="non-null-assertion-operator">Non-null assertion operator <i>!.</i></h2>
+
+<div>
+  <!--No hero, no text -->
+  <div *ngIf="hero">
+    The hero's name is {{hero!.name}}
+  </div>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- non-null assertion operator -->
+<hr><h2 id="any-type-cast-function">$any type cast function <i>$any( )</i>.</h2>
+
+<div>
+  <!-- Accessing an undeclared member -->
+  <div>
+    The hero's marker is {{$any(hero).marker}}
+  </div>
+</div>
+
+<div>
+  <!-- Accessing an undeclared member -->
+  <div>
+    Undeclared members is {{$any(this).member}}
+  </div>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- TODO: discuss this in the Style binding section -->
+<!-- enums in bindings -->
+<hr><h2 id="enums">Enums in binding</h2>
+
+<p>
+  The name of the Color.Red enum is {{Color[Color.Red]}}.<br>
+  The current color is {{Color[color]}} and its number is {{color}}.<br>
+  <button [style.color]="Color[color]" (click)="colorToggle()">Enum Toggle</button>
+</p>
+
+<a class="to-toc" href="#toc">top</a>
+
+
+<!-- 
+Copyright 2017-2018 Google Inc. All Rights Reserved.
+Use of this source code is governed by an MIT-style license that
+can be found in the LICENSE file at http://angular.io/license
+-->
+
+<div id="heroForm">
+  <form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
+    <div class="form-group">
+      <label for="name">Name
+        <input class="form-control" name="name" required [(ngModel)]="hero.name">
+      </label>
+    </div>
+    <button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
+  </form>
+  <div [hidden]="!heroForm.form.valid">
+    {{submitMessage}}
+  </div>
+</div>
+
+
+<!-- 
+Copyright 2017-2018 Google Inc. All Rights Reserved.
+Use of this source code is governed by an MIT-style license that
+can be found in the LICENSE file at http://angular.io/license
+-->
+
+=====================================output=====================================
+<!-- copied from: https://stackblitz.com/angular/ymdjlgmlavo -->
+
+<a id="toc"></a>
+<h1>Template Syntax</h1>
+<a href="#interpolation">Interpolation</a><br />
+<a href="#expression-context">Expression context</a><br />
+<a href="#statement-context">Statement context</a><br />
+<a href="#mental-model">Mental Model</a><br />
+<a href="#buttons">Buttons</a><br />
+<a href="#prop-vs-attrib">Properties vs. Attributes</a><br />
+<br />
+<a href="#property-binding">Property Binding</a><br />
+<div style="margin-left:8px">
+  <a href="#attribute-binding">Attribute Binding</a><br />
+  <a href="#class-binding">Class Binding</a><br />
+  <a href="#style-binding">Style Binding</a><br />
+</div>
+<br />
+<a href="#event-binding">Event Binding</a><br />
+<a href="#two-way">Two-way Binding</a><br />
+<br />
+<div>Directives</div>
+<div style="margin-left:8px">
+  <a href="#ngModel">NgModel (two-way) Binding</a><br />
+  <a href="#ngClass">NgClass Binding</a><br />
+  <a href="#ngStyle">NgStyle Binding</a><br />
+  <a href="#ngIf">NgIf</a><br />
+  <a href="#ngFor">NgFor</a><br />
+  <div style="margin-left:8px">
+    <a href="#ngFor-index">NgFor with index</a><br />
+    <a href="#ngFor-trackBy">NgFor with trackBy</a><br />
+  </div>
+  <a href="#ngSwitch">NgSwitch</a><br />
+</div>
+<br />
+<a href="#ref-vars">Template reference variables</a><br />
+<a href="#inputs-and-outputs">Inputs and outputs</a><br />
+<a href="#pipes">Pipes</a><br />
+<a href="#safe-navigation-operator">Safe navigation operator <i>?.</i></a
+><br />
+<a href="#non-null-assertion-operator">Non-null assertion operator <i>!.</i></a
+><br />
+<a href="#enums">Enums</a><br />
+
+<!-- Interpolation and expressions -->
+<hr />
+<h2 id="interpolation">Interpolation</h2>
+
+<p>My current hero is {{ currentHero.name }}</p>
+
+<h3>
+  {{ title }}
+  <img src="{{ heroImageUrl }}" style="height:30px" />
+</h3>
+
+<!-- "The sum of 1 + 1 is 2" -->
+<p>The sum of 1 + 1 is {{ 1 + 1 }}</p>
+
+<!-- "The sum of 1 + 1 is not 4" -->
+<p>The sum of 1 + 1 is not {{ 1 + 1 + getVal() }}</p>
+
+<a class="to-toc" href="#toc">top</a>
+
+<hr />
+<h2 id="expression-context">Expression context</h2>
+
+<p>
+  Component expression context (&#123;&#123;title&#125;&#125;,
+  [hidden]="isUnchanged")
+</p>
+<div class="context">
+  {{ title }}
+  <span [hidden]="isUnchanged">changed</span>
+</div>
+
+<p>Template input variable expression context (let hero)</p>
+<!-- template hides the following; plenty of examples later -->
+<ng-template>
+  <div *ngFor="let hero of heroes">{{ hero.name }}</div>
+</ng-template>
+
+<p>Template reference variable expression context (#heroInput)</p>
+<div (keyup)="(0)" class="context">
+  Type something:
+  <input #heroInput /> {{ heroInput.value }}
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<hr />
+<h2 id="statement-context">Statement context</h2>
+
+<p>Component statement context ( (click)="onSave() )</p>
+<div class="context">
+  <button (click)="deleteHero()">Delete hero</button>
+</div>
+
+<p>Template $event statement context</p>
+<div class="context">
+  <button (click)="onSave($event)">Save</button>
+</div>
+
+<p>Template input variable statement context (let hero)</p>
+<!-- template hides the following; plenty of examples later -->
+<div class="context">
+  <button *ngFor="let hero of heroes" (click)="deleteHero(hero)">
+    {{ hero.name }}
+  </button>
+</div>
+
+<p>Template reference variable statement context (#heroForm)</p>
+<div class="context">
+  <form #heroForm (ngSubmit)="onSubmit(heroForm)">...</form>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- New Mental Model -->
+<hr />
+<h2 id="mental-model">New Mental Model</h2>
+
+<!--<img src="http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png">-->
+<!-- Public Domain terms of use: http://www.wpclipart.com/terms.html -->
+<div class="special">Mental Model</div>
+<img src="assets/images/hero.png" />
+<button disabled>Save</button>
+<br /><br />
+
+<div>
+  <!-- Normal HTML -->
+  <div class="special">Mental Model</div>
+  <!-- Wow! A new element! -->
+  <app-hero-detail></app-hero-detail>
+</div>
+<br /><br />
+
+<div>
+  <!-- Bind button disabled state to \`isUnchanged\` property -->
+  <button [disabled]="isUnchanged">Save</button>
+</div>
+<br /><br />
+
+<div>
+  <img [src]="heroImageUrl" />
+  <app-hero-detail [hero]="currentHero"></app-hero-detail>
+  <div [ngClass]="{special: isSpecial}"></div>
+</div>
+<br /><br />
+
+<button (click)="onSave()">Save</button>
+<app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>
+<div (myClick)="clicked = $event" clickable>click me</div>
+{{ clicked }}
+<br /><br />
+
+<div>
+  Hero Name:
+  <input [(ngModel)]="name" />
+  {{ name }}
+</div>
+<br /><br />
+
+<button [attr.aria-label]="help">help</button>
+<br /><br />
+
+<div [class.special]="isSpecial">Special</div>
+<br /><br />
+
+<button [style.color]="isSpecial ? 'red' : 'green'">
+  button
+</button>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- property vs. attribute -->
+<hr />
+<h2 id="prop-vs-attrib">Property vs. Attribute (img examples)</h2>
+<!-- examine the following <img> tag in the browser tools -->
+<img src="images/ng-logo.png" [src]="heroImageUrl" />
+
+<br /><br />
+
+<img [src]="iconUrl" />
+<img bind-src="heroImageUrl" />
+<img [attr.src]="villainImageUrl" />
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- buttons -->
+<hr />
+<h2 id="buttons">Buttons</h2>
+
+<button>Enabled (but does nothing)</button>
+<button disabled>Disabled</button>
+<button disabled="false">Still disabled</button>
+<br /><br />
+<button disabled>disabled by attribute</button>
+<button [disabled]="isUnchanged">disabled by property binding</button>
+<br /><br />
+<button bind-disabled="isUnchanged" on-click="onSave($event)">
+  Disabled Cancel
+</button>
+<button [disabled]="!canSave" (click)="onSave($event)">Enabled Save</button>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- property binding -->
+<hr />
+<h2 id="property-binding">Property Binding</h2>
+
+<img [src]="heroImageUrl" />
+<button [disabled]="isUnchanged">Cancel is disabled</button>
+<div [ngClass]="classes">[ngClass] binding to the classes property</div>
+<app-hero-detail [hero]="currentHero"></app-hero-detail>
+<img bind-src="heroImageUrl" />
+
+<!-- ERROR: HeroDetailComponent.hero expects a
+     Hero object, not the string "currentHero" -->
+<div *ngIf="false">
+  <app-hero-detail hero="currentHero"></app-hero-detail>
+</div>
+<app-hero-detail prefix="You are my" [hero]="currentHero"></app-hero-detail>
+
+<p><img src="{{ heroImageUrl }}" /> is the <i>interpolated</i> image.</p>
+<p><img [src]="heroImageUrl" /> is the <i>property bound</i> image.</p>
+
+<p>
+  <span>"{{ title }}" is the <i>interpolated</i> title.</span>
+</p>
+<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
+
+<!--
+  Angular generates warnings for these two lines as it sanitizes them
+  WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
+ -->
+<p>
+  <span>"{{ evilTitle }}" is the <i>interpolated</i> evil title.</span>
+</p>
+<p>
+  "<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil
+  title.
+</p>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- attribute binding -->
+<hr />
+<h2 id="attribute-binding">Attribute Binding</h2>
+
+<!--  create and set a colspan attribute -->
+<table border="1">
+  <!--  expression calculates colspan=2 -->
+  <tr>
+    <td [attr.colspan]="1 + 1">One-Two</td>
+  </tr>
+
+  <!-- ERROR: There is no \`colspan\` property to set!
+    <tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
+  -->
+
+  <tr>
+    <td>Five</td>
+    <td>Six</td>
+  </tr>
+</table>
+
+<br />
+<!-- create and set an aria attribute for assistive technology -->
+<button [attr.aria-label]="actionName">{{ actionName }} with Aria</button>
+<br /><br />
+
+<!-- The following effects are not discussed in the chapter -->
+<div>
+  <!-- any use of [attr.disabled] creates the disabled attribute -->
+  <button [attr.disabled]="isUnchanged">Disabled</button>
+
+  <button [attr.disabled]="!isUnchanged">Disabled as well</button>
+
+  <!-- we'd have to remove it with property binding -->
+  <button disabled [disabled]="false">Enabled (but inert)</button>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- class binding -->
+<hr />
+<h2 id="class-binding">Class Binding</h2>
+
+<!-- standard class attribute setting  -->
+<div class="bad curly special">Bad curly special</div>
+
+<!-- reset/override all class names with a binding  -->
+<div class="bad curly special" [class]="badCurly">Bad curly</div>
+
+<!-- toggle the "special" class on/off with a property -->
+<div [class.special]="isSpecial">The class binding is special</div>
+
+<!-- binding to \`class.special\` trumps the class attribute -->
+<div class="special" [class.special]="!isSpecial">
+  This one is not so special
+</div>
+
+<div bind-class.special="isSpecial">This class binding is special too</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!--style binding -->
+<hr />
+<h2 id="style-binding">Style Binding</h2>
+
+<button [style.color]="isSpecial ? 'red' : 'green'">Red</button>
+<button [style.background-color]="canSave ? 'cyan' : 'grey'">Save</button>
+
+<button [style.font-size.em]="isSpecial ? 3 : 1">Big</button>
+<button [style.font-size.%]="!isSpecial ? 150 : 50">Small</button>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- event binding -->
+<hr />
+<h2 id="event-binding">Event Binding</h2>
+
+<button (click)="onSave()">Save</button>
+
+<button on-click="onSave()">On Save</button>
+
+<div>
+  <!-- \`myClick\` is an event on the custom \`ClickDirective\` -->
+  <div (myClick)="clickMessage = $event" clickable>click with myClick</div>
+  {{ clickMessage }}
+</div>
+
+<!-- binding to a nested component -->
+<app-hero-detail
+  (deleteRequest)="deleteHero($event)"
+  [hero]="currentHero"
+></app-hero-detail>
+<br />
+
+<app-big-hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero">
+</app-big-hero-detail>
+
+<div class="parent-div" (click)="onClickMe($event)" clickable>
+  Click me
+  <div class="child-div">Click me too!</div>
+</div>
+
+<!-- Will save only once -->
+<div (click)="onSave()" clickable>
+  <button (click)="onSave($event)">Save, no propagation</button>
+</div>
+
+<!-- Will save twice -->
+<div (click)="onSave()" clickable>
+  <button (click)="onSave()">Save w/ propagation</button>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<hr />
+<h2 id="two-way">Two-way Binding</h2>
+<div id="two-way-1">
+  <app-sizer [(size)]="fontSizePx"></app-sizer>
+  <div [style.font-size.px]="fontSizePx">Resizable Text</div>
+  <label>FontSize (px): <input [(ngModel)]="fontSizePx"/></label>
+</div>
+<br />
+<div id="two-way-2">
+  <h3>De-sugared two-way binding</h3>
+  <app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx = $event"></app-sizer>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- Two way data binding unwound;
+    passing the changed display value to the event handler via \`$event\` -->
+<hr />
+<h2 id="ngModel">NgModel (two-way) Binding</h2>
+
+<h3>Result: {{ currentHero.name }}</h3>
+
+<input
+  [value]="currentHero.name"
+  (input)="currentHero.name = $event.target.value"
+/>
+without NgModel
+<br />
+<input [(ngModel)]="currentHero.name" />
+[(ngModel)]
+<br />
+<input bindon-ngModel="currentHero.name" />
+bindon-ngModel
+<br />
+<input
+  [ngModel]="currentHero.name"
+  (ngModelChange)="currentHero.name = $event"
+/>
+(ngModelChange)="...name=$event"
+<br />
+<input
+  [ngModel]="currentHero.name"
+  (ngModelChange)="setUppercaseName($event)"
+/>
+(ngModelChange)="setUppercaseName($event)"
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgClass binding -->
+<hr />
+<h2 id="ngClass">NgClass Binding</h2>
+
+<p>currentClasses is {{ currentClasses | json }}</p>
+<div [ngClass]="currentClasses">
+  This div is initially saveable, unchanged, and special
+</div>
+
+<!-- not used in chapter -->
+<br />
+<label>saveable <input type="checkbox" [(ngModel)]="canSave"/></label> |
+<label
+  >modified:
+  <input
+    type="checkbox"
+    [value]="!isUnchanged"
+    (change)="isUnchanged = !isUnchanged"
+/></label>
+|
+<label>special: <input type="checkbox" [(ngModel)]="isSpecial"/></label>
+<button (click)="setCurrentClasses()">Refresh currentClasses</button>
+<br /><br />
+<div [ngClass]="currentClasses">
+  This div should be {{ canSave ? "" : "not" }} saveable,
+  {{ isUnchanged ? "unchanged" : "modified" }} and,
+  {{ isSpecial ? "" : "not" }} special after clicking "Refresh".
+</div>
+<br /><br />
+
+<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
+
+<div class="bad curly special">Bad curly special</div>
+<div [ngClass]="{bad: false, curly: true, special: true}">Curly special</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgStyle binding -->
+<hr />
+<h2 id="ngStyle">NgStyle Binding</h2>
+
+<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
+  This div is x-large or smaller.
+</div>
+
+<h3>[ngStyle] binding to currentStyles - CSS property names</h3>
+<p>currentStyles is {{ currentStyles | json }}</p>
+<div [ngStyle]="currentStyles">
+  This div is initially italic, normal weight, and extra large (24px).
+</div>
+
+<!-- not used in chapter -->
+<br />
+<label>italic: <input type="checkbox" [(ngModel)]="canSave"/></label> |
+<label>normal: <input type="checkbox" [(ngModel)]="isUnchanged"/></label> |
+<label>xlarge: <input type="checkbox" [(ngModel)]="isSpecial"/></label>
+<button (click)="setCurrentStyles()">Refresh currentStyles</button>
+<br /><br />
+<div [ngStyle]="currentStyles">
+  This div should be {{ canSave ? "italic" : "plain" }},
+  {{ isUnchanged ? "normal weight" : "bold" }} and,
+  {{ isSpecial ? "extra large" : "normal size" }} after clicking "Refresh".
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgIf binding -->
+<hr />
+<h2 id="ngIf">NgIf Binding</h2>
+
+<app-hero-detail *ngIf="isActive"></app-hero-detail>
+
+<div *ngIf="currentHero">Hello, {{ currentHero.name }}</div>
+<div *ngIf="nullHero">Hello, {{ nullHero.name }}</div>
+
+<!-- NgIf binding with template (no *) -->
+
+<ng-template [ngIf]="currentHero"
+  >Add {{ currentHero.name }} with template</ng-template
+>
+
+<!-- Does not show because isActive is false! -->
+<div>Hero Detail removed from DOM (via template) because isActive is false</div>
+<ng-template [ngIf]="isActive">
+  <app-hero-detail></app-hero-detail>
+</ng-template>
+
+<!-- isSpecial is true -->
+<div [class.hidden]="!isSpecial">Show with class</div>
+<div [class.hidden]="isSpecial">Hide with class</div>
+
+<!-- HeroDetail is in the DOM but hidden -->
+<app-hero-detail [class.hidden]="isSpecial"></app-hero-detail>
+
+<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
+<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgFor binding -->
+<hr />
+<h2 id="ngFor">NgFor Binding</h2>
+
+<div class="box">
+  <div *ngFor="let hero of heroes">{{ hero.name }}</div>
+</div>
+<br />
+
+<div class="box">
+  <!-- *ngFor w/ hero-detail Component -->
+  <app-hero-detail *ngFor="let hero of heroes" [hero]="hero"></app-hero-detail>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<h4 id="ngFor-index">*ngFor with index</h4>
+<p>with <i>semi-colon</i> separator</p>
+<div class="box">
+  <div *ngFor="let hero of heroes; let i = index">
+    {{ i + 1 }} - {{ hero.name }}
+  </div>
+</div>
+
+<p>with <i>comma</i> separator</p>
+<div class="box">
+  <!-- Ex: "1 - Hercules" -->
+  <div *ngFor="let hero of heroes; let i = index">
+    {{ i + 1 }} - {{ hero.name }}
+  </div>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<h4 id="ngFor-trackBy">*ngFor trackBy</h4>
+<button (click)="resetHeroes()">Reset heroes</button>
+<button (click)="changeIds()">Change ids</button>
+<button (click)="clearTrackByCounts()">Clear counts</button>
+
+<p><i>without</i> trackBy</p>
+<div class="box">
+  <div #noTrackBy *ngFor="let hero of heroes">
+    ({{ hero.id }}) {{ hero.name }}
+  </div>
+
+  <div id="noTrackByCnt" *ngIf="heroesNoTrackByCount">
+    Hero DOM elements change #{{ heroesNoTrackByCount }} without trackBy
+  </div>
+</div>
+
+<p>with trackBy</p>
+<div class="box">
+  <div #withTrackBy *ngFor="let hero of heroes; trackBy: trackByHeroes">
+    ({{ hero.id }}) {{ hero.name }}
+  </div>
+
+  <div id="withTrackByCnt" *ngIf="heroesWithTrackByCount">
+    Hero DOM elements change #{{ heroesWithTrackByCount }} with trackBy
+  </div>
+</div>
+
+<br /><br /><br />
+
+<p>with trackBy and <i>semi-colon</i> separator</p>
+<div class="box">
+  <div *ngFor="let hero of heroes; trackBy: trackByHeroes">
+    ({{ hero.id }}) {{ hero.name }}
+  </div>
+</div>
+
+<p>with trackBy and <i>comma</i> separator</p>
+<div class="box">
+  <div *ngFor="let hero of heroes; trackBy: trackByHeroes">
+    ({{ hero.id }}) {{ hero.name }}
+  </div>
+</div>
+
+<p>with trackBy and <i>space</i> separator</p>
+<div class="box">
+  <div *ngFor="let hero of heroes; trackBy: trackByHeroes">
+    ({{ hero.id }}) {{ hero.name }}
+  </div>
+</div>
+
+<p>with <i>generic</i> trackById function</p>
+<div class="box">
+  <div *ngFor="let hero of heroes; trackBy: trackById">
+    ({{ hero.id }}) {{ hero.name }}
+  </div>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- NgSwitch binding -->
+<hr />
+<h2 id="ngSwitch">NgSwitch Binding</h2>
+
+<p>Pick your favorite hero</p>
+<div>
+  <label *ngFor="let h of heroes">
+    <input type="radio" name="heroes" [(ngModel)]="currentHero" [value]="h" />{{
+      h.name
+    }}
+  </label>
+</div>
+
+<div [ngSwitch]="currentHero.emotion">
+  <app-happy-hero *ngSwitchCase="'happy'" [hero]="currentHero"></app-happy-hero>
+  <app-sad-hero *ngSwitchCase="'sad'" [hero]="currentHero"></app-sad-hero>
+  <app-confused-hero
+    *ngSwitchCase="'confused'"
+    [hero]="currentHero"
+  ></app-confused-hero>
+  <div *ngSwitchCase="'confused'">
+    Are you as confused as {{ currentHero.name }}?
+  </div>
+  <app-unknown-hero *ngSwitchDefault [hero]="currentHero"></app-unknown-hero>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- template reference variable -->
+<hr />
+<h2 id="ref-vars">Template reference variables</h2>
+
+<input #phone placeholder="phone number" />
+
+<!-- lots of other elements -->
+
+<!-- phone refers to the input element; pass its \`value\` to an event handler -->
+<button (click)="callPhone(phone.value)">Call</button>
+
+<input ref-fax placeholder="fax number" />
+<button (click)="callFax(fax.value)">Fax</button>
+
+<!-- btn refers to the button element; show its disabled state -->
+<button
+  #btn
+  disabled
+  [innerHTML]="'disabled by attribute: ' + btn.disabled"
+></button>
+
+<h4>Example Form</h4>
+<app-hero-form [hero]="currentHero"></app-hero-form>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- inputs and output -->
+<hr />
+<h2 id="inputs-and-outputs">Inputs and Outputs</h2>
+
+<img [src]="iconUrl" />
+<button (click)="onSave()">Save</button>
+
+<app-hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
+</app-hero-detail>
+
+<div (myClick)="clickMessage2 = $event" clickable>myClick2</div>
+{{ clickMessage2 }}
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- Pipes -->
+<hr />
+<h2 id="pipes">Pipes</h2>
+
+<div>Title through uppercase pipe: {{ title | uppercase }}</div>
+
+<!-- Pipe chaining: convert title to uppercase, then to lowercase -->
+<div>
+  Title through a pipe chain:
+  {{ title | uppercase | lowercase }}
+</div>
+
+<!-- pipe with configuration argument => "February 25, 1970" -->
+<div>Birthdate: {{ currentHero?.birthdate | date: "longDate" }}</div>
+
+<div>{{ currentHero | json }}</div>
+
+<div>
+  Birthdate: {{ currentHero?.birthdate | date: "longDate" | uppercase }}
+</div>
+
+<div>
+  <!-- pipe price to USD and display the $ symbol -->
+  <label>Price: </label>{{ product.price | currency: "USD":true }}
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- Null values and the safe navigation operator -->
+<hr />
+<h2 id="safe-navigation-operator">Safe navigation operator <i>?.</i></h2>
+
+<div>The title is {{ title }}</div>
+
+<div>The current hero's name is {{ currentHero?.name }}</div>
+
+<div>The current hero's name is {{ currentHero.name }}</div>
+
+<!--
+The null hero's name is {{nullHero.name}}
+
+See console log:
+  TypeError: Cannot read property 'name' of null in [null]
+-->
+
+<!--No hero, div not displayed, no error -->
+<div *ngIf="nullHero">The null hero's name is {{ nullHero.name }}</div>
+
+<div>The null hero's name is {{ nullHero && nullHero.name }}</div>
+
+<div>
+  <!-- No hero, no problem! -->
+  The null hero's name is {{ nullHero?.name }}
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- non-null assertion operator -->
+<hr />
+<h2 id="non-null-assertion-operator">Non-null assertion operator <i>!.</i></h2>
+
+<div>
+  <!--No hero, no text -->
+  <div *ngIf="hero">The hero's name is {{ hero!.name }}</div>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- non-null assertion operator -->
+<hr />
+<h2 id="any-type-cast-function">$any type cast function <i>$any( )</i>.</h2>
+
+<div>
+  <!-- Accessing an undeclared member -->
+  <div>The hero's marker is {{ $any(hero).marker }}</div>
+</div>
+
+<div>
+  <!-- Accessing an undeclared member -->
+  <div>Undeclared members is {{$any(this).member}}</div>
+</div>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- TODO: discuss this in the Style binding section -->
+<!-- enums in bindings -->
+<hr />
+<h2 id="enums">Enums in binding</h2>
+
+<p>
+  The name of the Color.Red enum is {{ Color[Color.Red] }}.<br />
+  The current color is {{ Color[color] }} and its number is {{ color }}.<br />
+  <button [style.color]="Color[color]" (click)="colorToggle()">
+    Enum Toggle
+  </button>
+</p>
+
+<a class="to-toc" href="#toc">top</a>
+
+<!-- 
+Copyright 2017-2018 Google Inc. All Rights Reserved.
+Use of this source code is governed by an MIT-style license that
+can be found in the LICENSE file at http://angular.io/license
+-->
+
+<div id="heroForm">
+  <form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
+    <div class="form-group">
+      <label for="name"
+        >Name
+        <input
+          class="form-control"
+          name="name"
+          required
+          [(ngModel)]="hero.name"
+        />
+      </label>
+    </div>
+    <button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
+  </form>
+  <div [hidden]="!heroForm.form.valid">
+    {{ submitMessage }}
+  </div>
+</div>
+
+<!-- 
+Copyright 2017-2018 Google Inc. All Rights Reserved.
+Use of this source code is governed by an MIT-style license that
+can be found in the LICENSE file at http://angular.io/license
+-->
+
+================================================================================
+`;
+
+exports[`tag-name.component.html 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<Table></Table>
+
+=====================================output=====================================
+<Table></Table>
+
+================================================================================
+`;
+
+exports[`tag-name.component.html 2`] = `
+====================================options=====================================
+parsers: ["angular"]
+printWidth: 80
+trailingComma: "es5"
+                                                                                | printWidth
+=====================================input======================================
+<Table></Table>
+
+=====================================output=====================================
+<Table></Table>
+
+================================================================================
+`;
+
+exports[`tag-name.component.html 3`] = `
+====================================options=====================================
+parsers: ["angular"]
 printWidth: 1
  | printWidth
 =====================================input======================================
@@ -10866,3 +13080,18 @@ printWidth: 80
 
 ================================================================================
 `;
+
+exports[`tag-name.component.html 5`] = `
+====================================options=====================================
+bracketSpacing: false
+parsers: ["angular"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<Table></Table>
+
+=====================================output=====================================
+<Table></Table>
+
+================================================================================
+`;
diff --git a/tests/html_angular/interpolation.component.html b/tests/html_angular/interpolation.component.html
index 134c8f934ac8..67a053c5135b 100644
--- a/tests/html_angular/interpolation.component.html
+++ b/tests/html_angular/interpolation.component.html
@@ -54,3 +54,13 @@
   {{ aNormalValue | aPipe }}:
   <strong>{{ aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis }}</strong>
 </span>
+<p>
+  {{
+      'delete'
+          | translate: {what: ('entities' | translate: {count: array.length})}
+  }}
+</p>
+<p>{{ {a:1+{} } }}</p>
+<p>{{ {a:a==={} } }}</p>
+<p>{{ {a:!{} } }}</p>
+<p>{{ {a:a?b:{} } }}</p>
diff --git a/tests/html_angular/jsfmt.spec.js b/tests/html_angular/jsfmt.spec.js
index b3183c49984d..a9de421417af 100644
--- a/tests/html_angular/jsfmt.spec.js
+++ b/tests/html_angular/jsfmt.spec.js
@@ -2,3 +2,4 @@ run_spec(__dirname, ["angular"]);
 run_spec(__dirname, ["angular"], { trailingComma: "es5" });
 run_spec(__dirname, ["angular"], { printWidth: 1 });
 run_spec(__dirname, ["angular"], { htmlWhitespaceSensitivity: "ignore" });
+run_spec(__dirname, ["angular"], { bracketSpacing: false });