📜 ⬆️ ⬇️

Experience Angular + Typescript + Offline SPA project in a year

image I want to share my small positive experience about the project based on Angular + Typescript after a year. This is far from a new bundle, and I am sure that many already use it successfully. Of course, many are already waiting for more articles on React or Angular 2.0, but it seems to me, and this experience will be useful to someone.

Context


I work in a grocery company. The main product is a corporate application and a history of 10+ years (Web, ASP.Net, C # MSSQL). Despite the honorary age and codebase 1M + LoC, the project is still relevant, supported, refactoring and updates of libraries and approaches are being systematically carried out.

But here the client wanted to make the replacement of the main UI with SPA (Angular) so that it would work on the devices. - So the new project began a year ago.

Before that, I worked as an architect on the backend C # parts, and worked on business rules, SQL-performance, SQL-deadlocks, queues, and so on.
')
Javascript was actively working only on its pet-projects and the amount of code was not significant. I even had to transfer one of my pet projects from javascript to java (GWT switch) after 15k LoC! Due to the fact that the project has ceased to be supported. Hard transition justified itself (until Angular appeared).

Therefore, it was difficult for me to imagine how to write a javascript project with> 20k LoC and at the same time save it maintained, readability and extensibility.

After analyzing the current similar SPA projects on Backbone, Angular, I was once again convinced that they had 20k-35k LoC, the structure was, but not clear, any refactoring in them was no longer possible.

Requirements


The requirements of the new Angular project were estimated to be 2-4 times greater than the current.

Moreover, the requirements also required support for HTML5 Offline mode, with support for data editing (which means that in addition to REST, we still need to have a similar implementation on IndexedDB).

The start of the project was planned in a month or two. Therefore, I had enough calendar time to study the current experience of the current javascript team and enter a new javascript for me - Angular ecosystem.

Making decisions


Angular

Not even discussed. We had a team with 1-2 years of its use. The customer insisted on it. He also impressed me, and after my long experience with clean jQuery, GWT UI, heavy UI Toolkits, it seemed like a breath of fresh air.
Knockout, and the good old Backbone demanded to build a model on their objects / methods. I ate this such “restrictions” in full before that. The ability to use Plain Javascript Objects / Arrays in Angular outweighed any performance benefits of Knockout, Backbone.

On the other hand, Angular has its own characteristics - its own DI and overdesign.

Service, Module is the unit for structuring code (like a class), Factory, Controller is the sugar around Service. I didn’t really like to use Angular DI, which is also completely confusing to WebStorm 9.

Typescript

The main decision that I had been thinking about for a long time, tried, prototyped and promoted to the client. By the way, it was rather difficult then to explain to the client (and the team) why we should write in a different language, and not in Javascript which is to the trend, especially on a strategic project designed for a year.

It is important to understand the main message - Typescript is NOT another language, it is an extension of Javascript (just like LESS is a CSS preprocessor).
It is also important to understand why we generally need Compile-time Type Checking (by analogy with C #, Java, C ++):


From my point of view, the Typescript killer feature is optional typing.

When I worked with GWT (there you write on java in your IDE, and the compiler converts it to javascript), it bothers me that EVERYTHING, you need to mark each variable with a type, it is expensive and not necessary. I want to designate types only some things inside the application (contracts between modules, data and their conversion into a model, arguments and return of functions etc.).

By the way, C #, Java, C ++ are also moving towards optional typing - var, auto, dynamic.

If you just copy the strict-Javascript code into a Typescript file, then it compiles without problems. If type checking is not needed - just led to the special type “any”, and everything is possible:

var a=<any>0; // 1 var a:any=0; // 2 a().some().thing()[15].else(); 

Generic-types support is especially convenient (as well as C # and Java), together with ES6 Arrow functions all translations and work with promises and models are extremely readable and safe, and it saves this:

 function loadUsers():ng.IPromise<Array<User>>{ /*some*/} function loadBirthDates():ng.IPromise<Array<Date>>{ return loadUsers().then(users=>users.map(u=>u.birthDate)); } 

Here the IDE clearly knows that u - has a class User, and will generate an error if birthDate is not there or is not of type Date.

Support for ES6 class in Typescript.

At first glance, ES6 class is an ordinary sugar (for example, babeljs, the Typescript compiler that generates the prototype). But it’s not about the runtime, it’s about IDE support and millions of programmers thinking more about OOP, not about Functional style.

Example 1:

 function a(){ function b(){ function (){ function d(){} } } } 

Suppose that this code is written in the OOP style, where is the module, where is the class, and where is the class method? Or maybe it's all a class in the private method b in which inside it? Maybe an example and contrived, but sometimes so also make out the code, as well as through prototype.

Example 2:

 module a{ class b{ c(){ var d = ()=>{}; } } } 

Here the levels in OOP terms are already clear without explanation. Moreover, they are clear not only to the person but also to the IDE. And the IDE already knows exactly what it is.
Readability is several times higher, although it is compiled into all the same nested functions.

Also, unlike GWT, Typescript is compiled file-by-file, with all indents and comments preserved (in WebStorm, to save the file). I do not turn on source-maps in Chrome when viewing the Typescript file - the generated JS file is not much different.

Also in our Backend team, everyone knows C # and OOP. And using Typescript makes it possible to rotate them on the Frontend (CSS knowledge and layout is not critical, I repeat, we have a corporate application).

Structure and implementation


The project structure looks like this:


The framework "sees" the library, the business "sees" the framework and the library, ui and routing see everything.

This structure and agreements was not chosen by chance, it resembles our ASP.Net project to which everyone in the backend team is used to. It may seem like a strange thing from the backend to the SPA, but for me the parallels and the convenience of the developers are obvious.

Standard Typescript module support is used (although there are many options available): import (s) at the beginning of the file and _references.d.ts files. Not a very convenient solution, but understandable IDE, I think that we will move on to something else if the IDE understands it.

Ui layer is accessed by business only through * Facade classes.

A typical Facade is a class in which almost all methods return promises. Facade inside is responsible for calling Adapters.

 ///<reference path="../_references.d.ts"/> module business.security { export class SecurityFacade { loadUsers():ng.IPromise<Array<UserDto>>{} loadGroups():ng.IPromise<Array<GroupDto>>{} renameUser(userId:string, name:string):ng.IPromise<void>{} } } 

Offline support is simple, for example, SecurityFacade.loadUsers is implemented as follows: call SecurityAdapter.loadUsers — if promise returns an error — load SecurityOffineAdapter.loadUsers from IndexedDb, if successful, we cache a successful result in IndexedDb - UserOffineAdapter.saveUsers. There are many details (for example, the queue to save, the limit Indexed DB and TP), but roughly works like this.

 ///<reference path="../_references.d.ts"/> module business.security { export class SecurityFacade { loadUsers():ng.IPromise<Array<UserDto>>{ SecurityAdapter.loadUsers().then(data => { SecurityOffineAdapter.saveUsers(data); return data; }).catch(ex => { if (isOffline()) { return SecurityOffineAdapter.loadUsers();} throw ex }); } } } 

The decision not to use Angular DI is possible, outside the box. Even without a special need for Angular in business, we have to wrap all promise types (for example jQuery) and difference onSuccess / onError in Angular promise.
Using Angular promise is extremely necessary - it will automatically cause $ apply at the end. Otherwise, you yourself will need to flood your $ apply code with calls, and also make sure that you are not in the digest cycle. Fortunately, $ q, like any other provider, is easy to get outside of Angular DI, for example, to use Angular promise in business:
 var $q = angular.element(document.body).injector().get("$q"); 

Any objects stored in Json after downloading and before sending are always checked by the Adapter. Typescript will not help you with this. Therefore, in each Dto class, we have a copyAndVerify static method that can internally check and clean each property of the object and also call it on the sub-objects, and also can do an “upgrade” of the object to the higher version (for example, renaming the field and changing the type). Calling copyAndVerify makes an Adapter for each object in the collection. When saving it is also necessary - Angular hangs its methods on the object that needs to be cleaned before serialization, and Angular directives can change the type - for example, save the number as text.

UI: SecurityController is also an ordinary class, only it is registered in Angular, and also selects the desired view.

 ///<reference path="../_references.d.ts"/> module ui.security { import SecurityFacade = business.shared.SecurityFacade; export class SecurityController { users:Array<User>; constructor(public $scope:IScope, public $window:ng.IWindowService) { new SecurityFacade().loadUsers().then(users=>this.users); //Arrow-functions    var _this=this; } } // @ngInject function SecurityDirective ():ng.IDirective { return { scope: {}, templateUrl: routing.isMobile() ? 'ui/security/Security.html' : 'app/security/SecurityMobile.html', controller:SecurityController , controllerAs: 'securityCtrl' } } angular.module('app').directive('securityDirective', SecurityDirective); } 

One year later


A bunch of satisfied. In the production is already half a year. Already about 23k LoC and at the same time the project easily experienced 7 medium refactorings, team change. It continues to be easily maintained, soon we plan to add another big feature. Recently changed IndexedDb on the. Code-review is easy to conduct.

I had to tinker with offline support, but 200Mb is easily stored on all iPhones, iPads, Chome, IE10. Plus, the entire SPA (2.5Mb) is loaded from the local cache, so it starts smartly regardless of the Internet connection.

Performance on Angular does come to periodically pay attention - remove the watch and replace it with events, revise the directives and the views code.

Autotests, by the way, are also written in Typescript using the Protractor and PageObject approach.

Now Typescript is supported perfectly in WebStorm, Visual Studio (already good in 2015) and some others probably. If they chose now, they could have chosen React instead of Angular. I also think to partially use Typescript in ASP.Net on heavy UI controls.

Source: https://habr.com/ru/post/274591/


All Articles