How to start a project on Angular the right way

Sergey Gultyayev
8 min readJan 9, 2022

How do you start a new project? ng new app and that’s it! Not anymore. If you want to make maintenance and development easier you should be sure to follow these steps.

Photo by Tim van der Kuip on Unsplash

Create the project

This one is straightforward. We need to create our app (where else would we be working, huh) ng new app .

Install Husky

We will need Husky for configuring our git hooks. We need them because they allow us to make automatic code modifications, linting, run tests etc. This doesn’t remove the necessity to have linting and tests in the CI/CD pipelines.

First, we run npm install husky -D to have it as our development dependency.

Now we need to run a few more scripts to set up husky.
npm set-script prepare "husky install" will add a “prepare” script which will set up our hooks.
Also, we need to invoke it first time ourselves npm run prepare as it won’t be called until the next npm install call.

Now we are ready to add hooks files. To do so we need to run

npx husky add .husky/pre-commit ""
npx husky add .husky/pre-push ""

It will create two files in .husky folder for two git hooks.

Configure pre-push hook

This hook should contain some checks that are meant to be run less often due to their time of execution. This is usually a place for the tests.

Tests are a great way to ensure that at least separate units by themselves are working and even if you don’t follow TDD they can come in handy when you need to develop a feature when the back-end isn’t ready yet, but you have the contract. You just write a lot of tests and simulate the real data flow and hence are not blocked to build business logic while the back-end endpoint is still in progress.

# pre-push file
...
npm test -- --watch false

Note the -- before the --watch flag. This is not an error. When you want to pass arguments to a script ran by npm run <script_name> you have to use -- so that the arguments are passed to the script body. Otherwise, the called script won’t receive the arguments.

Now, each time we push code to the repository it will run all our tests.

Add ESLint to the project

As TSLint is deprecated and Angular isn’t shipped with any of the linters anymore we should add them ourselves. You might wonder why. The answer is simple. The fewer choices developer has to make the faster the work goes and the more readable the codebase is because it’s consistent.

By introducing ESLint we ensure that the codebase follows a specific set of rules. The rules can be and should be adjusted with the agreement of the team that will be working on the project.

Furthermore, this way we don’t need to have a coding style guide in our documentation which no one likes to read. Every choice is automatically enforced by the tooling and won’t be overlooked in the code reviews.

We will use angular-eslint which allows us effortlessly add the linter to our project and it contains lots of the old TSLint rules that used to be default for Angular, so experienced Angular developers will know most of the rules in advance.

Just run ng add @angular-eslint/schematics . That’s it. The linting tool and its rules are installed. Now you can run ng lint to ensure it works.

There are some useful ESLint plugins that I recommend adding to the project as well:

  • RxJS rules — it’s a great plugin that helps you find anti-patterns such as nested subscriptions and also helps notice when you forgot to subscribe
  • Jasmine — a helpful plugin that targets unit tests

Let’s also add a few more rules for the HTML ESlint configuration

{
"files": ["*.html"],
"extends": ["plugin:@angular-eslint/template/recommended", "prettier"],
"rules": {
"@angular-eslint/template/no-duplicate-attributes": ["error"],
"@angular-eslint/template/accessibility-alt-text": ["error"],
"@angular-eslint/template/accessibility-elements-content": ["error"],
"@angular-eslint/template/accessibility-valid-aria": ["error"],
"@angular-eslint/template/banana-in-box": ["error"],
"@angular-eslint/template/eqeqeq": [
"error",
{
"allowNullOrUndefined": true
}
],
"@angular-eslint/template/no-any": ["error"]
}
}

The description of each rule you can find in the repo.

Add lint-staged

This tool will allow us to run scripts only over the changed files to spare time for checking files that haven’t been changed. npx mrm@2 lint-staged this will install the tool and add a configuration in the package.json .

We need to find the configuration section in package.json and adjust it to our needs. Change the extension to the .ts . The configuration should look like this

"lint-staged": {
"*.ts": "eslint --cache --fix"
}

And add “.eslintcache” to the .gitignore file so we don’t have the cache in the repo.

Also, when the script was running it has added npx lint-staged to the pre-commit file. That’s what we need. To fix our files according to ESLint rules on each commit.

The way it works is that you commit changes and if an issue is auto-fixable it will be fixed and the commit will complete, otherwise, the commit will be aborted and you will find in the console what caused the error and where.

This kind of message can be found in the log of the aborted commit. From it, we can see that the problem is that we defined the ngOnInit method, but we didn’t implement the interface. Also, we know that it’s the app.component.ts on line 11 column 3. Handy, right?

Also, you might want to enable the ESLint for your IDE of choice to see the errors in advance.

Add Prettier

Prettier will format our files for us, so we always have the same indentation, quotes type etc. across the project.

Run npm i prettier and modify the lint-staged config to run prettier over the changed files.

"lint-staged": {
"*.ts": "eslint --cache --fix",
"*": "prettier --write --ignore-unknown"
}

Run npx prettier --write src/**/* so to format all files according to the Prettier rules.

Now, we need to adjust our ESLint rules so it doesn’t conflict with Prettier. To do so we install the package npm i -D eslint-config-prettier and change the .eslintrc.json . In the file, we need to find all “extends” sections and add “prettier” to the end of each. You must keep it at the bottom.

Example of changed .eslintrc.json file with “prettier” used

Add Stylelint

This is similar to ESLint, however, it’s for the *CSS files.

Depending on whether you use a CSS preprocessor and which you might need to install a different set of rules for the stylelint which you can find here.

I will proceed with the default CSS approach and install the packages
npm install --D stylelint stylelint-config-standard and create the configuration file .stylelintrc.json with the following configuration

{
"extends": "stylelint-config-standard"
}

Also, we will need to install a config that will disable rules that conflict with Prettier npm i -D stylelint-config-prettier and update the .stylelintrc.json with the following config

{
"extends": ["stylelint-config-standard", "stylelint-config-prettier"]
}

The same rules apply here as with the ESLint — “stylelint-config-prettier" should be at the end of the list.

Now, let’s check if it’s working. I changed the app.component.css file to contain the rule background: url(/test/test.img); which breaks the Stylelint rules as all URLs by default are supposed to be wrapped with the quotes. When doing the commit I see the error

Stylelint error example

Great, it’s working as expected!

Now, let’s .stylelintcache to the .gitignore so we don’t commit stylelint cache to the repo.

Add Storybook

Storybook is a great tool for developing, testing and showcasing components without the overhead of going through the in-app authentications, API requests and not working back-end servers.

Run npx sb init , this will add the Storybook to your project in an automated mode. After this, you will see a few added scripts in the package.json file. Run npm run storybook , this will start a dev Storybook server and open the page in the browser.

There is a chance that it won’t start for you because of compodoc. You can simply remove it from the storybook build process by removing the npm run docs:json part from storybook scripts and commenting out the lines in the .storybook/preview.js

import { setCompodocJson } from "@storybook/addon-docs/angular";
import docJson from "../documentation.json";
setCompodocJson(docJson);

Currently, it seems that there is an issue with Angular 13.1 and Storybook 6.4 which I’ve just encountered while writing this article.

Add a local server

A local server is a great tool that you will need to use as an API stub for the times when your back-end is down or its vendors it depends on.

We won’t be doing anything special here. A simple express server where we will add route stubs with responses that will be suitable for most of the cases we would want to test locally. In case we want to test different flows often we could add authentication to the mock server and use it to return different responses based on the authenticated user.

For this, we create a new folder server , run npm init in it and add the express server npm install express .

Create an app.js and paste the following skeleton there

const express = require("express");
const app = express();
const port = 3000;
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(port, () => {
console.log(`Stub server is listening at http://localhost:${port}`);
});

Modify package.json in the server folder to have this script

"scripts": {
"start": "node app.js"
},

You will be using it to start the local server.

Now, you are rarely blocked by the back-end to develop the UI and test some simple scenarios. Cool, right?

Configure CI/CD pipelines and git checks

We have all checks running on local machines during git hooks. That’s great, however, in case someone disables hooks on their machines or makes a partial commit the repo snapshot may not be in the required conditions.

Therefore, you must ensure that your CI/CD pipelines and required checks that run on merge requests contain the following checks:

  • App prod build (it’s important to do a prod build exactly because some errors only appear during optimizations which are applied during the prod builds)
  • Run unit tests
  • Run linters
  • Run Prettier in check only mode

This way no one will be able to bypass the checks. Later on, if you have too many tests you might want to remove them from the pre-push hook to save time during push and rely on the same step during the checks on the merge request.

VSCode extensions

There are some very useful extensions I consider to be a must for any project. To help the fellow developers be sure they don’t miss them, we will update the .vscode/extensions.json file which then will show that this repository has extensions recommendations.

Replace the file content with the following

{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": [
"angular.ng-template",
"cyrilletuzi.angular-schematics",
"streetsidesoftware.code-spell-checker",
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"ms-vsliveshare.vsliveshare",
"yzhang.markdown-all-in-one",
"quicktype.quicktype",
"esbenp.prettier-vscode",
"mrmlnc.vscode-scss"
]
}

At last, we have finished with the repo initialization. It took some time, but it’s incomparable to the amount of time it will save you and your team.

In the repo, you can find the final result of all the manipulations.

I have published a package that does most of the setup for you.

--

--

Sergey Gultyayev

A front-end developer who uses Angular as a main framework and loves it