How to use AngularJS Components with TypeScript?
If you are after a component-based application structure, AngularJS has a special kind of directive called Components that uses a simpler configuration and has some defaults. In this post, I’ll tell you how to use AngularJS Components with TypeScript to create a reusable component that will be responsible to show reviews for movies or restaurants and manage user ratings.
Writing a component-based application structure allow us to write an app in a way that’s similar to using Web Components or using Angular 2’s style of application architecture.
Advantages of Components
-
simpler configuration than plain directives
-
promote sane defaults and best practices
-
optimized for component-based architecture
-
writing component directives will make it easier to upgrade to Angular 2
When not to use Components
-
for directives that rely on DOM manipulation, adding event listeners etc, because the compile and link functions are unavailable
-
when you need advanced directive definition options like priority, terminal, multi-element
-
when you want a directive that is triggered by an attribute or CSS class, rather than an element
Scope
The scope of this post is to create a reusable reviews component that can show reviews and allows user feedback. This component should be able to display any kind of review, for e.g. movies or restaurants. The user-interface of this component looks like this:
Use AngularJS Components with TypeScript to write a reusable reviews component
Why use Components?
Practically, the above requirement is also possible using Angular Controller and Directive. Other than the advantages of components mentioned above a component allows us to use the best of both – controller and directive. I’ve realised that when using a component, we don’t miss anything and it covers most of the cases that one needs to write a simple or a business application. Read the points below to understand the code in this post properly.
What I like about Components?
- To consume a controller, we now don’t need a linked route as a component can exist with or without a route and internally it uses controller, directive. So, this also removes usage of any
data-ng-controllerattributes. - It enforces developers to write
controllerAssyntax which is one of the best practices to write controllers in AngularJS. By default, one can access the controller’s members using$ctrlin HTML template and also allows developers to name it something different likevmormodel, etc. - It encapsulates information like HTML template to glue up with controller or directive, annotate dependencies for injection so that app is not broken when source files are minified.
- It has some good defaults if compared to Directives. Directives can be used as an element or attribute with inherited or isolated scope. A component is quite simple that can be used as a custom element with isolated scope.
- It also has a well-defined life-cycle discussed below.
Component’s life-cycle events
Each component can implement “life-cycle hooks”. Following are the methods that will be called at certain points in the life of the component:
$onInit(): this method is invoked after component’s controller is instantiated which separates instantiation and initialisation setup. Example, you may want to consume some services in controller at the time of initialisation and not instantiation.$onDestroy(): this method is called when component’s controller is destroyed. Use this to release external resources, watches and event handlers.$onChanges(changesObj): this method is called when input bindings change i.e. whenever one-way bindings are updated. ThechangesObjis a hash whose keys are the names of the bound properties that have changed and the values are an object of the form.$postLink: this method is called when controller’s element and its children have been linked.
Make sure you have installed TypeScript for your IDE or code-editor and install AngularJS definition from DefinitelyTyped Repository in the form of Nuget or bower package – whatever you use. And need not to say, install AngularJS as well. The definition file provides strong typing and good intellisense support.
If you have read all the points above, let us start making a reusable reviews component. To use this component, we just want to define following in HTML template – inside body or featured section:
1 2 3 |
<body data–ng–app=“app”> <review–list></review–list> </body |
This looks neat and very similar to a directive. The end goal is to write this custom element and let Angular perform its magic. Let us now create app module:
1 2 3 |
namespace app { angular.module(‘app’, []); } |
Similar to other angular directives like ng-model or ng-click, for review-list we need to write a reviewList component. In code, the unique key needs to be in camel-case and in HTML template the usage should be with a hyphen before all upper-case alphabet. The definition for this component is defined in review-list.component.ts file as mentioned below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
namespace app.components { interface IReviewListComponentController { reviews: Array<any>; } class ReviewListComponentController implements IReviewListComponentController { reviews = []; constructor(private $http) { } $onInit() { this.fetchReviews().then(movies => { this.reviews = movies; }); } upRating(review) { if (review.rating < 5) { review.rating += 1; } } downRating(review) { if (review.rating > 1) { review.rating -= 1; } } fetchReviews() { // make this more flexible by getting data from outer scope by using component’s bindings. return this.$http.get(‘/app/components/reviews.json’) .then(response => response.data); } } class ReviewListComponent implements ng.IComponentOptions { templateUrl = ‘/app/components/review-list.component.html’; controllerAs = ‘model’; controller = [‘$http’, ReviewListComponentController]; } angular.module(‘app’).component(‘reviewList’, new ReviewListComponent()); } |
Line no. 46 is the way to define a component which is same as defining a controller or directive. It requires a key to register a component and the definition. As such we are using TypeScript’s class to define component, we need to instantiate it at this step.
Thanks to the angular’s definition file, we have a class named ReviewListComponent on line no. 40 that implements ng.IComponentOptions that allows us to define a template, controller with controllerAs syntax. Even if we miss to give another name for controllerAs member, by default, all the controller’s members can be accessed via $ctrl in HTML template – e.g. $ctrl.reviews but with code above it will be model.reviews. The dependencies needed by controller is defined within the array and the last item in it is the controller class reference. Angular will instantiate this by itself which is same behaviour when writing a controller directly.
At line no. 7, we have ReviewListComponentController class that implements an interface. This interface defines all the members that controller must have. This class also uses $onInit() event hook to fetch reviews at the time of initialisation. Ideally service calls like this should be encapsulated inside a angular service for reusability, testing and maintenance reasons. To keep it simple, fetchReviews() just loads the JSON data in reviews.json file as mentioned below. As you can see, this data can be served from a service and can be a list of movies or restaurants. For this example, it is a list of restaurants with ratings. The controller also defines two behaviours to up/down the ratings for a review.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[ { “id”: 1, “title”: “Pinocchio’s”, “rating”: 5 }, { “id”: 2, “title”: “Cafe du Soleil”, “rating”: 4 }, { “id”: 3, “title”: “Cafe des Amis”, “rating”: 4 }, { “id”: 4, “title”: “Kathton House”, “rating”: 5 } ] |
The view is defined separately in review-list.component.html file that is mentioned below: