Always Bind with Brackets
Published: August 03, 2019This 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.