Monorepo: Nx + Storybook in Angular

Lalinda Dias
10 min readJul 13, 2022
Many Repo vs MonoRepo — Diagram

When you think about the architecture of front-end applications, you have to focus on two main things.

  1. Separating concerns
  2. Mitigating code repetitions

In order to achieve these, you have to break down large features into smaller, reusable packages (micro front-ends). But, then comes the question of how you manage all those small packages/repos?

Each package will have its own configuration. Every time you create a package, you have to use the same configuration, which is repetitive. Also, when you do a change in a package, in order to see the change in the workflow you have to locally build or publish the package, and wait for each build to finish, which can be tedious. This is where monorepos come into the picture.

What is a monorepo?

A monorepo is an architectural concept that allows putting multiple smaller repositories into a single repository. In simpler terms, instead of managing multiple isolated repositories, you manage a super repository which consists of all isolated code parts.

Monorepos contain one source of truth: single test suite, single configurations to deploy and pack(docker and webpack). Also, it is a single git repository.

Advantages of Monorepos

  • Common configs and tests
  • Dependency management is easy — Only a single package.json. No need to add a package.json for each repo. However, if there is a need, you can add a package.json for each repo as well.
  • Reuse components and features

Disadvantages of Monorepos

  • Cannot restrict access to some packages of the app

Only putting several repos into a single one does not make it a monorepo. A good monorepo has distinct and separate projects which have well-defined dependencies and relationships.

Nx Introduction

Nx is a smart, fast and extensible build system with monorepo support and integrations. An NX workspace contains Angular applications and libraries we create, and Nx is not a replacement for Angular CLI. It just adds some extra capabilities to Angular CLI, so finally we can say an Nx workspace is an Angular CLI workspace.

Create a workspace and an application

To create a new workspace, run the following command.

npx create-nx-workspace@latest

If you want a React, Angular, etc workspace, you can set it up by using the preset flag.

npx create-nx-workspace <workspace-name> --preset=react

npx create-nx-workspace <workspace-name> --preset=react-native

npx create-nx-workspace <workspace-name> --preset=angular

During the workspace creation process, you are asked to set up some configurations. You can give a name for your initial application here. I have given the name ‘bike-rental-app’. Also, you can set up Nx Cloud, but we are not going to talk about it in this article.

Workspace Creation: Initial Configurations

I have created an Angular workspace for this article, but the workspace structure is almost the same for React and other front-end technologies as well.

Folder Structure

There is no fixed folder structure. If you create a workspace using a preset as we did, the folder structure will be as follows.

Workspace folder structure

You can see the familiar angular.json file at the root, and inside the apps folder, the bike-store Angular app I just created is there. If you have used Angular CLI before, everything should be familiar.

/apps/ — This folder contains all the runnable application projects.

/libs/ — This folder contains all the library projects.

/tools/ — This contains scripts that act on your project, which can be database scripts, custom executors or workspace generators.

/nx.json — This configures the Nx CLI. It tells Nx what needs to be cached, how to run tasks, etc.

Inside the bike-store Angular app, .eslintrc.json extends all the rules from another .eslintrc.json which is available at the root level. You can also see a jest.config.js file at the root level. By default, Nx bootstraps your components with the Jest test runner.

There are more folders and files, but the above folders and files are the most important to get an initial idea of Nx.

E2E Testing

Along with the Angular App I create, there is another app called bike-store-e2e. This is a fully configured Cypress test suite, and the starter test has been linked to our app. You can launch it using the below command in Angular CLI.

ng run bike-store-e2e:e2e --watch

You can learn more about Cypress e2e testing from this link. Nx configures all of these for us.

Running Applications

To run the application, use the below command. It is the same command we use in Angular CLI, but we have to specify the project we run.

ng serve bike-rental-app

Libraries in Nx

Frontend developers should be able to build general solutions that can be reused in different applications because they can have common problems, UI and features. Such a solution can be developed in libraries, and these libraries can be published and shared. Libraries are a great way to save developers time and effort by reusing a solution to a common problem.

There can be different types of libraries.

1. Feature libraries: These libraries implement specific business use cases or pages in an application.

2. UI libraries: Contains presentational components. The components in UI libraries do not know about the outside world. It displays the content it receives from the wrapper component(parent) and has the possibility of emitting some events to wrapper components.

3. Data access libraries: Contains logic for interacting with backends and the code related to state management.

4. Utility libraries: Contains low-level utilities used by other libraries and applications.

All these libraries are again fallen into two main categories.

1. Publishable libraries: If you want to access the library from outside the Nx Workspace, you have to publish it. This can be achieved using the --publishableflag when creating a library. Do not forget that the --publishable flag does not automatically publish libraries. It adds a builder target to your Nx workspace library that compiles and bundles your app. The resulting artefact can be published to some registry like NPM. You can start the build using the command: nx build <lib-name> , which will then produce an optimized bundle in the dist/<lib-name>folder. One thing to when generating a library with --publishable is that it requires you to also provide an --importPath. The import path you give is the actual scope of your distributable package (e.g.: @myorg/mylib) - which needs to be a valid npm package. I have written a separate article on how to publish a library and refer to the naming guidelines in this article.

2. Buildable libraries: Buildable libraries are mainly used for producing some pre-compiled output that can be directly referenced from an Nx workspace application without the need to again compile it. Their scope however is not to distribute or publish them to some external registry. Thus they might not be optimised for bundling and distribution.

Every Nx library has a index.ts file which allows putting what should be exposed and for others to consume, and what should be private to library itself.

Creating a library project in Nx

To generate a library project inside an Nx workspace, run the below command.

npx generate lib SharedLib

SharedLib is the library name. Once you run the above command, you can see a library project called SharedLib in the libs folder. Currently, it does not do a lot, so let’s create a component in our newly created library project. We do not have any plans to publish this library to a package manager and our intention is to create UI components in this library so that they can be used in multiple applications in the same workspace.

npx g c button --project=SharedLib --export

I have used the project flag to tell in which project my component should be created and the export flag to tell I am exporting the component, so I can use it in one of my applications. What the export flag does is it adds our component to the exports array of the library module as in the below image.

Newly created library component in module’s export array

Using library components in applications

Now, let’s use the newly created library component, which is button, in our bike-rental-app.

You have to import the library module, which is shared, to the application module. Since we have only app Module at the moment, I have imported it to the app.module.ts file. Do not forget to add the library module to the import array as well.

Importing library module

Also, add the below HTML tag to the app.component.html, and you will see the library component in the view.

<stores-button> </stores-button>

If you create another application in Nx workspace and import the library, you should be able to use the component there as well. Now, if you make a change in the library component, both applications will get updates instantly.

Storybook Integration with Library

Storybook allows you to easily build design systems, and a collection of reusable components and standards, and also it helps your team and organization to stay consistent when building products and brands. Simply, it creates isolated components and allows you to work on one component at a time. Simply, you can view all of your UI libraries using Storybook and test the reusability in the library itself.

To install the storybook configurations, run the below command in a terminal.

npm install -D @nrwl/storybook

Stories in Storybook

A story is a state of a component. Developers can write multiple states for a component. For example, let’s take a button. In a typical UI, there can be different states such as primary, secondary, etc.

Source: Angular Material (https://material.angular.io/components/button/overview)

Stories allow you to develop these states. In development terms, a story is a function that instructs how to render the component. We can generate stories for our library component using the below command.

ng generate storybook-configuration shared

Once you run the command, you have to select some configurations for storybook.

Storybook configurations

Running Storybook

To run storybook, you have to use the below command.

nx run shared-cmps:storybook

By default, storybook use 4400 port, and you should be able to run storybook in a browser using http://localhost:4400/.

Storybook View

You can see all the components in library in the left pane, and controls and actions for a specific component at the bottom drawer. I have two components in my library called button and header. In order to see the states of components, you have to click on the relevant component.

A Few Improvements to our button component

Before writing stories for component, I am going to suggest some changes in order to improve accessibility of some components. One common mistake people make creating button components is using element selector. The browser comes with button by default and it is better if we can use the same. The reason is to improve accessibility. For example, if you add a click handler on a button element, pressing spacebar in the keyboard will also fire the click handler. This will not happen if you attach a click handler on a div element. For that reason and to leverage some other accessibility guidelines, it is better to use attribute selector. (Angular Material button component does the same) Go to the button.component.ts and change the selector as below to add attribute selector.

Adding attribute selector to our button

If you get a linting error, go to the .eslintrc.json file and change the value of type attribute to [‘element’, ‘attribute’] inside @angular-eslint/component-selector. After that, change the Template variable button.component.stories.ts as below.

Then add the <ng-content></ng-content> to button.component.html. Add the below styles to your button.component.scss file and I have created diffenrt variations of my button in terms of styles.

button.component.scss

Our button.component.ts file should look like below to accept different states of button and attach class names based on the state.

button.component.ts

Writing Stories for Components

Writing stories for components is very simple. The below is my button.component.stories.ts file and you can see the stories of Basic and Stroked at the bottom.

button.component.stories.ts

You can go to the storybook and test the button component changing the state of the button component.

Final Button Component in Storybook View

Now, it is up to you to implement other states of the button.

Important:

Inside the library project, you can create a module for each component, so we do not need to import the whole library module when we need a single component in library. We are going to talk this in the next article.

Conclusion

I hope you learnt something in this article. There are more advanced concepts in Nx and Storybook. You can refer the official documentation to refer those and I am planning to write separate articles for them.

Thank you for reading.

--

--