Opinionated Angular Setup

kaisnb

Kai

Posted on January 24, 2021

Opinionated Angular Setup

Introduction

This is my opinionated guide on how to set up a new Angular Project. Below, I described all of the steps that I usually take whenever I set-up a new Angular Project. This was originally meant to be a checklist for myself, but then I decided to write it down in the format of an article. I hope someone can benefit from this. If you are a newbie and just want to learn Angular, you should not care much about this setup. But if you want to set up a serious project and need to make some decision on e.g. testing, formatting, or ChangeDetection then this guide may be useful for you. I additionally tried to add some context and explain my decisions. If you are in a hurry just read the bullet points or jump right to the section you are interested in.

At the time of writing this post, the latest @angular-cli version was 11.0.4. I used this version to generate the project. Since the project structure changes sometimes, the below steps may vary a little bit depending on your CLI version. You can use your favorite editor and follow along with the steps. I used VS-Code with the plugins Angular Language Service, TSLint, Prettier, and SCSS Formatter. They are all optional and just a recommendation.

You can find the final result on my GitHub. Take a look at the commit history to see every single step as a granular commit.

Index

1. Generate a new Angular project

The first step we must take is to generate a new Angular project. This is usually done by using the CLI. If you haven’t done it yet, go to https://nodejs.org/en/ and install the LTS version. After doing this you should have the package manager NPM available. Open your favorite terminal and type npm i -g @angular/cli. This will install the Angular CLI globally so that you can use the ng command everywhere. Run ng --version to check if everything went well. If it was already installed, you can use the same command to update to the newest version. Using the ng tool, we can now generate a new project. Navigate to the folder where you want to create the project. Type ng new my-angular-project to generate a new project called my-angular-project. The CLI will now ask you a few questions. These are my preferred answers:

  • Strict type checking and stricter bundle budgets? -> Yes
  • Routing? -> Yes
  • Stylesheet format? -> SCSS

Everything can be changed later. Strict type checking is always a good idea to prevent any errors that may result from loose typing - like Null-Pointer and many others. It's out of the scope of this post to discuss this topic. Routing is needed in almost every bigger SPA. You have to decide on your own if your SPA needs it or not. The last question is about the format of your Stylesheets. I prefer to write Scss since it is a superset of CSS, which is not the case for Sass. Choosing Scss over Less is just a personal preference. Choose whatever you are the most familiar with.

2. Change Detection Default

The following section is a bit more complicated to understand as a beginner. Basically, there are two Change Detection Strategies in Angular. One is called Default and the other is called OnPush. The default applies if you do not override the Change Detection at the Component level. If you take a look into an already generated Component e.g. app.component.ts, you see that there is no changeDetection property inside the configuration of the Component Decorator.

Default Change Detection is a little bit easier to implement but is less performant. What this means is that every time Angular runs its Change Detection, all bindings are checked. This is costly and can become a bottleneck in more complex applications. What I prefer is always switching to On-Push. It is more performant since a Change Detection Cycle does only check the Bindings of Components that are marked as dirty. A Component will be marked as dirty when (a) an Input of the Component changes, (b) an event is emitted from this Component, (c) you use the async Pipe inside the template or (d) you manually mark it as dirty. There are plenty of good posts about this topic out there. I really recommend you to conduct a quick Google Search and dive deeper into this topic if you are interested.

You can enable the Angular Debug Tools to profile how fast Angular runs a Change Detection Cycle with each of the strategies. You will see that there is a huge difference. The tradeoff is that you may have to write a little bit more code sometimes to run the Change Detection at the right moment. If you structure your code well in reactive style and leverage tools like RxJS and the AsyncPipe then your application will automatically be compatible with the OnPush strategy.

What we do now is set the OnPush strategy as default for the schematics. Every time you generate a new Component using ng g c the Change Detection will be set correctly.

  • Open the angular.json and add a property changeDetection inside projects > my-angular-project > schematics > @schematics/angular:component with the value OnPush
  • Refactor your AppComponent to use the correct strategy.

3. Think of a Component and Directive Prefix

It’s suggested to use a prefix for all your components and directives. You can read more about the reasons inside the Angular coding style guide. There is a section about Component Prefix and Directive Prefix. The default prefix in our newly generated project is app. Angular picks up this prefix when generating new components. To enforce this style there exists a custom tslint rule which comes with the codelyzer package.

  • Open the angular.json and change the property projects > my-angular-project > prefix to your own prefix.
  • Open the ts-lint.json and replace app with your own prefix inside the directive-selector and component-selector rule configuration.
  • Refactor your AppComponent to use the correct prefix. Don’t forget about the index.html.

As TSLint is officially deprecated I assume that this section will change slightly in the future and TSLint will be replaced with ESLint.

4. Setup Prettier

TSLint already does a lot for us but it is not meant to be a full-fledged formatter. For that reason, it’s a good idea to add a formatter to enforce a consistent code style across all developers. Since the line between linting and formatting seems to be a little bit blurry, there are areas where the tools overlap. For example, both tools care about the maximum line length or quotes. This means that we need consistency in our configuration, otherwise the linter will complain if run after the formatter and vice-versa. I decided to use Prettier for formatting since it is widely adopted (12.148.717 weekly downloads on npm at the time of writing this post) and opinionated. Opinionated means that we do not have much to configure, which is great. I like the defaults and do not care much about how the formatting looks. My main goal is consistency. There are many different ways to configure Prettier. My preferred way is to put the configuration inside the package.json.

  • Install the NPM package npm i --save-dev prettier
  • Open the package.json and add a key ”prettier”. Use the following config object: { "printWidth": 140, "singleQuote": true }
  • For convenience add a script in your package.json to apply the formatting: "prettier": "prettier --write \"**/*.{ts,js,css,html,scss}\"",
  • And one to check the formatting: "prettier:check": "prettier --check \"**/*.{ts,js,css,html,scss}\"", which is useful for the CI Pipeline.
  • To run it from your Editor, download the appropriate Editor Integration https://prettier.io/docs/en/editors.html

5. Replace Karma/Jasmine with Jest

Angular comes with a default framework for Unit-Testing. They use Karma as a Test Runner and Jasmine is the JavaScript Testing Framework they use for other things like assertions. Both are good tools that work well. However, I ultimately decided to use Jest. There are tons of good articles out there where you can read about Jest vs Karma. I made my decision because of two key reasons. First, Jest has a much bigger community around it. Since it comes from the React ecosystem, it is widely used and loved by millions. At the time of writing it has about ten million weekly downloads on NPM compared to less than two million for Karma. This is a huge benefit in my opinion especially when you browse the web for solutions to tricky problems. The other reason is its architecture. The architecture is fundamentally different compared to Karma. Jest uses jsdom to simulate the DOM instead of running a Browser like Karma. This improves performance and is in my opinion the right way to go. Jest is built on top of Jasmine, its API is mostly compatible. After following the below steps you can run the app.component.spec.ts test we already have and it will work without any changes to the code.

  • Run npm uninstall jasmine-core jasmine-spec-reporter @types/jasmine karma-chrome-launcher karma-coverage karma-jasmine-html-reporter karma-jasmine karma to remove all Karma and Jasmine dependencies
  • Delete src/test.ts and karam.conf.js
  • Open angular.json and remove the whole projects > my-angular-project > architect > test block.
  • Run npm i jest @types/jest jest-preset-angular --save-dev to add all dependencies we need for Jest
  • Open tsconfig.spec.json and replace the types [jasmine] with [jest, node]
  • Add a file called setup-jest.ts in the project root directory with the following content import 'jest-preset-angular';
  • Add a file called jest.config.js in the project root directory and put the following code inside: module.exports = { preset: 'jest-preset-angular', setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'], };
  • Inside the package.json replace the old test script with "test": "jest", "test:coverage": "jest --coverage",
  • Update the documentation inside the README.md

If you want more details regarding what happened here, check out this excellent Blogpost for more information and useful tips.

6. Replace Protractor with Cypress

Angular’s default solution for End-to-End-Testing is Protractor. It is developed by the Angular Team. I am going to remove Protractor and replace it with Cypress. I suggest you search the web if you are looking for more information about the comparison of Protractor with Cypress. But again there are two main reasons why I prefer Cypress over Protractor. First, it has a big community and is very well maintained. They are pushing new releases and the Changelog is always huge, full of improvements, bug fixes, and new features. I started using it with version 3.3.0 and it has since then constantly getting better and more stable. The second reason is that the architecture is fundamentally different from Protractor. Cypress is executed in the same event loop as the application tested and that allows it to be much more stable and not as flaky as traditional E2E testing frameworks. To set up Cypress and remove Protractor we will use an awesome Schematics made available by Briebug.

  • Run ng add @briebug/cypress-schematic --addCypressTestScripts and choose yes to remove Protractor.
  • Add /cypress/videos and /cypress/screenshots to your .gitignore
  • Update the documentation inside the README.md and
  • Run npm run e2e to verify that everything's working correctly

7. Add PWA Features

In Angular, there is this really nice Schematics to turn your application into a PWA with just one command. It creates a manifest.webmanifest file, adds the necessary configuration to use Service Workers, and adds some dummy icons. Almost every application can benefit from Service Worker support. Your application does load faster now and you can use features like A2HS and show a Splash Screen.

  • Run ng add @angular/pwa

8. Add Angular Material (Optional)

In the next step, I want to add a component library. This step is totally optional, but in almost any project you choose some sort of UI library to speed up the development of your application. There are a lot of very good libraries out there like NG-ZORRO, PRIMENG, or ng-bootstrap. A more comprehensive list can be found in the official Angular Docs. Whenever possible I choose Angular Material Components. It's a set of components for Angular that follows Google's Material Design specification. The assortment of components is not the largest but they are all really high quality. I have personally used them a lot and have never experienced any issues. The following steps will be specific to this library. See the installation guide.

  • Run ng add @angular/material in the root of your project.
  • Q: Choosing a Theme -> Custom
  • Q: Setup global typography? -> Yes
  • Q: Setup browser animations? -> Yes

Of course, you can answer the question depending on your needs. I only wanted to give you the options I typically pick.

PERF TIP: Below I want to show two optimizations I typically apply to improve the application performance a bit. They may be considered to be "premature optimizations" so avoid them if you do not know that you need them.

  • Instead of including the whole theme @include angular-material-theme($theme);, we can customize and only include the CSS for components that we want to use e.g.
@include mat-core-theme($theme);
@include mat-button-theme($theme);
@include mat-icon-theme($theme);
Enter fullscreen mode Exit fullscreen mode
  • Instead of loading the whole icon font, I prefer to manually include the SVG's of Icons that I use and embed them in the code. Remove the icon font include from the index.html and register the SVG's in your AppModule. Take a look at the MatIconRegistry docs for more details.
export class AppModule {
  constructor(iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) {
    iconRegistry.addSvgIconSetLiteralInNamespace(
      "mdi",
      sanitizer.bypassSecurityTrustHtml(`
        <svg xmlns="http://www.w3.org/2000/svg">
            <def>
                <svg id="close" width="24" height="24" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
                <svg id="menu" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>
            </def>
        </svg>`)
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

9. Use a System Font Stack (Optional)

If the design of your UI allows it, I prefer to use a System Font Stack. The main reason why I do this is that I avoid loading a font file and save a lot of bytes. Below, I will use the System Font Stack which is used by GitHub.com. The following steps are specific to Angular Material. If you have chosen a different component library there may be other ways to do it.

Define your custom typography and pass it as an argument to the mat-core include. That's it.

$custom-typography: mat-typography-config(
  $font-family: "-apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji"
);

@include mat-core($custom-typography);
Enter fullscreen mode Exit fullscreen mode

In the Angular Material docs, you can find more about typography customization.

10. More Ideas

These were the key steps I usually take when setting up a serious Angular project. Of course, you can also code right away and do any of these steps at a later point in time. Before I leave you alone to start coding, I want to give you a quick list of a few more things that came to my mind. These were topics I first wanted to include but then left out since I think they make less sense to set up right at the beginning of a project.

Thanks for Reading

I hope this post was at least a little bit useful for you. Please feel free to reach out with any questions, comments, or feedback.

Kai Schönberger, 2021/01/24

💖 💪 🙅 🚩
kaisnb
Kai

Posted on January 24, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related