Component Based Development With Angular and Typescript

Categories: Software Product Development |

Modern web applications are increasingly required to provide a user experience closer to that of a native application. Web development therefore increasingly relies on single page applications (SPA). This rise in SPA popularity brings several frameworks and tools to make our life easier.

 Component-Based Development Practices

As Wikipedia states “component based development, is a branch of software engineering that emphasizes the separation of concerns in respect of the wide-ranging functionality available. It is a reuse-based approach to defining, implementing and composing independent components”.

As applications grow, its source may host non-business logic (common functions, widgets, grids, extensions, etc), hosting this code at our application core is a bad practice, particularly for large applications. We should make the effort to ensure that the core of our application is only for business logic, and separate the rest into generic and reusable components. Every component should come with tests, documentation and examples of usage, which should be easy to install, configure and extend.

Why TypeScript?

Why not! TypeScript is a superset of JavaScript, it can run plain JS but it also adds a lot of aspects on top of it (interfaces, standard OOP, built in support for modules) to assist us in writing predictable and maintainable code. JS will have to wait for the upcoming ES6 (Find out more here: https://github.com/lukehoban/es6features/blob/master/README.md).

The Basics Of Creating A Component

The following section of this blog post will outline a typical angularjs widget using a service and a directive (we could be using other frameworks instead of Angular).

Let’s create a click counter button widget. The namespace for our module is MyWidgets.ClickCounter, I like the ‘MyWidgets’ namespace first because it can contain more widgets like our ClickCounter.

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
<b>File: clickCounterBase.ts</b>
 
module MyWidgets.ClickCounter {
 
"use strict";
 
 
 
// remember the path to the library for importing html templates too
 
var libraryCurrentPath: string = $("script[src$=’clickCounterBase.js']")[0]
 
.getAttribute("src").replace("clickCounterBase.js", "");
 
 
 
export interface IClickCounterService {
 
clickCounter: number;
 
clickMe: () =&gt; void;
 
}
 
}

Let’s implement an Angular service following the IClickCounterService interface. It is just a one var and one function service:

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
<b>File: clickCounterService.ts</b>
 
/// &lt;reference path='./clickCounterBase.ts' /&gt;
 
module MyWidgets.ClickCounter {
 
"use strict";
 
export class ClickCounterService implements IClickCounterService {
 
public clickCounter: number = 0;
 
public incrementCounter: () =&gt; void;
 
public static $inject = ["$rootScope"]; // will use $broadcast for unit testing
 
 
 
constructor($rootScope: ng.IRootScopeService) {
 
this.incrementCounter = (): void =&gt; {
 
this.clickCounter++;
 
// broadcast a signal for testing
 
$rootScope.$broadcast("clickCounter:incremented");
 
};
 
}
 
}
 
angular.module("clickCounterService", []).service("clickCounterService", [
 
"$rootScope", ($rootScope: ng.IRootScopeService): MyWidgets.ClickCounter =&gt;
 
new ClickCounterService($rootScope)
 
]);
 
}

We have a counter starting at 0, a function that increments the counter at every call and broadcasts a signal about it. Let’s test this!

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
File: clickCounterTests.ts
/// 
/// 
/// 
/// 
/// 
 
describe("Test clickCounterService", (): void =&gt; {
   angular.module("TestApp", ["clickCounterService"]);
 
   var clickCounterService: MyWidgets.ClickCounter.IClickCounterService;
   var $rootScope: any;
 
   beforeEach(module("TestApp"));
 
   beforeEach(inject(($injector: any): void =&gt; {
       clickCounterService = $injector.get("clickCounterService");
       $rootScope = $injector.get("$rootScope");
       spyOn($rootScope, "$broadcast");
   }));
 
   it("should increment click counter", (): void =&gt; {
       clickCounterService.incrementCounter();
       expect($rootScope.$broadcast).toHaveBeenCalledWith("clickCounter:incremented");
       // the counter starts with 0, so it should be 1 after the increment.
       expect(clickCounterService.clickCounter).toBe(1);
   });
});

And finally our directive and template.

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
File: clickCounterService.ts
module MyWidgets.ClickCounter {
	"use strict";
 
   export class UiClickCounter implements ng.IDirective {
       public templateUrl: string;
       public replace: boolean;
       public restrict: string;
       public scope: any;
       public link: ng.IDirectiveLinkFn;
 
       public static $inject = ["$timeout", "clickCounterService"];
 
       constructor($timeout: ng.ITimeoutService, clickCounterService: IClickCounterService) {
           this.scope = {};
           this.templateUrl = libraryCurrentPath + "uiClickCounter.html";
           this.replace = true;
           this.restrict = "E";
           this.link = (scope: any, iElement: ng.IAugmentedJQuery): void =&gt; {
               angular.noop(iElement);
 
	// called every time the button is clicked
	scope.increment: () =&gt; void = (): void =&gt; { clickCounterService.incrementCounter() 
}
 
	   // will update view counter when the clickCounterService broadcast an increment.
               var update: () =&gt; void = (): void =&gt; {
                   $timeout((): void =&gt; {
                       scope.clickCounter = clickCounterService.clickCounter;
                       scope.$apply();
                   });
               };
 
               update();
               scope.$on("clickCounterService:incremented", update);
           };
       }
   }
 
   angular.module("clickCounterDirective", []).directive("uiClickCounter", [
       "$timeout", "clickCounterService",
       ($timeout: ng.ITimeoutService, clickCounterService: IClickCounterService):
       MyWidgets.ClickCounter =&gt; new UiClickCounter($timeout, clickCounterService)
   ]);
}
 
File: uiClickCounter.html
<button> {{ scope.clickCounter }} </button>

Ready To Go!

Our component is done!. We have a module that can be imported to our main angular application and a directive that will contain our widget. The next step for integration is to use Gulp (for compilation, minification, TSLint, etc…) and Bower so the package can be easy to import and update. Our component should live in its own repository and come with documentation and an example application as well. What a great way to develop maintainable and reusable software!

    IMHO the most elegant solution for component based development is React.js + Flux, the React.js API is clear and so much easy that Angular (in terms of UI/Views). I don’t see the benefits of using TypeScript, if i wanna use ES6 today, would use Babel (http://babeljs.io/), which has more support for ES6 than TypeScript, if i need a optional type system for JS, would use Flow (http://flowtype.org/) or flowcheck (https://github.com/gcanti/flowcheck). With all this you could reuse existing tools for plain JS.