General web/mobile development best practices : Angular example
This project is aimed to be the support example of a tutorial walking you through best practices of front-end development (web/mobile) with a concrete example based on an Angular project.
To see tutorial come as soon as possible you can vote here.
This project is the result of my experience working on helping startups and more traditional industries (in finance and aerospatial) defining and developing their front-end projects (web and mobule).
I have noticed, that every time, one of the most difficult parts when launching a product is defining the best practices and finding the best tools to put in place the development workflow.
So I have decided to create this project, to be a concentrate of best practices ready to use out of the box and that may save developers and spetially tech-leads/technical-architects days and even months of hard work to find and define the best workflow for their projects.
This project/tutorial main focus is development best practices. So, for the beginning, it won’t include any material related to Continuous Integration or application deployment.
Notice 1: Many of the best practices present in this project are, as mentioned before, general to front-end development and even to development in general (not only front-end), so even if you are not using Angular in your project you can walk through it to get some interesting ideas.
Notice 2: You can see the content of different project commits to have an idea of the evolution of the project and the steps to add/include a specific tool, library or pattern to the project.
This project was generated with Angular CLI version 7.3.1.
For this project I mainly use Yarn. But you can run the same scripts/commands using npm.
For example to start the project using yarn
you run yarn start
. To do the same thing using npm
you can run npm run start
.
To be able to launch this project you need to install:
npm
to run different scripts. (optional)Before being able to start the project, you have to install the different dependencies/libraries. To do so run:
# if npm
npm install
# if yarn
yarn
Here is a list of optional tools you may need in general for your projects’ development:
The main branch where you can find the latest working and tested code is the master.
You can follow the day to day commits and development on the develop branch.
A tagging system will come along different upgrades and releases of the project.
Run yarn start
for a dev server. Navigate to http://localhost:4200/
. The app will automatically reload if you change any of the source files.
Run ng generate component component-name
to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module
.
Run yarn build
to build the project. The build artifacts will be stored in the dist/
directory. Use the --prod
flag for a production build.
Actually, a default angular-cli generated project uses Karma tool for unit testing. The problem with Karma (it can be an advantage in some cases), is that it needs to launch a browser to run a test which in many cases is not necessary and at the same time extends the test execution time. In addition, you may have Continuous Integration integrated to you development/delivery process that runs on an environment where you can have a browser.
There is an interesting alternative to Karma
which is Jest. It makes it faster and easier to write tests. No browser is needed. It comes with built-in mocking and assertion abilities. In addition, Jest runs your tests concurrently in parallel, providing a smoother, faster test run.
jest-preset-angular : Used to make the jest configuration easier. The actual used version is 6.0.2, so documentation and the configuration will be different for the futur versions of this library.
Run yarn test:all
to execute the unit tests via Jest on the whole project.
If you want to run unit tests in a specific project like the connection
project run yarn test:connection
. Don’t forget to add the needed script to your package.json
file in addiion to the matching jest configuration file to be able to launch test on a new library. You can take the example of how it is done for the connection
library.
You can also launch you tests and watch for changes by running for exmaple yarn test
.watch
VS Code and Jest debug: If you use VS Code, you can debug your Jest based unit tests by adding a launch.json
file under your .vscode
folder (you can find an example file in the actual repo). The debugger will use the built-in Node debugger. A more complete documentation can be fond here.
Run yarn e2e
to execute the end-to-end tests via Protractor.
If we want to import a component from connection
library we can use the @connection
annotation.
Example : import { ConnectionModule } from '@connection'
;
This is possible thanks to the adding of the paths
attribute to the tsconfig.json
file.
"compilerOptions": {
...,
"paths": {
"@connection": [
"projects/connection/src/public_api"
],
...
},
...
}
If we want to get more specific about the path (for example in case of a circular dependancy), we can add an other path to the tsconfig.json
file like follow :
"compilerOptions": {
...,
"paths": {
"@connection": [
"projects/connection/src/public_api"
],
"@connection/*": [
"projects/connection/src/*"
]
...
},
...
}
It will allow as to import components or other angular exported functionalities like the following example :
Example : import { ConnectionComponent } from '@connection/lib/modules/main/pages';
;
To make sure that developers follow a precise worklow while commiting and pushing the code, so that you don’t have to do verfications and run scripts manually, the following tools are very useful :
In package.json
you add :
"scripts" {
"commit": "git-cz",
...
}
So when you run yarn commit
the cz-cli
is used. So no more direct git commit
.
cz-cli
plugin.In package.json
you add :
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
},
"cz-customizable": {
"config": "path/to/custom/cz-config.js"
}
},
...
If you don’t give any custom file in the configuration (config.cz-customizable.config
), the .cz-config.js
file present at the root of the project will be used.
Note: To be able to use VS Code to edit git commit comments or other file manipulation tasks instead of default vim
you can run git config --global core.editor "code --wait"
at the condiction that VS Code is available from commande line (you can check it by running code --help
).
More information here.
Add the husky
configuration at the root of the package.json
file :
"husky": {
"hooks": {
"pre-commit": "yarn lintstaged",
"prepush": "yarn prod"
}
}
If you want to skip the hools just add the --no-verify
flag to your git command. Example: git push --no-verify
So to the already defined husky
hooks configuration, you can add the commit-msg
hook :
"husky": {
"hooks": {
...,
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
commit-msg
hook allows you to lint commits before they are created.
You can add a commitlint.config.js
file at the root of the project, to define linting rules/conventions.
commitlint.config.js
example:
module.exports = {
// we use the default @commitlint/config-conventional rules.
// you have to install @commitlint/config-conventional library to be able to use it.
extends: ['@commitlint/config-conventional'],
// Any rules defined here will override rules from @commitlint/config-conventional
// => custom rules
rules: {
'header-max-length': [2, 'always', 100],
'subject-case': [
2,
'never',
['sentence-case', 'start-case', 'pascal-case', 'upper-case']
],
...
}
};
Note: If you want to retry a commit so that you don’t have to re-enter the same information again just run yarn commit:retry
.
The angular’s RouterModule was used. The angular’s documentation is very complete and I advise you to take a look at it.
In this project, I have made the choice that for the app
(standalone) project(s), I use the direct routing/loading. In the other hand, for the main app (root app) the module are lazy loaded and it affects the way the routing works.
To see how how, the lzay loading is dealt with you can take a look at the src/app/lazy
directory where the lazy loaded modules are defined. Then these modules are “really” lazy loaded within the src/app/app-routing.module.ts
file. For each lazy loaded module, a path is defined. This path must preceed all the paths defined in the original module.
Exemple: Suppose that in your orignal module you access the page-one
content via the url localhost:4200/page-one
when you direct load it (like in the app/standalone project). At the same time, the path you have defined to lazy load the same module is my-lazy-loaded-path
. So to access the same content/page, you should use the url localhost:4200/my-lazy-loaded-path/page-one
instead.
And here to make my module work while lazy loaded or direct loaded, a combination of forRoot
method over the loaded module and environment variables is used.
When it comes to manipulating forms, in angular you have the choice between Reactive forms and Template-driven forms.
In the official Angular documentation you can find:
Reactive forms are more robust: they’re more scalable, reusable, and testable. If forms are a key part of your application, or you’re already using reactive patterns for building your application, use reactive forms.
Template-driven forms are useful for adding a simple form to an app, such as an email list signup form. They’re easy to add to an app, but they don’t scale as well as reactive forms. If you have very basic form requirements and logic that can be managed solely in the template, use template-driven forms.
You can find a table of key differences here.
For this project, I have chosen to use Reactive forms for all the advantages it comes with like having a strcutered data model or taking advantage of synchronicity between your template (view/html) and you controller (component class/model). Besides, generally, in big projects you may have complex forms and the reactive forms
makes the task build them easier for you.
When you launch your project you can base it first on an already existing styling library. It helps you save time when styling your application.
Here are some examples of libraries you can use :
Actually, for this project it is bootstrap
which was used (not ng-boostrap
).
Most libraries like React, Angular, etc. are built with a way for components to internally manage their state without any need for an external library or tool. It does well for applications with few components but as the application grows bigger, managing states shared across components becomes a chore.
In an app where data is shared among components, it might be confusing to actually know where a state should live. Ideally, the data in a component should live in just one component. So sharing data among sibling components becomes difficult (source).
The way a state management library works is simple. There is a central store that holds the entire state of the application. Each component can access the stored state without having to send down props from one component to another.
For example, for React one of the most used state management libraries is Redux. And the use of the react-redux package makes it easier. For sure, you have other state management libraries for react
like facebook’s flux. So choose what suits you most knwoing that redux
is more used that flux
because it is not centred on react
and can be used with any other view library.
For angular
you have many options for state management like:
For Angular
, after studying the different options, I find that ngxs
is the best option. It is written for Angular
at the first place so it is implemented following the Angular’s code style and takes andvantage of the Dependency Injection
provided by Angular
. In addition, it is less verbose then other libraries. For these reasons we have made the choice to use it in many companies I worked with. @amcdnl/why-another-state-management-framework-for-angular-b4b4c19ef664">You can find here of a complete explanation of why to use ngxs
.
Used ngxs
plugins for this repo:
The facade pattern is a software-design pattern commonly used in object-oriented programming. Analogous to a facade in architecture, a facade is an object that serves as a front-facing interface masking more complex underlying or structural code. A facade can:
While this seems like a rather trivial change (and an extra layer), the Facade has a huge positive impact of developer productivity and yields significantly less complexity in the view layers (source).
An other advantage is that it makes your controllers (Angular components for example), independant from the state management library you have chosen to use.
For the internationalization you have two options:
1 - Use the Angular’s i18n system
2 - Use ngx-translate library.
I won’t go into details, but the choice for this project and many other production like projects was to use ngx-translate
. The main reasons are that, for the same result, it is simpler to use and develop with and Angular i18n
forces you to build the application per language and it reloads the application on language change.
To get more help on the Angular CLI use ng help
or go check out the Angular CLI README.
If you are using VS Code you may find the following plugins very helpful:
Copyright by @haythem-ouederni. All project sources are released under the Apache License license.