·
10 min read

How I Set Up A New Angular Project

How I Set Up A New Angular Project Image

I think Angular is the best choice for large enterprise applications. The basic project setup, which is generated using the Angular CLI is good, but I prefer another way to set up a new project. In this article, I want to talk about these topics:

  • Using Nx instead of the Angular CLI
  • TypeScript configuration
  • Internationalization
  • UI Component Explorer
  • Domain-driven Design for your models
  • Error Handling
  • Build Complex Components
  • Miscellaneous

Nx

Nx is not a replacement for the Angular CLI but uses the Angular CLI's power and enhances it with additional tools. Anything you can do with the Angular CLI can also be done with Nx, and you configure your project (as usual) with the angular.json configuration file.

I love Nx due to these facts:

  • I can easily integrate modern tools like Cypress, Jest and Prettier to my Angular project
  • I can use effective development practices which are pioneered at Google, Facebook, and Microsoft

Nx is an easy to use version of the powerful monorepo tools used at companies like Google.

Let us first talk about the usage of Cypress and Jest in Angular projects.

Why should I consider using Cypress instead of Protractor?

Check out this nice comparison to get more information about the differences between the two technologies.

Cypress is modern and interesting because it is not based on Selenium. Whereas Selenium executes remote commands through the network, Cypress runs in the same run-loop as your application. Additionally, it is fast and has nice features like:

  • Time travel
  • Debuggability
  • Real-time reloads
  • Automatic waiting
  • Spies, stubs and clocks
  • Network traffic control
  • Consistent results
  • Screenshots and videos

You can find further details about these features on the official feature website.

The most significant disadvantage of Cypress is, in my opinion, that it does not have full integration with tools like SauceLabs and BrowserStack and does not support other browsers than Chrome. This probably might change in the future, but these features are not available at the time of writing.

In my opinion, Cypress is not a perfect choice for every Angular project but I would recommend that you should give it a try and make your own decision.

Why should I consider using Jest instead of Karma/jasmine?

In my experience, the testing experience using Karma + jasmine is worse when the projects become bigger:

  • Slow build times (especially initially)
  • Recompiling does not work reliably
  • HTML reporter like karma-jasmine-html-reporter tend to be buggy

Jest was created by Facebook and is faster than other test runners because it is parallelizing tests. Additionally, it provides a CLI and has less configuration effort than other testing frameworks.

Some of the advantages of Jest compared to Karma + jasmine:

  • Tests run faster as it can execute tests without building the whole app
  • Using the CLI it is possible to filter by a filename or regex, which reduces the need for fdescribe
  • Nearly no configuration needed to get started
  • Stable tests
  • The syntax is similar to jasmine
  • Provides snapshot testing
  • More active community

I haven't used Jest in any of my Angular projects yet, but I will try it in one of my following Angular projects. The main reason why I haven't used it yet is that I worked on existing codebases with many jasmine tests, and there was no need/time/budget to migrate them to Jest. But I already used Jest in a Vue.js project and liked it.

If you are just annoyed with the verbose code produced by using Angular's TestBed API I would suggest trying Spectator, which allows us to write "readable, sleek and streamlined unit tests".

A summary of my testing suggestions:

  • Consider using Spectator instead of the TestBed API of Angular.
  • Consider using Jest instead of Karma/Jasmine (Migration is relatively easy)
  • Consider using ng-mocks to mock your component, directives, services, pipes, and more. Your unit tests should be pure and therefore isolated.
  • Consider using a functional component testing approach over the technical class testing approach. Test your component from the DOM and not the class. It will help if you think of user events instead of methods.

Effective Development Practices

Using Nx you can work in a "monorepo" way of building your application. This approach is used by large software companies like Google, Facebook, Twitter, and more to make it easier to work with multiple applications and libraries. These are some of the advantages of a monorepo approach:

  • You commit a working piece of software which may include multiple parts like frontend and backend
  • One toolchain setup
  • Dependency management is easier, e.g. all applications & libs in a Nx workspace share one package.json and can thus use the same Angular version
  • Code can be split into composable modules
  • Consistent developer experience

What I also like is the possibility to create applications and libraries in Nx, which provide an excellent way to structure larger applications:

  • An application is anything that can run in the browser or on the server. It's similar to a binary.
  • A library is a piece of code with a well-defined public API. A library can be imported into another library or application. You cannot run a library.

For example, we could define a TypeScript library that shares our TypeScript interfaces between our TS-based applications in our workspace. Of course, our workspace can contain applications that rely on different frontend (or backend) frameworks like React, Angular, NestJS, and even more.

One of my favorite features is the dependency graph which can show me a graphical representation of my workspace by running nx affected:dep-graph:

Angular Nx Dependency Graph

As we used affected, we can see what parts of our workspace are affected by our current changes (highlighted in red). This way, we can also run only tests or recompile code that was effected by our changes:

bash
nx affected:apps # prints the apps affected by a PR nx affected:build # reruns build for all the projects affected by a PR nx affected:test # reruns unit tests for all the projects affected by a PR nx affected:e2e # reruns e2e tests for all the projects affected by a PR nx affected --target=lint # reruns any target (for instance lint) for projects affected by a PR

See the official documentation to learn how to use these mechanics in Nx.

TypeScript Configuration

I prefer to start with this tslint configuration as it uses the tslint configuration of Angular CLI and aligns with the Angular style guide.

In my tsconfig.json file I enable strictNullChecks which makes the code base more robust against possible null or undefined errors during runtime.

json
{ "compilerOptions": { "strictNullChecks": true } }

From the official documentation:

In strict null checking mode, the null and undefined values are not in the domain of every type and are only assignable to themselves and any (the one exception being that undefined is also assignable to void).

Internationalization (i18n)

I configure internationalization from the beginning of a project even if the product is only planned for one country. It has two reasons:

  • You get used to storing your translated texts in one file and not as hardcoded strings across the whole application.
  • If the application needs to get translated into another language you are prepared for it.

I always use ngx-translate in my Angular projects, especially as it allows you to switch between languages during your application's runtime. This can be handy if you implement a language switcher in your app.

UI Component Explorer

If you develop your components, creating a custom view with all available components can be helpful, or using existing solutions like StoryBook.

In some projects, I created a separate page in the application (which was only visible to certain people) that showed a list of all available components. This page was used in manual testing sessions and provided a quick way to see if a new feature impacted any existing component. Additionally, it was possible to test the components in isolation.

Use Domain-driven Design for your models

One of the main ideas behind Domain-Driven Design is the separation of business logic (domain) from the rest of the application or implementation details. This can be easily implemented in Angular using TypeScript.

The goal of our domain model is to represent business logic. We want to avoid that certain business logic is split across multiple components and services but is available at a certain place. This way, we can easily react and change the logic if something in the business requirement has changed.

An example of such a domain model could look like this:

export class User { private firstName: string private lastName: string private age: number get firstName() { return this.firstName } get lastName() { return this.lastName } get fullName() { return `${this.firstName} ${this.lastName}` } get age() { return this.age } constructor(firstName: string, lastName: string, age: number) { this.setName(firstName, lastName) this.setAge(age) } setName(firstName: string, lastName: string) { if (this.validName(firstName) && this.validName(lastName)) { this.firstName = firstName this.lastName = lastName } } setAge(age: number) { if (age >= 18) { this.age = age } else { throw new Error('User age must be greater than 18') } } private validName(name: string) { if (name.length > 0 && /^[a-zA-Z]+$/.test(name)) { return true } else { throw new Error('Invalid name format') } } }

If, for example, the minimum age should be changed from 18 to 16 this logic needs only to be changed in this domain model class.

This article provides further details and a good approach to handling server-side business logic in your frontend application.

Error Handling

I would always add a LoggerService and global error handler at the beginning of the project.

Additionally, try to use an error tracking software like Sentry to be able to monitor and fix crashes in real-time.

Example for a LoggerService:

import { Injectable } from '@angular/core' @Injectable() export class LoggerService { debug(message: string, ...optionalParams: unknown[]): void { console.debug(message, ...optionalParams) } log(message: string, ...optionalParams: unknown[]): void { console.log(message, ...optionalParams) } warn(message: string, ...optionalParams: unknown[]): void { console.warn(message, ...optionalParams) } error(message: string, ...optionalParams: unknown[]): void { // Send error to Sentry Sentry.captureMessage(`Error message: ${message}, optionalParams: ${JSON.stringify(optionalParams)}`) console.error(message, ...optionalParams) } }

To catch global errors in Angular, you can use the ErrorHandler:

class MyErrorHandler implements ErrorHandler { constructor(loggerService: LoggerService) {} handleError(error) { // Send error to Sentry Sentry.captureError(error) } } @NgModule({ providers: [{ provide: ErrorHandler, useClass: MyErrorHandler }], }) class CoreModule {}

Build Complex Components

Often we need to develop complex components in our applications. For this case, I suggest the following:

Try to solve your problem using the fantastic Angular CDK, which provides a set of tools that implement common interaction patterns while being unopinionated about their presentation. Examples are tools for accessibility, overlays, scrolling, drag & drop, tables and more.

If you build your component, look at existing open-source Angular libraries like Angular Material. There you can see how components are written the "Angular way".

You can also look for existing Angular components in npm. Therefore I can recommend taking a look at curated component lists like Awesome Angular Components or Awesome Angular. Anyways, I would advise checking the following for each 3rd party library you want to integrate into your project:

  • When was it published the last time?
  • Is it actively maintained? How many open issues are on GitHub?
  • Is it actively used by checking npm weekly download numbers?

Miscellaneous

  • Use Prettier as code formatter
  • Use Augury, Redux DevTools or any other useful browser dev tools
  • Use Compodoc (or any other similar tool) to generate documentation for your application.
  • Use Husky to check if the commit message has the correct format, the code is formatted, the linter has no errors, and the run unit tests before you push your code.
  • Lazy load all your modules. This way, you can split your application into smaller bundles that are only loaded if necessary.

Conclusion

It is essential to agree with your team on such an opinionated setup. I would propose this approach to the team, discuss alternatives, advantages, and disadvantages and try to find a good compromise. In the end, the project should be scalable, and the team should be able to deliver features quickly.

This article showed you my approach to setting up a new Angular project. It is not complete and maybe not a perfect approach, but it is my experience, so your suggestions are always welcome in the comments.

I recommend reading this free eBook from Manfred Steyer Enterprise Angular - DDD, Nx Monorepos and Micro Frontends, which covers a lot of the discussed topics in more detail.

I will never share any of your personal data. You can unsubscribe at any time.

If you found this article helpful.You will love these ones as well.
How To Generate Angular & Spring Code From OpenAPI Specification Image

How To Generate Angular & Spring Code From OpenAPI Specification

Manually Lazy Load Modules And Components In Angular Image

Manually Lazy Load Modules And Components In Angular

How To Build An Angular App Once And Deploy It To Multiple Environments Image

How To Build An Angular App Once And Deploy It To Multiple Environments

The Last Guide For Angular Change Detection You'll Ever Need Image

The Last Guide For Angular Change Detection You'll Ever Need