This blog shows the initial assessment of an application implemented by a different vendor for one of my clients using Angular and NodeJS with TypeScript.
The assessment proposed a new design and guidelines to be followed by a team of developers and co-op students and helped them in building 2 new applications besides the existing one.
In the first month of working for this client, I have written a new client and server from scratch, and below you can see the blueprint of this demo application.
For some of the sections, I have added details about how each concept evolved and has been implemented in the finalized application.
1) The client-side of the framework has been organized by modules.
Initial demo app:
Each module has its own routing file where the routes are defined.
The common “app-routing.module.ts” starts the application by default on the iap route: http://localhost:4200/iap
All the new web pages for the new project will be created inside this module.
By navigating to the URL from above, the old web site is displayed.
The first 2 web pages have been integrated into the new framework and can be used as an example to be followed for developing the new pages.
The initial concept evolved: each module ends up as a stand-alone application.
The application navigation menu code is common but its content comes from inside each module. The navigation code has been added to a node package that contains common functionality between the applications.
2) Every new module will have to contain the same folder structure:
Initial demo app:
Components
Models
Services
Shared
The components and services which are shared between modules will have to be placed in the “shared” folder displayed below the modules.
The structure of one of the applications in a later stage:
3) Common Node packages
Initial demo app:
The common services for both modules have been installed as node packages.
The common node packages need to be copied to the “dist” folder and installed from there using the “npm install” command.
The initial concept evolved: all the common functionality between the 2 applications has been moved into the common node package (UI components used for dialog messages, authentication services, etc.)
Below are the steps you need to follow when creating a shared node package library:
1.Install TypeScript
npm install -g TypeScript
2.Install ng-packagr
npm install ng-packagr -g
3.Install angular-cli
npm install @angular/cli -g
4.Generate the extracted package
ng generate library common-authentication
This creates a new folder named Projects with a folder structure underneath containing the module, component, service of the new library.
5.Add to the root's folder package.json the following build command
"build-common-auth": "ng build common-authentication"
6.Add to the root's folder package.json the following watch command
"ng build common-authentication --watch"
7.Build the package
npm run build-common-auth
8.Install the package into your project using a command like this one:
npm install dist/common-authentication
4) Removed ngRx folders and files – 81 files (144KB from a total of 294 KB)
Initial demo app:
While using the ngRx pattern is useful for complex applications, for a simple application where the user fills in some forms placed in a stepper control and saves the data to the database, I have decided it is overkill to maintain this structure. Working with a team of students it was important as well to have a simple to understand architecture.
By removing these classes half of the code from the app went away - 144KB from a total of 294 KB.
Since the services are singleton in Angular, the state of a page can be kept in the ApplicationService service and restored when needed.
app.service.ts
setPersonalInfo(personalInfoData: IPersonalInfo) {
this.personalInfoData = personalInfoData;
}
getPersonalInfo() {
return this.personalInfoData;
}
Personal-Info.component.ts
private loadFormData()
{
this.httpServicePI.getPIRequest("getApplicantBioData").subscribe((result) =>
{
// get cached data from the service
let cachedInfo = this.applicationService.getPersonalInfo();
if (cachedInfo) {
result = Object.assign(cachedInfo);
}
public onSaveClick(event: Event)
{
// prevent page refresh onSubmit action
event.preventDefault();
// Clearing unused form fields
this.clearFields(this.personalInfoForm);
if (this.personalInfoForm.valid)
{
// save data via a http post
this.httpServicePI.postRequest("storeApplicantBioData", this.personalInfoForm.value).subscribe();
// store saved data into the service variable
this.applicationService.setPersonalInfo(this.personalInfoForm.value);
5) Common HTTP service
All the API services from the old web site have been replaced by a single service: “application-http.service.ts”
Old app
New app
The “application-http.service.ts” file contains a service class for every collection of APIs.
These APIs calls into an HTTP service (common to all the modules) by calling get or post requests and passing the URL and data as parameters.
(Above you can see the “HttpServiceAI” that is the service for the APIs calls for the “ApplicationInfo” data – all services are similar except the type they are using i.e. “ApplicantInfo”.
This code is a candidate for making it more generic in the future using generics: <T>)
6) API caching
The HTTP-common.service.ts uses a separate service to implement caching: cacheable.service.ts
The API calls are done in 2 ways:
a) By calling getAPIRequest()
- This calls into httpCommonServiceAPI.getHttpData()
If the API call is to make for the first time, the calls make it to the server and return the data from the database. This data is cached and all the following calls to this method return the data from the cache.
b) By calling refreshPIRequest()
This calls into httpCommonServiceAPI.refresh()
In this case, the data force the cache to refresh by retrieving the data again from the server and caching it.
public getPIRequest(request: string, objectName: any = null): Observable<IPersonalInfo>
{
return this.httpCommonServicePI.getHttpData(request, objectName) as Observable<IPersonalInfo>;
}
public refreshPIRequest(): Observable<IPersonalInfo>
{
return this.httpCommonServicePI.refresh() as Observable<IPersonalInfo>;
}
Example: In the personal-info.component.ts constructor the provincesStates and language codes are retrieved from the server and the cached.
Each time the page is loaded, the calls are not sent to the server anymore (blue box in the image).
On the other hand, the Personal Information data is always refreshed from the server (you can see the number of times – 6 – it is refreshed from the server in the red box from the image)
let cachedInfo = this.applicationService.getPersonalInfo();
if (cachedInfo) {
this.httpServicePI.refreshPIRequest();
}
this.httpServiceCtry.getCtryRequest("getProvincesStates", "countriesList").subscribe(
result => {
this.countryList = result;
}
);
this.httpServiceLng.getLngRequest("getLanguageCodes", "QasData").subscribe(
result => {
this.languageList = result;
}
);
}
This level of caching will save some time when the pages load: in the image from below you can see the APIs calls to the server for the old site takes around 3 seconds out of the page’s load time of 5 seconds.
Caching will save around 2 seconds when loading the page for the initial load.
7) Coding guidelines – client-side
1. Keep imports on one line
Old app
New app
(Will have to find a way to make sure GIT formatting doesn’t screw it up.)
Solution found: Solved this by installing TypeScript Hero Visual Code extension and using Ctrl+Alt+O
2. Do not add un-necessary comments
Old app
New app
3. Declare variables at the top of the class and initialize them in the constructor
Same images as before.
4. Make data members and methods public, private, or protected.
Same images as before.
5. Use common functions for code that is similar and move these functions to the shared folder if they are used between components.
Old app
New app
6. Hide the implementation details from the HTML and move it to the code-behind.
Old app
New app
7) Move the text displayed in the pages on the server or in the database (to resource files or database – to help with the translation).
This feature will need to be added.
8) Formatting inside HTML
Split into several lines just for the flex attributes – if there are too many and you think it makes the code simpler to understand. However, the code’s readability is important too.
Old app
New app
8. The server-side code has been organized as follows:
The functionality for the old app module has been added in the controller, service, and repository prefixed with “iwa_”:
9) Initial demo app: The old app SQL statements have been moved from the code into the database via stored procedures:
Old app
New app
The initial concept evolved: while the demo implements methods to call both stored procedures and inline SQL statements, the team preferred to use the latter.
10) Each time when you need to create a set of related APIs, create a new controller, service, and repository class.
The name of the folder containing the controller class becomes automatically the route:
“iwa-api\iwa.controller.ts” makes the server to listen for the API calls on this address: http://localhost:5200/api/iwa-api/getApplicationInfo
All these classes need to define and implements an interface.
11) Initial demo app: All the repository’s methods need to log into the database the errors.
The initial concept evolved: since the applications are deployed to Azure, the logging is done by the Azure app service and additional information has been added by using Azure Application Insights ( see the blog from here: https://www.ideliversoft.com/post/azure-application-insights-1 )
12) The SQL driver class allows calling into the database using stored procedures or queries defined in the code.
The repository named: “newsfeedRepository.ts” contains examples for both these 2 cases.
However, as a whole team, we should use a single approach.
13) The APIs return JSON to the client.
However, the NewsFeedRepository’s methods that use stored procedures return a Promise of an object: Promise<NewsFeed> that eventually it is converted to a JSON.
Working with models (like NewsFeed) has the advantage of defining the business as entities (that may be beneficial if the complexity of the application will increase) and keeps together all the related methods that deals with formatting the model’s data – i.e. constructing the parameters list, etc.)
as opposed to:
We are recommending as a team practice to always use models.
On the same token, the server architecture implements the following classes:
Controller --> Service --> Repository
The service class may look redundant, but the concern of this class is the following:
- Implements the business logic (when one exists)
- Instantiate the test repository or the real repository classes
We are recommending as a team practice to implement the service class as well even if at this moment the business logic is very simple.
14) Both the client and the server application can be started in debug mode.
The tsconfig.json file defines the definitions used by the tsc compiler that builds the *.ts files into *.js files and moved them into the “./build” folder in the project’s root folder.
The launch.json file is required to be set up in order to be able to start a debug session on the server-side.
15) Initial demo app: The new app client-side framework can be run locally against the local version of SQL Server.
The connection parameters are found in the following environment variables:
The modified application’s web page always works for a saved application with the id of: 56000010000
The initial concept evolved: storing environment variables on the server-side can be done by using a .env file or a JSON file. I have opted for creating a ServerConstants class that contains methods to determine if the application is running locally and needs to load local variables or running from Azure app service and needs to use the variables defined into the app service's configuration.
16) Initial demo app: Responsive design and replacing Flex interfaces with CSS
Old app – HTML page
Old app – code-behind
Html CSS elements are defined in the code.
a) The Angular Design elements can be styles as CSS allowing for a cleaner code
b) For cases when we need to add more functionality in CSS we can use Bootstrap CSS and Angular Material together.
There is a third route that displays the Personal Info page re-written to use Bootstrap CSS.
New app
The Bootstrap CSS is simpler to understand, allows CSS customization, and allows the page to be responsive to changes.
This is still a work in progress, we are still researching the best solution to accommodate Angular Material with custom CSS and responsive design.
Performance:
Bootstrap related libraries total 169 KB and added a load of 396 ms to the page.
The initial concept evolved: the final application does not make use of Bootstrap CSS but it is using only Angular Material. SASS was used to create a structure of basic files that contain the definition for common elements and colors of the site and the components SCSS files were designed to use these elements.
The page Html has been cleaned up, contains Angular material controls and it is easy to understand.
18) Adhering to the new app development guidelines.
Code-reviews needs to happen moving forward.
The code needs to be reviewed before being moved to the build branch.
1. Keep imports on one line.
2. Do not add unnecessary comments.
3. Declare variables at the top of the class and initialize them in the constructor.
4. Make data members and methods public, private, or protected.
5. Use common functions for code that is similar and move these functions to the shared folder if they are used between components.
6. Hide the implementation details from the HTML and move it to the code-behind.
7. Move the text displayed in the pages on the server or in the database (to resource files or database – to help with the translation).
8. Move SQL statements from the code into the database via stored procedures.
9. On the server-side, all these classes need to define and implements an interface.
10. On the server-side, all the repository’s methods need to log into the database the errors.
11. On the server-side, use controller --> service --> repository classes as a design pattern.
12. On the server-side, always use a model class for your data.
13. On the client-side, use CSS classes as opposed to Flex classes that extend interfaces.
14. Use the client-side service's methods to cache API's calls that are not frequent.
Comments