DEV Community

Cover image for Be proactive when you join an Angular project
Tim Deschryver for This is Angular

Posted on • Originally published at timdeschryver.dev

Be proactive when you join an Angular project

Follow me on Twitter at @tim_deschryver | Subscribe to the Newsletter | Originally published on timdeschryver.dev.


Read the TLDR version on timdeschryver.dev

As developers, we're always using analogies to explain our work to non-developers.
We do this to be to give an idea of what it's like without using too much of our technical jargon.
In this intro, I'm doing the same.

Working on a project is a lot like gardening.
You start to plant seeds and watch them slowly grow into a beautiful, colorful, garden, and a home to some animals.
When you start out and everything is new, you give your new garden a lot of attention, but then you realize that it requires a consistent commitment to properly maintain the garden. If you want to do it right, you have to give it the required time, otherwise it transforms into a wilderness with dead flowers.
Because the garden keeps on growing it becomes harder and harder to give each area the desired time. Sadly, you also need to attain other chores that you don't like as much as maintaining your garden. This is all taking away valuable time the garden needs.
Eventually, the garden slowly loses its beauty and so does your motivation.

Starting a new project is a lot like the above-described garden.
The project starts off with good intentions, but eventually some trade-offs need to be made along the way due to several factors. Doing your best with the current information, deadlines that had to be reached, members that leave the team, evolving technology, and the lack of experience are all reasons that decide the quality of the codebase. When it becomes worse it also has an impact on the future development, at its worst-case, it also lowers the team mood and team morale. Making a new change requires more energy than it used to be.

When you join a team it usually takes a while to be brought up-to-speed and to start being or feeling productive.
When you hear your team members complain about the current state of the codebase, that it got out of control, that it could need a polishment, or when you notice it yourself, there's a chance for you to make a positive impact by being proactive.

Giving a codebase the required, but the long over-due focus is a win-win situation for you and the team.
You get an introduction to the codebase, and the whole team wins because you improve the state of the application.
Because, let's be honest, no one else wants to do it at this point in time.
It's also a perfect opportunity to get to know your team members because what you're about to do is going to result in questions on both sides.

Let's take a look at what you can do now to clean up the codebase by bringing it up to date.
Let's start gardening!

Tune up the strictness

TypeScript

This is probably the most impactful item on your to-do list, but also the most time-consuming.
With strictness enabled it results that silly mistakes being caught at compile time, and thus you create a safer environment.
For example, it can detect an unsafe operation on an object that could possibly be null.

To enable "strict mode" in your application, open the tsconfig.json file and set the compilerOptions.strict property to true.
strict is a superset containing multiple strict options. Besides the strict flag, you can also enable more properties that result in a safer environment, for example, noImplicitOverride, noPropertyAccessFromIndexSignature, noImplicitReturns, and noFallthroughCasesInSwitch, which are enabled by default in a new Angular 13 project.

Angular templates

Besides the TypeScript strict mode, Angular also provides a strictTemplates option.
The option can be compared to the strict option, but for the HTML templates.
For example, it warns you when you pass a method the wrong type, e.g. a string instead of a number.

The strictTemplates option can be enabled by setting the angularCompilerOptions.strictTemplates to true in the tsconfig.json file.

Enable strict mode

The first time when you enable one or both strict options you are probably going to see some errors when you try to run )and build) the application.
These need to be addressed first, before the application can run again.

/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2017",
    "module": "es2020",
    "lib": [
      "es2020",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "strictTemplates": true
  }
}
Enter fullscreen mode Exit fullscreen mode

See the TypeScript docs and the Angular docs for more info.

Betterer

Refactoring all the errors at once is the best scenario, but don't worry when there are too many of them.
Luckily, Betterer provides a solution to incrementally improve the state of the codebase.

By using Betterer, you're not forced to fix all the errors in one go, but the development process can continue.
This means that you don't have to "waste" time before you can run the application.
With Betterer you can take your time to take care of the errors one by one, without the addition of new errors, and this can be a team effort.

To add Betterer run the init command:

# Install Betterer and create a blank test
npx @betterer/cli init
# Install the plugins we need
npm i --save-dev @betterer/typescript @betterer/angular
Enter fullscreen mode Exit fullscreen mode

Then, you can remove the strict option from the tsconfig.json file (the one we added before) and move them to a Betterer test inside the .betterer.ts file.

import { typescript } from '@betterer/typescript';

export default {
  'stricter compilation': () =>
    typescript('./tsconfig.json', {
      strict: true,
    }).include('./src/**/*.ts'),
};
Enter fullscreen mode Exit fullscreen mode

Before you run the Betterer command, also add the --strict flag to the added betterer script in the package.json file, making it harder to cheat with the test results.

{
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "betterer": "betterer --strict"
  },
  "dependencies": {},
  "devDependencies": {
    "@betterer/cli": "^5.1.6",
    "@betterer/typescript": "^5.1.6",
    "@betterer/angukar": "^5.1.6"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, you're ready to run Betterer for the first time, which gives you the following result.

npm run betterer

   \ | /     _         _   _
 '-.ooo.-'  | |__  ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ \/ _ \ __| __/ _ \ '__/ _ \ '__|
 .-'ooo'-.  | |_)|  __/ |_| ||  __/ | |  __/ |
   / | \    |_.__/\___|\__|\__\___|_|  \___|_|

πŸŽ‰ Betterer (4.743ms): 1 test done!
βœ… stricter compilation: "stricter compilation" got checked for the first time! (291 issues) πŸŽ‰

1 test got checked. πŸ€”
1 test got checked for the first time! πŸŽ‰
Enter fullscreen mode Exit fullscreen mode

As you can see, the Betterer command checks for violations that are configured in the test file.
In this case, with the TypeScript strictness enabled.
What you can't see, is that it stores the results in a separate .betterer.results file.

The next time that the command is run, Betterer compares the two results and throws an error when the result has gotten worse.

πŸŽ‰ Betterer (3.809ms): 1 test done!
βœ… stricter compilation: "stricter compilation" got better! (0 fixed issues, 291 remaining) 😍

・ New issue in "/work/project/src/state.ts"
・ > 2 | import {  BehaviorSubject, throwError } from 'rxjs';
・     |                            ^^^^^^^^^^ 'throwError' is declared but its value is never read.

1 test got checked. πŸ€”
1 test got worse. πŸ˜”
Enter fullscreen mode Exit fullscreen mode

Great! You can now detect new violations and prevent them from being committed (more on this later).

When you've positively made improvements, Betterer lets you commit the changes, and it updates its results file.

πŸŽ‰ Betterer (6.809ms): 2 tests done!
βœ… stricter compilation: "stricter compilation" got better! (49 fixed issues, 242 remaining) 😍

1 test got checked. πŸ€”
1 test got better! 😍
Enter fullscreen mode Exit fullscreen mode

Multiple tests can be added to the .betterer.ts file, for example, we can also include a test for the Angular strict templates option.

import { typescript } from '@betterer/typescript';
import { angular } from '@betterer/angular';

export default {
    'stricter compilation': () =>
        typescript('./tsconfig.json', {
          strict: true,
        }).include('./src/**/*.ts'),

    'stricter template compilation': () =>
        angular('./tsconfig.json', {
            strictTemplates: true
        }).include('./src/*.ts', './src/*.html'
};
Enter fullscreen mode Exit fullscreen mode

Update Angular

Another item on your to-do list is to verify that the project is running on the latest version of Angular.
If you notice that this isn't the case you can try to update Angular.
Most of the time, it only takes a few minutes, up to an hour, for it to complete.
When you notice that this isn't the case, you can abort the upgrade progress and document what went well and what didn't, this is valuable information to schedule the upgrade. Also, if you notice that the latest version of Angular is just released a few weeks before, ask your colleagues if it's OK to upgrade Angular because there might be a policy that restricts this.

Updating Angular's dependencies isn't difficult, and there's an official Angular Update Guide that lays down the details and gives a step-by-step update path.

To check if a dependency can be updated, run the ng update command.
If the project isn't using the latest version, you'll see an output like the one below.

npx ng update

The installed local Angular CLI version is older than the latest stable version.
Installing a temporary version to perform the update.
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Using package manager: 'npm'
      @angular/cdk                            11.2.13 -> 12.2.9        ng update @angular/cdk@12
      @angular/cli                            11.2.11 -> 12.2.9        ng update @angular/cli@12
      @angular/core                           11.2.12 -> 12.2.9        ng update @angular/core@12
      @ngrx/store                             11.1.1 -> 13.0.2         ng update @ngrx/store

    There might be additional packages that don't provide 'ng update' capabilities that are outdated.
    You can update the additional packages by running the update command of your package manager.
Enter fullscreen mode Exit fullscreen mode

Next, pass the desired libraries as an input argument to the ng update command and let the Angular CLI do its magic.

npx ng update @angular/cli@12 @angular/cdk@12 @ngrx/store
Enter fullscreen mode Exit fullscreen mode

Read about how you can include your own libraries to the update command in ng update: the setup

ESLint

In the early years of Angular relied on TSLint to statically analyze your code to quickly find problems (also known as a linter) an Angular project.
In 2019-2020, TSLint became deprecated and was ported over to ESLint as typescript-eslint.

Because TSLint was included with the creation of a new Angular project, a lot of older Angular projects still depend on TSLint.
This gives us another item on our to-do list, the migration from TSLint to ESLint.

For Angular projects, there's the angular-eslint ESLint plugin, which is the ESLint equivalent of codelyzer.

Luckily, the angular-eslint team has put a lot of effort into an automatic migration to offer us a smooth transition going from TSLint to ESLint. To upgrade your project to ESLint run the following commands.

npx ng add @angular-eslint/schematics
npx ng generate @angular-eslint/schematics:convert-tslint-to-eslint
Enter fullscreen mode Exit fullscreen mode

The script ports over the TSLint rules to ESLint rules and tries to find the ESLint equivalents to your installed TSLint plugins.
While you're installing and configuring ESLint, I recommend you to also add the RxJS ESLint plugin and if your project is using NgRx, there's also the NgRx ESLint Plugin.

Besides being useful (it can detect common mistakes), linters also includes fixers for some deprecations and best practices.

For a simple project, this results in the following ESLint config.

{
  "root": true,
  "ignorePatterns": [
    "projects/**/*"
  ],
  "overrides": [
    {
      "files": [
        "*.ts"
      ],
      "parserOptions": {
        "project": [
          "tsconfig.json"
        ],
        "createDefaultProgram": true
      },
      "extends": [
        "plugin:@angular-eslint/recommended",
        "plugin:@angular-eslint/template/process-inline-templates"
      ],
      "rules": {
        "@angular-eslint/directive-selector": [
          "error",
          {
            "type": "attribute",
            "prefix": "app",
            "style": "camelCase"
          }
        ],
        "@angular-eslint/component-selector": [
          "error",
          {
            "type": "element",
            "prefix": "app",
            "style": "kebab-case"
          }
        ]
      }
    },
    {
      "files": ["*.ts"],
      "parser": "@typescript-eslint/parser",
      "parserOptions": {
        "ecmaVersion": 2019,
        "project": "./tsconfig.json",
        "sourceType": "module"
      },
      "extends": ["plugin:rxjs/recommended"]
    },
    {
      "files": ["*.ts"],
      "extends": ["plugin:ngrx/recommended"]
    },
    {
      "files": [
        "*.html"
      ],
      "extends": [
        "plugin:@angular-eslint/template/recommended"
      ],
      "rules": {}
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Before pushing these changes, let's also run ESLint against your whole codebase and let it fix violations automatically by using the --fix flag.

npx eslint . --fix
Enter fullscreen mode Exit fullscreen mode

This already solves a lot of issues, but for other issues you need to manually rewrite the code that causes the violation.
To see all the ESLint errors and warnings, run the following command.

npx eslint .
Enter fullscreen mode Exit fullscreen mode

Here again, you can resort to Betterer if there are too many errors to fix at once by using the built-in Betterer ESLint Test.

Prettier

Because everyone has a unique writing (and format) style, it sometimes makes it harder to review a change.
By enforcing a team style you make sure that a change is isolated to just the task and nothing more.
This practice makes it easier to review changes.

To enforce the writing style you can use Prettier, an opinionated code formatter.

To add prettier to your project, run the next command.

npm i --save-dev prettier
Enter fullscreen mode Exit fullscreen mode

Then, create a prettier.config.js config file and configure the options to your desires, for example:

module.exports = {
  tabWidth: 2,
  useTabs: false,
  semi: true,
  singleQuote: true,
  trailingComma: 'all',
};
Enter fullscreen mode Exit fullscreen mode

I also encourage you to immediately run prettier on the whole project.
Otherwise, a small change to a file may result in a lot of formatting changes in that same file, making it harder to review the isolated change.

To format your whole codebase at once, run the following command.

npx prettier . --write
Enter fullscreen mode Exit fullscreen mode

Unifying libraries

There might be some inconsistencies on a project where different developers have been working on.
For example, different libraries that are used to do the same thing, e.g. icon libraries, utility libraries,...

For new developers that join the team, this makes it harder to follow the best practices and to keep the design consistent.
It's up to you to detect these libraries and unify them into one library.
Better, you can also document how something is best done in your project.

An additional benefit is that the bundle size shrinks.

Writing Tests

On a project that doesn't have tests, there might be a fear of regression to touch existing parts of the application.
To give you a layer of security, I find that end-to-end tests offer a lot of value.
It also gives you a chance to go through the application and get acquainted with the domain.

A simple happy-path test to a critical part of the application is good enough to start with.
While this provides direct value, it also acts as a good foundation that can be built upon.

To write the end-to-end test, I'm currently using Playwright.
One of the key essentials is that it has a test generator command where you can just click through your application, and the generator writes the test case for you. It can just be that simple.

In future blog posts, I probably go into more details about why I like and use Playwright and how to set it up in an Angular project.

Git hooks

The above-mentioned tools and rules are a great way to improve and maintain the state of the project, but it isn't a one-time thing, and we're also lacking a way to enforce them to the whole team and future developers.

Simply mentioning and documenting what you've done, and asking the team to pay attention to keep the application in a better shape isn't good enough. While the thought of it makes the team happy, in practice, these good intentions tend to evaporate quickly.

To force the team to follow these rules, you need to introduce git hooks.
A hook is executed before (pre) or after (post) running a git command.

Usually you write a hook that runs before the git command is executed.
Two popular hooks are the pre-commit and pre-push hook to prevent "falsy" code is committed or pushed to a branch.

In the example below a pre-commit file is created in the .githooks folder, in which you implement the pre-commit hook.
The implementation of the hook can run your npm scripts, in this case we want to run Betterer with the precommit option, and we want to run lint-staged.

#!/bin/sh

npx betterer precommit
npx lint-staged

# instead of adding the commands in this file,
# you can also add a script to the package.json scripts to run the desired checks
# npm run pre-commit
Enter fullscreen mode Exit fullscreen mode

To register the git hook, add the prepare lifecycle hook to the scripts of the package.json file. When a team member runs the npm install command, the prepare hook is executed and the git hook is registered.

{
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "prepare": "git config core.hookspath .githooks"
  },
  "dependencies": {},
  "devDependencies": {
    "lint-staged": "^12.3.3",
  }
}
Enter fullscreen mode Exit fullscreen mode

The lint-staged is a library (yes, another one) that helps us to keep the application in better shape.
It makes it easy to run commands to staged git files before these are committed.
In other words, a touched filed is automatically formatted (important if someone's IDE doesn't run prettier) and is checked against ESLint and strictness violations. This makes sure that code is always formatted and prevents that there are no violations against strict compilers and ESLint rules.
You could also always run the commands to the whole project, but this could take up some time, using lint-staged makes sure that almost no time is lost.

To install lint-staged, run the following command.

npm install --save-dev lint-staged
Enter fullscreen mode Exit fullscreen mode

To configure lint-staged, create a lint-staged.config.js file and add the prettier and eslint commands.

module.exports = {
  '*.{ts,js}': ['prettier --write', 'eslint --fix'],
  '*.html': ['prettier --write', 'eslint'],
  '*.{json,md,css}': ['prettier --write'],
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

Joining a new team is always exciting and you never know in which shape you'll encounter the application.
To start things off on a positive note there are probably some chores that you can pick up that no one else feels like doing.

By listening to the complaints and by taking a closer look at the codebase, I'm sure that you can find things that need to be improved. Sadly, no codebase is perfect.
In these cases, I say "be proactive" and get to work.

This benefits you because you can get to know the application and your team members better while immediately making a good impact.
It also benefits the whole team because a codebase that is well maintained leads to a better mood and motivates the team to continually improve. Because the morale is better, productivity also increases, making the managers happy.

Enjoy and take care of your clean workspace!


Follow me on Twitter at @tim_deschryver | Subscribe to the Newsletter | Originally published on timdeschryver.dev.

Top comments (0)