Always Bind with Brackets

Published: August 03, 2019

This is a quick tip that will make your components more error-proof and easier to refactor.

Two Ways of Binding Constant String Values to Inputs

Consider the following 2 components:

@Component({
  selector: 'app-child-component',
  ...
})
export class ChildComponent {
  @Input() public title: string;
}

@Component({
  template: `
    <app-child-component title="Great Component">
    </app-child-component>
  `
})
export class ParentComponent {}

Here we have a ChildComponent that's used in the ParentComponent template. The part I want to talk about is how we're binding the title property.

When you're supplying a constant string value as an input to a component, you have two choices:

// Option 1
<app-child-component title="Great Component">
</app-child-component>

// Option 2
<app-child-component [title]="'Great Component'">
</app-child-component>

When the code is correct, both options have the exact same semantics.

When Things Go Wrong

But what happens when you accidentally bind to a property that doesn't exist? Let's say that we decided that label would be a more appropriate name than title, so we refactor the ChildComponent like this:

@Component({
  selector: 'app-child-component',
  ...
})
export class ChildComponent {
  @Input() public label: string; // title -> label
}

But then we forget to update the binding in the ParentComponent template.

First, let's see what happens if we used Option 1, a regular HTML attribute:

<app-child-component title="Great Component">
</app-child-component>

Since title doesn't exist on SomeComponent anymore, we'd hope that Angular would throw an error to let us know.

ng build --prod

chunk {0} runtime-es2015.10a1f01eef199006286d.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es2015.6eab524d525c70681e7e.js (main) 210 kB [initial] [rendered]
chunk {2} polyfills-es2015.e4a1185e6871d06f842f.js (polyfills) 36.4 kB [initial] [rendered]
chunk {3} styles.09e2c710755c8867a460.css (styles) 0 bytes [initial] [rendered]
Date: 2019-08-02T23:15:52.728Z - Hash: 51f8a815410eec2a0386 - Time: 11488ms

Unfortunately, the build succeeds and your component is silently broken.

What would have happened if we'd used a bracket binding?

<app-child-component [title]="'Great Component'">
</app-child-component>
ng build --prod

ERROR in Can't bind to 'title' since it isn't a known property of 'app-child-component'.

Perfect. Since we used a bracket binding, the build fails and we're reminded to fix our component.

The Difference

When binding without brackets, we're not really binding at all. We're just setting an HTML attribute that Angular will use to initialize an @Input property if it exists and silently ignore otherwise.

When binding with brackets, we're telling Angular to setup a real binding that evaluates the template expression and assigns it to an @Input on the component. If the propety doesn't exist, Angular throws an error.

Performance

There is a slight performance penalty for taking the safer approach. When we just set an attribute on an element, Angular doesn't actually setup a binding -- it just initializes the component value and that's it. When we bind with brackets, we setup a real binding. I doubt there's a measurable difference in any real app given the number other bindings you'd have, but I wanted to mention it for completness. In my opinion, the fact that you're binding a constant string is a coincidence. If you were binding an object or even a constant number, you wouldn't think twice about using brackets. Furthermore, with OnPush change detection, your bindings won't be evaluated on every app tick anyway.