Written for Angular 2 (Version 2.0.0-beta7). May work with other versions.
Introduction
This is the second artilce of this series. In the first post we created a small Angular 2 Application with two components. In this article we will add services with dependency injection and routing / navigation.
You can find the first article here: Angular 2 Tutorial in JavaScript/ES5 - Part 1 to 3
Using the code
Part 4 - Dependency Injection
The most important aspect in part 4 will be to pass a service in our AppComponent using dependency injection.
Create the Service to Inject
Create a service
For the HeroService
we create a separate JavaScript file named hero.service.js. We register the object as app.HeroService
and start with an constructor
. The code snippet below already contains the public getHeroes()
function.
(function (app) {
app.HeroService = ng.core
.Class({
constructor: function () {
this.getHeroes = function() {
return HEROES;
};
}
});
})(window.app || (window.app = {}));
Injectable Decoration
In TypeScript, the HeroService
gets decorated with Injectable()
.
@Injectable()
export class HeroService {
}
In the transpiled Javascript code we find a corresponding statement. But it seems to have no influence on the class's behavior if we skipped it.
Mock array
The HeroService
's getHeroes()
function returns the mock data HEROES
. Following the tutorial, we move this array from app.js to its own file mock-heroes.js.
Register Scripts in index.html
Don't forget to register the new files hero.service.js and mock-heroes.js in index.html. The script registration now should look like this.
<!--
<script src='app/mock-heroes.js'></script>
<script src='app/hero.service.js'></script>
<script src='app/hero-detail.js'></script>
<script src='app/app.js'></script>
<script src='app/main.js'></script>
Dependency Injection
In the next step we inject the HeroService
in AppComponent
. To resolve the dependencies correctly in Angular 2, we have to perform two steps:
Register the Provider
We register the provider at the component. This is almost the same for TypeScript and JavaScript. All we have to do change, is to specify, how to resolve the app.HeroService
from our Angular app directly in the providers property:
app.AppComponent = ng.core
.Component({
providers: [ app.HeroService ]
})
Inject the Service in the Constructor
Here comes the trickiest part. In TypeScript we can inject the provided instance in the constructor as arguments by providing the following details:
- access modifier
- variable name
- variable type
In our example, it looks like this:
constructor(private _heroService: HeroService) { }
The JavaScript version of this code is not as obvious on the first look. Instead of a single function we define the constructor as an array. This array contains the type of every provided instance we want to inject and as last element the constructor function. Then we can pass arguments to constructor, which get resolved as provided dependency types by order.
constructor: [
app.HeroService,
function (heroService) {
this.heroService = heroService;
}
]
Note, that there is no relation between provided types and argument names. If we injected the type app.HeroService
as a constructor argument named foo
, Angular would be totally finde.
Use the Injected Service
If heroService
was a public property, we had to add it to the object (e.g. this.heroService = heroService);
). But as we keep the object private, we will work with the constructor argument heroService
in the following getHeroes()
function.
this.getHeroes = function() {
this.heroes = heroService.getHeroes();
};
Now the application will start again. As you can see in the screen shot to the right, the heroes from the service will be listed again in the AppComponent.
Using Angular Life Cycle Hooks
It is a common use case to execute a function in our component, when it passes a certain "state" (e.g. being initialized or being destroyed). Therefore angular provides several life cycle hooks [^]. In this case we want to use the ngOnInit
hook to call getHeroes()
.
Deriving from OnInit
In TypeScript we would implement the interface OnInit
that provides a function ngOnInit()
, which will be called by Angular during the corresponding life cycle event. In the strict sense this is some sort of inheritance. So we will solve it in JavaScript by providing the correct function on our object's prototype:
app.HeroListComponent.prototype.ngOnInit =
function () {
this.getHeroes();
};
Other life cycle hooks, like ngOnChanges
or ngOnDestroy
can be used in the same way.
Using Promises
The last steps in part 4 cover how to process async results with promises. As this is a commonly used practices, you should take a close look at the tutorial. Here only comes the changed function for the heroService
:
this.getHeroes = function () {
return new Promise(resolve =>
setTimeout(() => resolve(HEROES), 2000)
);
};
Results
After completing these steps, the application will load the heroes from a service. To simulate a remote server, the response is 2 seconds delayed.
Part 5 - Routing and Navigation
Tutorial part 5 is all about routing and navigating in our app. Therefore we use the Angular 2 default router from ng.router
package.
Add a push URL Base
To enable client side navigation in the web browser, we use so called push states. Have a look at the angular documentation [^] and the mdn documentation [^] about this topic. In our application we implement a <base href="/">
element in our index.html.
<!--
<head>
<base href="/">
If you get the following exception in an Angular 2 application, it is very likely that you are missing this line.
EXCEPTION: Error during instantiation of LocationStrategy! (RouterLink -> Router -> Location -> LocationStrategy).
Configure the Router
Router Provider
In Angular 2 we need one component to host the router. This will be our AppComponent
. To tell our application how to resolve a router instance, we reference the router provider ng.router.ROUTER_PROVIDERS
.
.Component({
providers: [ng.router.ROUTER_PROVIDERS, app.HeroService]
})
Configure a Route
Next we can configure our first route to a component by specifying a path
, a name
and the component
's type. The first two properties are strings, the latter is, well..., the component's type as object. By adding the property useAsDefault
we tell the router which route to resolve if none was selected. To add the route to a route configuration, we pass it as argument to ng.router.RouterConfig()
. Usually we configure multiple routes at once. Therefore RouterConfig()
takes an array as argument. Then we decorate our AppComponent
with this instance.
app.AppComponent = __decorate([
ng.router.RouteConfig([ {
path: '/heroes',
name: 'Heroes',
component: app.HeroesComponent,
useAsDefault: true
}])
],
app.AppComponent);
The __decorate()
function in the code example is what we get from transpiled TypeScript code. Not the most readable part but it works.
Use the Router
We want to call a router link to navigate through the router's states.
Register the Router Directive
To use the router directives in HTML, we first reference the router directives.
.Component({
directives: [ng.router.ROUTER_DIRECTIVES],
providers: [ng.router.ROUTER_PROVIDERS, app.HeroService]
})
Router Links and Router Outlet
Having this implement in JavaScript, we can simply add a routerLink
and a <router-outlet />
in HTML:
<!--
<a [routerLink]="['Heroes']">Heroes</a>
<router-outlet>Heroes</router-outlet>
Results
When we start the app now, we see that the router navigated to our heroes component already. Note how the browser URL changed. Yet the heroes route is the only route we have and there is nothing to navigate to. We will change this in the following part.
Completing the Tutorial
Adding the router and configuring the routes was the toughest tasks of this part. Now we add a new component named dashboard and set up the routes between dashboard, hero list and hero detail view. When you stick with the original TypeScript tutorial, implementing this in JavaScript should not be that hard. You can find my JavaScript solution for part 5 attached to this post.
Conclusion
With a little effort, it is possible to set up this application using only JavaScript / ECMAScript 5. For a first approach to Angular 2, there is no need to deep-dive into TypeScript, altough some TypeScript knwoledge is necessary to understand the documentation and hints. If I had known these steps in advance, I'd saved myself quite some time of research, debugging and trying.
How were your first experiences with Angular 2, especially in combination with JavaScript/ES5? Did you try different appraoches? Could you figure out a simpler solution for certain aspects or did I miss something? Please let me know in the comments below.
History
- 5th March, 2016: Initial version
License
The original tutorial, in particular the source code files, are published under an MIT-style license that can be found in the LICENSE file at http://angular.io/license. This tutorial and the source code modifications are published under MIT license, that can be found at https://opensource.org/licenses/MIT.