Now that you've got a bit of background regarding the motiviations for creating Angular 2.0, let's look at a few key feature areas.
AtScript is a language that is a superset of ES6 and it's being used to author Angular 2.0. It uses TypeScript's type syntax to represent optional types which can be used to generate runtime type assertions, rather than compile-time checks. It also extends the language with metadata annotations. Here's an example of what some AtScript code looks like:
import {Component} from 'angular';
import {Server} from './server';
@Component({selector: 'foo'})
export class MyComponent {
constructor(server:Server) {
this.server = server;
}
}
Here we have some baseline ES6 code with a couple of AtScript additions. The import statements at the top of the example and the class syntax come straight from ES6. There's nothing special there. But, take a look at the constructor function. Notice that the server parameter specifies a type. In AtScript, this type is used to generate a runtime type assertion. A reference is also stored in a known location so that a framework, such as a dependency injection framework, can locate the type information and use it. Notice also the @Component syntax above the class declaration. This is a metadata annotation. Component is actually a normal class like any other. When you decorate something with an annotation the compiler generates code that instantiates the annotation and stores it in a known location so that it can be accessed by a framework such as Angular. With that in mind, here's what the above code transpiles to in terms of straight ES6:
import * as rtts from 'rtts';
import {Component} from 'angular';
import {Server} from './server';
export class MyComponent {
constructor(server) {
rtts.types(server, Server);
this.server = server;
}
}
MyComponent.parameters = [{is:Server}];
MyComponent.annotate = [
new Component({selector: 'foo'})
];
RTTS stands for RunTime Type System. This is a small assertion library geared around runtime type checking. Here the compiler injects some code that will assert that the server variable is of type Server. This is an instance of a nominal type check. You can also write custom type assertions in order to use structural typing or apply adhoc type rules. When you deploy to production, the compiler can leave out these assertions in order to improve performance.
One nice thing is that independent of the type assertions, the type annotation and the metadata annotation can be tracked. Both of these annotations were translated to very simple ES5 compatible data structures stored on the MyComponent function itself. This makes it easy for any framework or library to discover this metadata and use it. Over the years this has proved to be quite a handy tool on platforms such as .NET and Java. It also bears some resemblance to Ruby's metaprogramming capabilities. Indeed, when combined with a library, annotations can be used to do metaprogramming, which is exactly how Angular 2.0 makes building directives easier. More on that later.
A core feature of Angular 1.x was Dependency Injection (DI). Through DI you can more easily follow a "divide and conquer" approach to software development. Complex problems can be conceptualized in terms of their roles and responsibilities. These can then be represented in objects which collaborate together to achieve the end goal. Large (or small) systems that are deconstructed in this way can be assembled at runtime through the use of a DI framework. Such systems are usually easier to test since the resulting design is more modular and allows for easier isolation of components. All this was possible in Angular 1.x of course. However, there were a few problems.
The first problem that plagued the 1.x DI implementation was related to minification. Since the DI relied on parsing parameter names from functions, essentially treating them as string tokens, when these names were changed during minification, they no longer matched the registered services, controllers and other components. The result was a broken app. An API was added to allow a more minification-friendly approach to DI, but it lacked the elegance of the original. Other problems with the 1.x implementation center around missing features common to the more advanced server-side DI frameworks available in the .NET and Java worlds. Two big examples of missing features that put constraints on developers are lifetime/scope control and child injectors.
Through AtScript we've introduced a generalized mechanism for associating metadata with any function. Also, the AtScript format for metadata is resilient in the face of minification and easy to write by hand with ES5. This makes it a fantastic candidate for supplying a DI library with the information it needs to construct object instances. I'm sure it's no surprise that that's exactly how the new DI works.
When the DI needs to instance a class (or call a function) it examines it to see if it has any associated metadata. Recall this code from the AtScript transpiled output above:
MyComponent.parameters = [{is:Server}];
If the new DI finds the parameters value it will use it to determine the dependencies of the function it's trying to invoke. In this case, it can tell that there is exactly one parameter of type Server. So it will acquire an instance of Server and push that into the function before invoking it. You can also be explicit by providing a special Inject annotation for the DI to use. This will override the parameter data. It's also easy to supply if you're using a language that doesn't automatically generate the parameter metadata. Here's what that looks like in pure ES5 code:
MyComponent.annotate = [new Inject(Server)];
The runtime affect of this is the same as the parameter data. It should be noted that you can actually use anything as an injection token. So you could do this:
MyComponent.annotate = [new Inject('my-string-token')];
As long as you configure the DI with something that it can map to 'my-string-token' it will work just fine. That said, the recommended usage is via constructor instances as I've shown in all previous examples.
In Angular 1.x, all instances in the DI container were singletons. This is the default for Angular 2.0 as well. In order to get different behavior you had to use Services, Providers, Constants, etc. That's some confusing stuff. Fortunately, the new DI has a new, more general and more powerful feature. It now has instance scope control. So, if you want the DI to always create a new instance of a class, every time you ask for one, you can just do this:
@TransientScope
export class MyClass { ... }
This becomes even more powerful when you create your own scope identifiers for use in combination with child injectors...
Child injectors are a major new feature. A child injector inherits from its parent all of its parent's services, but it has the ability to override them at the child level. When combining this with custom scope identifiers, you can easily call out certain types of objects in your system that should automatically be overridden in various scopes. It's pretty powerful. As an example of this, the new router has a "Child Routers" capability. Internally, each child router creates its own child injector. This allows for each part of the route to inherit services from parent routes or to override those services during different navigation scenarios.
If you've read this far, you must be very curious about Angular 2.0. Thanks for taking so much time. We've still got a way to go and we're about to get into the really interesting stuff: templating and binding. I'm going to discuss them in tandem here. While the databinding system is technically separate from the templating system, you experience them as a single unit when you write apps. So, I think addressing them side-by-side makes the most sense.
Let's start by understanding how a view gets on screen, then break things down a bit. Essentially, you start with an HTML fragment. This will live inside a <template> element. That HTML fragment is handed to the template compiler. The compiler traverses the fragment, identifying any directives, binding expressions, event handlers, etc. All of this data is extracted from the DOM itself into data structures which can be used to eventually instantiate the template. As part of this phase, some processing is done on the data such as parsing the binding expressions, for example. Every node that contains one of these special instructions is then tagged with a special class. The result of this process is cached so that none of this work needs to be repeated. We call this result a ProtoView. Once you've got a ProtoView you can use it to create a View. When a ProtoView makes a View all the directives that were previously identified are instantiated and attached to their DOM nodes. Watches are set up on binding expressions. Event handlers are configured. You get the idea. The data structures that were previously processed in the compile phase allow us to do this very quickly. Once you've got a View, you can display it by adding it to a ViewPort. A ViewPort represents a region of the screen that you can display Views in. As a developer you won't see most of this, you'll just write templates and it will work. But I wanted to layout the process at a high level briefly before I dig into the details.
One of the huge missing features in Angular 1.x was dynamic loading of code. If you wanted to add new directives or controllers on the fly, that was very hard or impossible. It certainly wasn't supported. In 2.0 we've designed things from scratch with async in mind. So, when you go to compile a template, that's actually an async process.
Now I need to mention a detail of template compilation I left out in my simplified explanation above. When you compile a template, you not only provide the compiler with a template, but you also provide a Component definition. We'll get into the details of that in a bit. For the sake of this explanation, the Component definition contains metadata about what directives, filters, etc. were used in the template. This ensures that the necessary dependencies are loaded before the template gets processed by the compiler. Because we are basing our code on the ES6 module spec, simply referencing the dependencies in the Component definition will cause the module loader to load them, if they haven't already been loaded. So, by integrating with ES6 modules in this way, we get dynamic loading of whatever we want for free.
Before we dig into the syntax of templates, we need to look at directives, Angular's means of extending HTML itself. In Angular 1.x the Directive Definition Object (DDO) was used to create directives. This seems to be one of the great sources of suffering for many an Angular developer.
What if we could make directives simpler?
We've been talking about modules, classes and annotations. What if we could leverage those core constructs to build directives? Well, of course that's what we did.
In Angular 2.0 there are three types of directives.
Component Directive - Creates a custom component composed of a View and a Controller. You can use it as a custom HTML element. Also, the router can map routes to Components.
Decorator Directive - Decorates an existing HTML element with additional behavior. A classic example is ng-show.
Template Directive - Transforms HTML into a reusable template. The directive author can control when and how the template is instantiated and inserted into the DOM. Examples include ng-if and ng-repeat.
You may have heard that Controllers are dead in Angular 2.0. Well, that's not exactly true. In reality, Controllers are one part of what we are calling a Component. The Component has a View and a Controller. The View is your HTML template and the Controller has your JavaScript behavior. Rather than needing an explicit registration API for the controller or some non-standard APIs like 1.x, in 2.0 you can just create a plain class with some annotations. Here's an example of the controller half of a tab container component (we'll look at the view a bit later):
@ComponentDirective({
selector:'tab-container',
directives:[NgRepeat]
})
export class TabContainer {
constructor(panes:Query<Pane>) {
this.panes = panes;
}
select(selectedPane:Pane) { ... }
}
There are several features to notice here.
First, the controller for the component is just a class. Its constructor will have its dependencies injected automatically. Because child injectors are used, it can get access to any service up the DOM hierarchy, but also services local to its own element. For example, here it's having a Query injected. This is a special collection that automatically stays synchronized with the child Pane elements and lets you know when things are added or removed. You could also have the Element itself injected. This allows you to handle the same logic as the $link callback from Angular 1.x, but it's handled in a more consistent fashion through the class's constructor.
Now, take a look at the @ComponentDirective annotation. This identifies the class as a Component and provides metadata that the compiler needs to plug it in. For example selector:'tab-container' is a CSS selector that will be used to match HTML. Any element that matches this selector will be turned into a TabContainer. Also, directives:[NgRepeat] indicates the dependencies that the template for this component has. I haven't shown you that yet. We'll look at that in a minute when we talk about syntax.
An important detail to note is that the template will bind directly to this class. That means any property or method of the class can be accessed directly in the template. This is similar to the "controller as" syntax from Angular 1.2. There's no $scope sitting between this class and the template. The result is a simplification of Angular's internals, a simpler syntax for the developer and less work marshaling things back and forth from the $scope object.
Let's look at a Decorator Directive next. What about a simple NgShow?
@DecoratorDirective({
selector:'[ng-show]',
bind: { 'ngShow': 'ngShow' },
observe: {'ngShow': 'ngShowChanged'}
})
export class NgShow {
constructor(element:Element) {
this.element = element;
}
ngShowChanged(newValue){
if(newValue){
this.element.style.display = 'block';
}else{
this.element.style.display = 'none';
}
}
}
Here we can see a few more aspects of directives. Again, we have a class with annotations. The constructor gets injected with the HTML Element that the decorator is attached to. The compiler knows this is a decorator because of the DecoratorDirective and knows to apply it to any element that matches the selector:'[ng-show]' CSS selector.
There's a couple of other curious properties on this annotation though.
bind: { 'ngShow': 'ngShow' } is used to map class properties to HTML attributes. Not all your class's properties are surfaced to the HTML as attributes. If you want your property to be bindable in HTML, you specify it in the bind metadata. The observe: {'ngShow': 'ngShowChanged'} tells the binding system that you want to be notified whenever the ngShow property changes and that you want to be called back using the ngShowChanged method. Notice that the ngShowChanged callback responds to changes by altering the display of the HTML element that it's attached to. (Note that this is a very naive implementation, only for demonstration purposes.)
Ok, so what does a Template Directive look like? Why don't we look at NgIf?
@TemplateDirective({
selector: '[ng-if]',
bind: {'ngIf': 'ngIf'},
observe: {'ngIf': 'ngIfChanged'}
})
export class NgIf {
constructor(viewFactory:BoundViewFactory, viewPort:ViewPort) {
this.viewFactory = viewFactory;
this.viewPort = viewPort;
this.view = null;
}
ngIfChanged(value) {
if (!value && this.view) {
this.view.remove();
this.view = null;
}
if (value) {
this.view = this.viewFactory.createView();
this.view.appendTo(this.viewPort);
}
}
}
Hopefully you can make sense of the TemplateDirective annotation. It registers this directive with the compiler and provides the necessary metadata to set up properties and observation, just as with the NgShow example. Being that this is a TemplateDirective, it has access to a couple of special services which can be injected into its constructor. The first is the ViewFactory. As I mentioned earlier, a Template Directive transform the HTML it is attached to into a template. The template is automatically compiled and you now have access to the view factory in your template directive. Calling the createView API on the factory instantiates the template itself. You also have access to a ViewPort. This represents the location in the DOM where the template was extracted from. You can use it to add or remove instances of the template from the DOM. Notice how the ngIfChanged callback responds to changes by instantiating the template and adding it to the view port, or removing it from the view port. If you were implementing something like NgRepeat instead, you could instantiate the template multiple times and even provide a specific data item to the createView API and then you could add multiple instance into the view port. That's the basics.
Now you've seen some canonical examples of the three types of directives. I hope that clarifies things a bit in terms of how you'll be able to extend the HTML compiler with new behavior.
However, there's an important thing I still haven't adequately explained: Controllers.
How do you create a controller for your application? Let's say you want to set up the router so it navigates to a controller and displays its view. How do you do that? The simple answer is that you do it with a Component Directive.
In Angular 1.x Directives and Controllers were two different things. There were different APIs and different capabilities. In Angular 2.0, since we've removed the DDO and made Directives class-based, we were able to unify Directives and Controllers into the Component model. So, now you have one way to accomplish both. So, when you are setting up your routes, you simply map the router to a ComponentDirective (which consists of a view and controller essentially, just like before).
So, if you were creating a hypothetical customer edit controller, you might have something like this:
@ComponentDirective
export class CustomerEditController {
constructor(server:Server) {
this.server = server;
this.customer = null;
}
activate(customerId) {
return this.server.loadCustomer(customerId)
.then(response => this.customer = response.customer);
}
}
There's nothing new here really. We are just injecting our hypothetical server service and using it to load up the customer when we are activated by the router. What's interesting is that you don't need a selector or any of the other metadata. The reason is that this component is not being used as a custom element. It's being dynamically created by the router and rendered dynamically into the DOM. As a result, you get to leave off the unneeded details
So, if you know how to make ComponentDirectives, you know how to build the equivalent of Angular 1.x controllers used by the router. This would have been very painful in Angular 1.x to unify, but since we have this nice class and metadata-driven system in Angular 2.0, directives are significantly simplified and it becomes very easy to create your "controllers" in this way.
Note: I'd like to point out that the directive code samples shown above are based on a combination of early prototype code and newer design document specs. They should be interpreted as an explanatory tool, not the exact syntax for directives, which is still in flux. The template compiler and binding languages are the most volatile parts of Angular 2.0 right now, with design changes happening quite frequently.
Template Syntax
So, you've got an understanding of the high level compilation process, that it can load code asynchronously, how to write directives and how they are plugged in and how controllers fit into the puzzle, but we still haven't looked at an actual template. Let's do that now by looking at the template for the hypothetical TabContainer I showed earlier. I'll include the directive code again here for convenience:
@ComponentDirective({
selector:'tab-container',
directives:[NgRepeat]
})
export class TabContainer {
constructor(panes:Query<Pane>) {
this.panes = panes;
}
select(selectedPane:Pane) { ... }
}
<template>
<div class="border">
<div class="tabs">
<div [ng-repeat|pane]="panes" class="tab" (^click)="select(pane)">
<img [src]="pane.icon"><span>${pane.name}</span>
</div>
</div>
<content></content>
</div>
</template>
Please suspend any horror you have when looking at that syntax. Yes, that is valid HTML according to the spec. No it's not our final binding syntax. But let's use this as en example so we have a starting point for a richer discussion.
The key to understanding the data binding syntax is in the left side of the attribute declaration. With this in mind, let's first look at the image tag.
<img [src]="pane.icon"><span>${pane.name}</span>
When you see an attribute name surrounded with [] that tells you that the right side, the value of the attribute, has a binding expression.
When you see an expression surrounded with ${} that tells you that there's an expression that should be interpolated into the content as a string. (This is the same syntax as ES6 uses for string interpolation.)
Both of these bindings are unidirectional from model/controller to view.
Now let's look at that scary div:
<div [ng-repeat|pane]="panes" class="tab" (^click)="select(pane)">
ng-repeat is a TemplateDirective. You can tell that we are binding it with an expression because it's surrounded by []. However, it also has a | and the word "pane". This indicates the local variable name to be used inside the template is "pane".
Now look over at (^click). Parenthesis are used to indicate that we are attaching the expression as an event handler. If there's a ^ as well inside the parens, that means we don't attach the handler directly to the DOM node, rather we let it bubble and be handled at the document level.
I'm holding off on expressing my own opinion on this and other things I've discussed in templating until the commentary section below. Let's ignore what you or I may think upon first sight of this and first talk about why a syntax like this was chosen.
Web Components change everything. This is another instance of the Web changing underneath the framework. Most databinding-based frameworks assume a fixed set of HTML elements and understand certain special behaviors of elements like inputs, etc. However, in a world with Web Components, no assumptions can be made. A developer, not targeting Angular, can create a custom element with any number of properties and events which do whatever he/she wants. Unfortunately, there's no way to inspect the Web Component and gather some metadata about it that could be used to drive the binding system. There's no way to know what events it actually raises, for example. Look at this:
<x-foo bar="..." baz="..."></x-foo>
Looking at bar and baz, can you determine which is the event and which is the property? No....and unfortunately Angular can't tell either because the Web Components spec doesn't include the notion of self-describing components. That's unfortunate because it means that a databinding system can't tell whether or not it needs to connect up a binding expression or whether it needs to add an event handler to invoke expressions. In order to solve this problem, we need a generalized databinding system with syntax that allows the developer to tell it what is an event and what is a property binding.
That's not the only difficulty though. Additionally, the information has to be provided in such a way that it will not break the Web Component itself. What I mean by this is that the Web Component cannot be allowed to see the expression. That could break the component. It can only be allowed to see the result of evaluating the expression. Actually this doesn't only apply to Web Components, but also to built-ins. Consider this:
<img src="{{some.expression}}">
This code will cause a bad http request to be made to try to find the "some.expression" image. That's not what we want at all though. We never want img to see that expression, only the value of it. AngularJS 1.x solved this with ng-src, a custom directive. Now, back to Web Components....it would be a disaster if you had to create a custom directive for every attribute of any Web Component, wouldn't it? I don't think we want that, so we need to solve this problem more generally in the binding system.
To accomplish this, you have two options. The first is to remove the attribute from the DOM during template compilation. That will prevent the Web Component from getting ahold of the expression text. However, doing this means that inspecting the DOM will show no trace of the binding expression that is operating on the attribute. This would make debugging more difficult. Another option is to actually encode the attribute name so that the Web Component doesn't "recognize" it. This would allow Angular to to see the expression, but prevent it from being seen by the Web Component. It would also allow us to leave the attribute on the element after compilation so that inspecting the DOM would allow you to see it. That's a better debugging story for sure.
AtScript
AtScript is a language that is a superset of ES6 and it's being used to author Angular 2.0. It uses TypeScript's type syntax to represent optional types which can be used to generate runtime type assertions, rather than compile-time checks. It also extends the language with metadata annotations. Here's an example of what some AtScript code looks like:
import {Component} from 'angular';
import {Server} from './server';
@Component({selector: 'foo'})
export class MyComponent {
constructor(server:Server) {
this.server = server;
}
}
Here we have some baseline ES6 code with a couple of AtScript additions. The import statements at the top of the example and the class syntax come straight from ES6. There's nothing special there. But, take a look at the constructor function. Notice that the server parameter specifies a type. In AtScript, this type is used to generate a runtime type assertion. A reference is also stored in a known location so that a framework, such as a dependency injection framework, can locate the type information and use it. Notice also the @Component syntax above the class declaration. This is a metadata annotation. Component is actually a normal class like any other. When you decorate something with an annotation the compiler generates code that instantiates the annotation and stores it in a known location so that it can be accessed by a framework such as Angular. With that in mind, here's what the above code transpiles to in terms of straight ES6:
import * as rtts from 'rtts';
import {Component} from 'angular';
import {Server} from './server';
export class MyComponent {
constructor(server) {
rtts.types(server, Server);
this.server = server;
}
}
MyComponent.parameters = [{is:Server}];
MyComponent.annotate = [
new Component({selector: 'foo'})
];
RTTS stands for RunTime Type System. This is a small assertion library geared around runtime type checking. Here the compiler injects some code that will assert that the server variable is of type Server. This is an instance of a nominal type check. You can also write custom type assertions in order to use structural typing or apply adhoc type rules. When you deploy to production, the compiler can leave out these assertions in order to improve performance.
One nice thing is that independent of the type assertions, the type annotation and the metadata annotation can be tracked. Both of these annotations were translated to very simple ES5 compatible data structures stored on the MyComponent function itself. This makes it easy for any framework or library to discover this metadata and use it. Over the years this has proved to be quite a handy tool on platforms such as .NET and Java. It also bears some resemblance to Ruby's metaprogramming capabilities. Indeed, when combined with a library, annotations can be used to do metaprogramming, which is exactly how Angular 2.0 makes building directives easier. More on that later.
Dependency Injection
A core feature of Angular 1.x was Dependency Injection (DI). Through DI you can more easily follow a "divide and conquer" approach to software development. Complex problems can be conceptualized in terms of their roles and responsibilities. These can then be represented in objects which collaborate together to achieve the end goal. Large (or small) systems that are deconstructed in this way can be assembled at runtime through the use of a DI framework. Such systems are usually easier to test since the resulting design is more modular and allows for easier isolation of components. All this was possible in Angular 1.x of course. However, there were a few problems.
The first problem that plagued the 1.x DI implementation was related to minification. Since the DI relied on parsing parameter names from functions, essentially treating them as string tokens, when these names were changed during minification, they no longer matched the registered services, controllers and other components. The result was a broken app. An API was added to allow a more minification-friendly approach to DI, but it lacked the elegance of the original. Other problems with the 1.x implementation center around missing features common to the more advanced server-side DI frameworks available in the .NET and Java worlds. Two big examples of missing features that put constraints on developers are lifetime/scope control and child injectors.
Annotations
Through AtScript we've introduced a generalized mechanism for associating metadata with any function. Also, the AtScript format for metadata is resilient in the face of minification and easy to write by hand with ES5. This makes it a fantastic candidate for supplying a DI library with the information it needs to construct object instances. I'm sure it's no surprise that that's exactly how the new DI works.
When the DI needs to instance a class (or call a function) it examines it to see if it has any associated metadata. Recall this code from the AtScript transpiled output above:
MyComponent.parameters = [{is:Server}];
If the new DI finds the parameters value it will use it to determine the dependencies of the function it's trying to invoke. In this case, it can tell that there is exactly one parameter of type Server. So it will acquire an instance of Server and push that into the function before invoking it. You can also be explicit by providing a special Inject annotation for the DI to use. This will override the parameter data. It's also easy to supply if you're using a language that doesn't automatically generate the parameter metadata. Here's what that looks like in pure ES5 code:
MyComponent.annotate = [new Inject(Server)];
The runtime affect of this is the same as the parameter data. It should be noted that you can actually use anything as an injection token. So you could do this:
MyComponent.annotate = [new Inject('my-string-token')];
As long as you configure the DI with something that it can map to 'my-string-token' it will work just fine. That said, the recommended usage is via constructor instances as I've shown in all previous examples.
Instance Scope
In Angular 1.x, all instances in the DI container were singletons. This is the default for Angular 2.0 as well. In order to get different behavior you had to use Services, Providers, Constants, etc. That's some confusing stuff. Fortunately, the new DI has a new, more general and more powerful feature. It now has instance scope control. So, if you want the DI to always create a new instance of a class, every time you ask for one, you can just do this:
@TransientScope
export class MyClass { ... }
This becomes even more powerful when you create your own scope identifiers for use in combination with child injectors...
Child Injectors
Child injectors are a major new feature. A child injector inherits from its parent all of its parent's services, but it has the ability to override them at the child level. When combining this with custom scope identifiers, you can easily call out certain types of objects in your system that should automatically be overridden in various scopes. It's pretty powerful. As an example of this, the new router has a "Child Routers" capability. Internally, each child router creates its own child injector. This allows for each part of the route to inherit services from parent routes or to override those services during different navigation scenarios.
Templating and Databinding
If you've read this far, you must be very curious about Angular 2.0. Thanks for taking so much time. We've still got a way to go and we're about to get into the really interesting stuff: templating and binding. I'm going to discuss them in tandem here. While the databinding system is technically separate from the templating system, you experience them as a single unit when you write apps. So, I think addressing them side-by-side makes the most sense.
Let's start by understanding how a view gets on screen, then break things down a bit. Essentially, you start with an HTML fragment. This will live inside a <template> element. That HTML fragment is handed to the template compiler. The compiler traverses the fragment, identifying any directives, binding expressions, event handlers, etc. All of this data is extracted from the DOM itself into data structures which can be used to eventually instantiate the template. As part of this phase, some processing is done on the data such as parsing the binding expressions, for example. Every node that contains one of these special instructions is then tagged with a special class. The result of this process is cached so that none of this work needs to be repeated. We call this result a ProtoView. Once you've got a ProtoView you can use it to create a View. When a ProtoView makes a View all the directives that were previously identified are instantiated and attached to their DOM nodes. Watches are set up on binding expressions. Event handlers are configured. You get the idea. The data structures that were previously processed in the compile phase allow us to do this very quickly. Once you've got a View, you can display it by adding it to a ViewPort. A ViewPort represents a region of the screen that you can display Views in. As a developer you won't see most of this, you'll just write templates and it will work. But I wanted to layout the process at a high level briefly before I dig into the details.
Dynamic Loading
One of the huge missing features in Angular 1.x was dynamic loading of code. If you wanted to add new directives or controllers on the fly, that was very hard or impossible. It certainly wasn't supported. In 2.0 we've designed things from scratch with async in mind. So, when you go to compile a template, that's actually an async process.
Now I need to mention a detail of template compilation I left out in my simplified explanation above. When you compile a template, you not only provide the compiler with a template, but you also provide a Component definition. We'll get into the details of that in a bit. For the sake of this explanation, the Component definition contains metadata about what directives, filters, etc. were used in the template. This ensures that the necessary dependencies are loaded before the template gets processed by the compiler. Because we are basing our code on the ES6 module spec, simply referencing the dependencies in the Component definition will cause the module loader to load them, if they haven't already been loaded. So, by integrating with ES6 modules in this way, we get dynamic loading of whatever we want for free.
Directives
Before we dig into the syntax of templates, we need to look at directives, Angular's means of extending HTML itself. In Angular 1.x the Directive Definition Object (DDO) was used to create directives. This seems to be one of the great sources of suffering for many an Angular developer.
What if we could make directives simpler?
We've been talking about modules, classes and annotations. What if we could leverage those core constructs to build directives? Well, of course that's what we did.
In Angular 2.0 there are three types of directives.
Component Directive - Creates a custom component composed of a View and a Controller. You can use it as a custom HTML element. Also, the router can map routes to Components.
Decorator Directive - Decorates an existing HTML element with additional behavior. A classic example is ng-show.
Template Directive - Transforms HTML into a reusable template. The directive author can control when and how the template is instantiated and inserted into the DOM. Examples include ng-if and ng-repeat.
You may have heard that Controllers are dead in Angular 2.0. Well, that's not exactly true. In reality, Controllers are one part of what we are calling a Component. The Component has a View and a Controller. The View is your HTML template and the Controller has your JavaScript behavior. Rather than needing an explicit registration API for the controller or some non-standard APIs like 1.x, in 2.0 you can just create a plain class with some annotations. Here's an example of the controller half of a tab container component (we'll look at the view a bit later):
@ComponentDirective({
selector:'tab-container',
directives:[NgRepeat]
})
export class TabContainer {
constructor(panes:Query<Pane>) {
this.panes = panes;
}
select(selectedPane:Pane) { ... }
}
There are several features to notice here.
First, the controller for the component is just a class. Its constructor will have its dependencies injected automatically. Because child injectors are used, it can get access to any service up the DOM hierarchy, but also services local to its own element. For example, here it's having a Query injected. This is a special collection that automatically stays synchronized with the child Pane elements and lets you know when things are added or removed. You could also have the Element itself injected. This allows you to handle the same logic as the $link callback from Angular 1.x, but it's handled in a more consistent fashion through the class's constructor.
Now, take a look at the @ComponentDirective annotation. This identifies the class as a Component and provides metadata that the compiler needs to plug it in. For example selector:'tab-container' is a CSS selector that will be used to match HTML. Any element that matches this selector will be turned into a TabContainer. Also, directives:[NgRepeat] indicates the dependencies that the template for this component has. I haven't shown you that yet. We'll look at that in a minute when we talk about syntax.
An important detail to note is that the template will bind directly to this class. That means any property or method of the class can be accessed directly in the template. This is similar to the "controller as" syntax from Angular 1.2. There's no $scope sitting between this class and the template. The result is a simplification of Angular's internals, a simpler syntax for the developer and less work marshaling things back and forth from the $scope object.
Let's look at a Decorator Directive next. What about a simple NgShow?
@DecoratorDirective({
selector:'[ng-show]',
bind: { 'ngShow': 'ngShow' },
observe: {'ngShow': 'ngShowChanged'}
})
export class NgShow {
constructor(element:Element) {
this.element = element;
}
ngShowChanged(newValue){
if(newValue){
this.element.style.display = 'block';
}else{
this.element.style.display = 'none';
}
}
}
Here we can see a few more aspects of directives. Again, we have a class with annotations. The constructor gets injected with the HTML Element that the decorator is attached to. The compiler knows this is a decorator because of the DecoratorDirective and knows to apply it to any element that matches the selector:'[ng-show]' CSS selector.
There's a couple of other curious properties on this annotation though.
bind: { 'ngShow': 'ngShow' } is used to map class properties to HTML attributes. Not all your class's properties are surfaced to the HTML as attributes. If you want your property to be bindable in HTML, you specify it in the bind metadata. The observe: {'ngShow': 'ngShowChanged'} tells the binding system that you want to be notified whenever the ngShow property changes and that you want to be called back using the ngShowChanged method. Notice that the ngShowChanged callback responds to changes by altering the display of the HTML element that it's attached to. (Note that this is a very naive implementation, only for demonstration purposes.)
Ok, so what does a Template Directive look like? Why don't we look at NgIf?
@TemplateDirective({
selector: '[ng-if]',
bind: {'ngIf': 'ngIf'},
observe: {'ngIf': 'ngIfChanged'}
})
export class NgIf {
constructor(viewFactory:BoundViewFactory, viewPort:ViewPort) {
this.viewFactory = viewFactory;
this.viewPort = viewPort;
this.view = null;
}
ngIfChanged(value) {
if (!value && this.view) {
this.view.remove();
this.view = null;
}
if (value) {
this.view = this.viewFactory.createView();
this.view.appendTo(this.viewPort);
}
}
}
Hopefully you can make sense of the TemplateDirective annotation. It registers this directive with the compiler and provides the necessary metadata to set up properties and observation, just as with the NgShow example. Being that this is a TemplateDirective, it has access to a couple of special services which can be injected into its constructor. The first is the ViewFactory. As I mentioned earlier, a Template Directive transform the HTML it is attached to into a template. The template is automatically compiled and you now have access to the view factory in your template directive. Calling the createView API on the factory instantiates the template itself. You also have access to a ViewPort. This represents the location in the DOM where the template was extracted from. You can use it to add or remove instances of the template from the DOM. Notice how the ngIfChanged callback responds to changes by instantiating the template and adding it to the view port, or removing it from the view port. If you were implementing something like NgRepeat instead, you could instantiate the template multiple times and even provide a specific data item to the createView API and then you could add multiple instance into the view port. That's the basics.
Now you've seen some canonical examples of the three types of directives. I hope that clarifies things a bit in terms of how you'll be able to extend the HTML compiler with new behavior.
However, there's an important thing I still haven't adequately explained: Controllers.
How do you create a controller for your application? Let's say you want to set up the router so it navigates to a controller and displays its view. How do you do that? The simple answer is that you do it with a Component Directive.
In Angular 1.x Directives and Controllers were two different things. There were different APIs and different capabilities. In Angular 2.0, since we've removed the DDO and made Directives class-based, we were able to unify Directives and Controllers into the Component model. So, now you have one way to accomplish both. So, when you are setting up your routes, you simply map the router to a ComponentDirective (which consists of a view and controller essentially, just like before).
So, if you were creating a hypothetical customer edit controller, you might have something like this:
@ComponentDirective
export class CustomerEditController {
constructor(server:Server) {
this.server = server;
this.customer = null;
}
activate(customerId) {
return this.server.loadCustomer(customerId)
.then(response => this.customer = response.customer);
}
}
There's nothing new here really. We are just injecting our hypothetical server service and using it to load up the customer when we are activated by the router. What's interesting is that you don't need a selector or any of the other metadata. The reason is that this component is not being used as a custom element. It's being dynamically created by the router and rendered dynamically into the DOM. As a result, you get to leave off the unneeded details
So, if you know how to make ComponentDirectives, you know how to build the equivalent of Angular 1.x controllers used by the router. This would have been very painful in Angular 1.x to unify, but since we have this nice class and metadata-driven system in Angular 2.0, directives are significantly simplified and it becomes very easy to create your "controllers" in this way.
Note: I'd like to point out that the directive code samples shown above are based on a combination of early prototype code and newer design document specs. They should be interpreted as an explanatory tool, not the exact syntax for directives, which is still in flux. The template compiler and binding languages are the most volatile parts of Angular 2.0 right now, with design changes happening quite frequently.
Template Syntax
So, you've got an understanding of the high level compilation process, that it can load code asynchronously, how to write directives and how they are plugged in and how controllers fit into the puzzle, but we still haven't looked at an actual template. Let's do that now by looking at the template for the hypothetical TabContainer I showed earlier. I'll include the directive code again here for convenience:
@ComponentDirective({
selector:'tab-container',
directives:[NgRepeat]
})
export class TabContainer {
constructor(panes:Query<Pane>) {
this.panes = panes;
}
select(selectedPane:Pane) { ... }
}
<template>
<div class="border">
<div class="tabs">
<div [ng-repeat|pane]="panes" class="tab" (^click)="select(pane)">
<img [src]="pane.icon"><span>${pane.name}</span>
</div>
</div>
<content></content>
</div>
</template>
Please suspend any horror you have when looking at that syntax. Yes, that is valid HTML according to the spec. No it's not our final binding syntax. But let's use this as en example so we have a starting point for a richer discussion.
The key to understanding the data binding syntax is in the left side of the attribute declaration. With this in mind, let's first look at the image tag.
<img [src]="pane.icon"><span>${pane.name}</span>
When you see an attribute name surrounded with [] that tells you that the right side, the value of the attribute, has a binding expression.
When you see an expression surrounded with ${} that tells you that there's an expression that should be interpolated into the content as a string. (This is the same syntax as ES6 uses for string interpolation.)
Both of these bindings are unidirectional from model/controller to view.
Now let's look at that scary div:
<div [ng-repeat|pane]="panes" class="tab" (^click)="select(pane)">
ng-repeat is a TemplateDirective. You can tell that we are binding it with an expression because it's surrounded by []. However, it also has a | and the word "pane". This indicates the local variable name to be used inside the template is "pane".
Now look over at (^click). Parenthesis are used to indicate that we are attaching the expression as an event handler. If there's a ^ as well inside the parens, that means we don't attach the handler directly to the DOM node, rather we let it bubble and be handled at the document level.
I'm holding off on expressing my own opinion on this and other things I've discussed in templating until the commentary section below. Let's ignore what you or I may think upon first sight of this and first talk about why a syntax like this was chosen.
Web Components change everything. This is another instance of the Web changing underneath the framework. Most databinding-based frameworks assume a fixed set of HTML elements and understand certain special behaviors of elements like inputs, etc. However, in a world with Web Components, no assumptions can be made. A developer, not targeting Angular, can create a custom element with any number of properties and events which do whatever he/she wants. Unfortunately, there's no way to inspect the Web Component and gather some metadata about it that could be used to drive the binding system. There's no way to know what events it actually raises, for example. Look at this:
<x-foo bar="..." baz="..."></x-foo>
Looking at bar and baz, can you determine which is the event and which is the property? No....and unfortunately Angular can't tell either because the Web Components spec doesn't include the notion of self-describing components. That's unfortunate because it means that a databinding system can't tell whether or not it needs to connect up a binding expression or whether it needs to add an event handler to invoke expressions. In order to solve this problem, we need a generalized databinding system with syntax that allows the developer to tell it what is an event and what is a property binding.
That's not the only difficulty though. Additionally, the information has to be provided in such a way that it will not break the Web Component itself. What I mean by this is that the Web Component cannot be allowed to see the expression. That could break the component. It can only be allowed to see the result of evaluating the expression. Actually this doesn't only apply to Web Components, but also to built-ins. Consider this:
<img src="{{some.expression}}">
This code will cause a bad http request to be made to try to find the "some.expression" image. That's not what we want at all though. We never want img to see that expression, only the value of it. AngularJS 1.x solved this with ng-src, a custom directive. Now, back to Web Components....it would be a disaster if you had to create a custom directive for every attribute of any Web Component, wouldn't it? I don't think we want that, so we need to solve this problem more generally in the binding system.
To accomplish this, you have two options. The first is to remove the attribute from the DOM during template compilation. That will prevent the Web Component from getting ahold of the expression text. However, doing this means that inspecting the DOM will show no trace of the binding expression that is operating on the attribute. This would make debugging more difficult. Another option is to actually encode the attribute name so that the Web Component doesn't "recognize" it. This would allow Angular to to see the expression, but prevent it from being seen by the Web Component. It would also allow us to leave the attribute on the element after compilation so that inspecting the DOM would allow you to see it. That's a better debugging story for sure.
No comments:
Post a Comment