With the departure of the Angular 1.x concept of $scope
inheritance, mentally (partially or entirely) remodeling how information would be passed around your application is a must. In its place, you have an entirely new system of propagating information throughout the application's hierarchy.
Gone also is the concept of defaulting to bidirectional data binding. Although it made for an application that was simpler to reason about, bidirectional data binding incurs an unforgivably expensive drag on performance. This new system operates in an asymmetric fashion: members are propagated downwards through the component tree, but not upwards unless explicitly performed.
The code, links, and a live example of this are available at .
Suppose you had a simple application that intended to (but currently cannot) pass data from a parent ArticleComponent
to a child AttributionComponent
:
[app/components.ts] import {Component} from '@angular/core'; @Component({ selector: 'attribution', template: ` <h3>Written by: {{author}}</h3> ` }) export class AttributionComponent { author:string; } @Component({ selector: 'article', template: ` <h1>{{title}}</h1> <attribution></attribution> ` }) export class ArticleComponent { title:string = 'Belching Choir Begins World Tour'; name:string = 'Jake'; }
In this initial implementation, the components defined here are not yet aware of each other, and the <attribution></attribution>
tag will remain inert in the DOM. This is a good thing! It means these two components are completely decoupled, and you are able to only introduce connection logic as necessary.
First, since the <attribution></attribution>
tag appears inside the Article
component, you must make the component aware of the existence of AttributionComponent
. This is accomplished by introducing the component in the module in which ArticleComponent
is also declared:
[app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {ArticleComponent, AttributionComponent} from './components'; @NgModule({ imports: [ BrowserModule ], declarations: [ ArticleComponent, AttributionComponent ], bootstrap: [ ArticleComponent ] }) export class AppModule {}
For the purpose of this recipe, don't concern yourself just yet with the details of what NgModule
is doing. In the example in this recipe, the entire application is just an instance of ArticleComponent
with AttributionComponent
inside it. So, all the component declarations can be done inside the same module.
With this, you will see that ArticleComponent
is able to match the <attribution></attribution>
tag with the AttributionComponent
definition.
Inside a single module, the order of the definition could matter a lot. ES6 and TypeScript class declarations are not hoisted, so you cannot reference them at all before the declaration without generating errors. In this recipe, since ArticleComponent
is defined before AttributionComponent
, the former cannot directly reference the latter inside its definition.
If you were to instead define AttributionComponent
inside a separate module and import it with the module loader, the order issue becomes irrelevant. As you will notice, this is one of the excellent benefits of having a highly modular application structure.
One caveat to this is that Angular does make it possible to do out-of-order class references using a forwardRef
. However, if solving the order problem is possible by splitting it into separate modules, that is preferred over forwardRef
.
This being the case, go ahead and split your component file into two separate modules and import them accordingly:
[app/app.module.ts] import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {ArticleComponent} from './article.component'; import {AttributionComponent} from './attribution.component'; @NgModule({ imports: [ BrowserModule ], declarations: [ ArticleComponent, AttributionComponent ], bootstrap: [ ArticleComponent ] }) export class AppModule {} [app/article.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'article', template: ` <h1>{{title}}</h1> <attribution></attribution> ` }) export class ArticleComponent { title:string = 'Belching Choir Begins World Tour'; name:string = 'Jake'; } [app/attribution.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'attribution', template: ` <h3>Written by: {{author}}</h3> ` }) export class AttributionComponent { author:string; }
Similar to the Angular 1 directive's scope property object, in Angular 2, you must declare the members of the parent component to bring them down to the child component. In Angular 1, this could be done implicitly with an inherited $scope
, but this is no longer the case. Angular 2 component inputs must be explicitly defined.
Another important difference between Angular 1 and Angular 2 is that @Input
in Angular 2 is a unidirectional data binding feature. Data updates will flow downwards, and the parent will not be updated unless explicitly notified.
The process of declaring inputs in a child component is done through the Input
decorator, but the decorator is invoked inside the class definition instead of doing so in front of it. Input
is imported from the core module and invoked inside the class definition that is paired with a member.
Don't let this confuse you. The implementation of the actual decorating function is hidden from you since it is imported as a single target, so don't think much about what the @Input()
syntax is doing. There is a defined Input
function in the Angular source, and you are certainly invoking this method here. However, for your purposes, it is merely declaring the member that follows it as the one that will be passed in explicitly from the parent component. You use it in the same way as the Component decorator, just in a different place.
[app/attribution.component.ts] import {Component, Input} from '@angular/core'; @Component({ selector: 'attribution', template: ` <h3>Written by: {{author}} </h3> ` }) export class AttributionComponent { @Input() author:string; }
Next, you must pass the value bound to the child component tag to the parent component. In the context of this recipe, you want to pass the name
property of the Article
component object to the author
property of the Attribution
component object. One way of accomplishing this is by using the square bracket notation on the tag attribute, which specifies the attribute string as bound data:
[app/article.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'article', template: ` <h1>{{title}}</h1> <attribution [author]="name"></attribution> ` }) export class ArticleComponent { title:string = 'Belching Choir Begins World Tour'; name:string = 'Jake'; }
With this, you have successfully passed a member property down, from a parent to a child component!
Recall that the starting point of this example was that we had two components that didn't know the other exists, even though they are defined and exported inside the same module. The process demonstrated in this recipe is to provide the child component to the parent component, configure the child component to expect that a member will be bound to an input attribute, and finally provide that member in the template of the parent component.
Some people have an issue with the square bracket notation. It is valid HTML, but some developers feel it is unintuitive and looks odd.
Additionally, the bracket notation is not valid XML. Developers using HTML generated through XSLT will not be able to utilize the new syntax. Fortunately, everywhere the new Angular 2 syntax utilizes new new []
or ()
syntax, there is an equivalent syntax that the framework supports which will behave identically.
Instead of using pairs of square brackets, you can prefix the attribute name with bind-
and it will behave identically:
[app/article.component.ts] import {Component, Input} from '@angular/core'; @Component({ selector: 'article', template: ` <h1>{{ title }}</h1> <attribution bind-author="name"></attribution> ` }) export class ArticleComponent { title:string = 'Belching Choir Begins World Tour'; name:string = 'Jake'; }
Note that the value of the attribute name
is not a string but an expression. Angular knows how to evaluate this expression in the context of the parent component. As is the case with Angular expressions though, you are more than welcome to provide a static value and Angular will happily evaluate it and provide it to the child component.
For example, the following change would hardcode the child component to assign the string "Mike Snifferpippets"
as the author
property:
[app/article.component.ts] import {Component, Input} from '@angular/core'; import {AttributionComponent} from './attribution.component'; @Component({ selector: 'article', template: ` <h1>{{title}}</h1> <attribution [author]="'Mike Snifferpippets'"></attribution> `, directives: [AttributionComponent] }) export class ArticleComponent { title:string = 'Belching Choir Begins World Tour'; name:string = 'Jake'; }
The data binding you have set up in this recipe is actually unidirectional. More specifically, changes in the parent component member will propagate downwards to the child component, but changes to the child component member will not propagate upwards. This will be explored further in another recipe, but it is important to keep in mind that the Angular 2 data flow is, by default, downwards through the component tree.
Angular doesn't care about the nature of the bound value. TypeScript will enforce type correctness should you deviate from the declared type, but you are welcome to pass parent methods to the child with this strategy as well.
Keep in mind that passing a method bound in this way does not enforce the context in which it is evaluated. If the parent component passes a member method that utilizes the this
keyword and the child component evaluates it, this
will refer to the child component instance and not the parent component. Therefore, if the method tries to access the member data on the parent component, it will not be available.
There are a number of ways to mitigate this problem. Generally though, if you find you are passing a parent member method down to the child component and invoking it, there is probably a better way to design your application.