Angular 10 + Storybook + npm package = NG Design System 💜 - Step-by-Step
Tags: angular, npm, storybook, uikit
Published: 2020-10-15 15:54:24

> ! The Angular version used here is **10.x**

## tldr - The Task was:

**Create a UI Kit with Angular that lives on its own and scales to multiple product usage.**

Or in technical terms:

`npm i @your-company/your-ui-kit`
```typescript
import { Button } from '@your-company/your-ui-kit'
```

## The Final Result:

- Angular-based library published as `@your-prefix/pattern-lib` 
- Using Storybook to preview the library elements
- Using and exporting global `scss` styles

---

# 1. Background and Decision-Making 👨🏽‍💻

Our product application is based on [Angular](https://angular.io/). We wanted it to scale such that we can quickly re-use components even across other products.
Those components should always go with the Brand visuals.

To achieve that we wanted to do what the de-facto standard is: *Creating a Design System that is implemented as a Pattern Library* (furthermore called Pattern Lib)


## 1.1. A "No" to Web Components and a "Yes" to NG components 👀

If you follow me on different channels you know that I love **Web Components**. **But**:

> Use the right tool for the right job 🛠

At [Mercedes-Benz.io](https://www.mercedes-benz.io/) we were using Web Components together with [Stencil](https://stenciljs.com/) to create the Pattern Library. This decision was based on the fact that we had React, Vue, Angular and partially even Elm in our product stack and therefore needed to find a Library approach that is framework independent.

However being framework independent comes with the downside of not getting the best from that very framework since you are mixing up different technologies - that can especially get messy once your requirement says: Enable SSR.

So at the end of the day: You have the balance the upsides and downsides based on how your business is built.

### The process that led to an Angular-based PatternLib

1. Angular does a lot of preprocessing in its compilation process. So the compiler ensures the best overall output is achieved - when using Angular. Angular cannot optimize the code you put in your Web Component.

2. Maintaining a framework-agnostic solution makes only sense if you plan to support multiple frameworks in the long run.

3. Small team: Focus switch avoidance between e.g. Stencil and Angular. Sticking with Angular delivers overall better output since we can apply the same guidelines.

4. You do not necessarily benefit of awesome Editor help when mixing up 2 technologies - or you have to put in more of work to achieve that.

5. If you still want to output Web Components from your library you will still be able to tune up your build to do so.

![](https://media.giphy.com/media/26xBP13FAQL2mhc1W/giphy-downsized.gif)

---

# 2. How to implement an Angular UI Kit / NG UI Library

## 2.1. The Pre-setup 

I guess it is clear that you will have to have `@angular/cli` installed and ready.

Besides that: The first thing you want to do is to be able to test your library to confirm that you did a good job. So before we create the actual library we want to create the application that consumes it.

```bash
ng new my-app --prefix=ma --style=scss --legacyBrowsers=true 
```

We use the `prefix` to be safe and isolated from any used library (that might be unprefixed).
You do not need `legacyBrowsers` if you do not plan to support IE.

## 2.2. Creating the library

Run the following command in the directory where your `my-app` is located (so basically in its parent directory `./my-app/../`).

```bash
ng new design-system --create-application=false --prefix=ds --style=scss
```

**Important Note**: In Angular 10.1.4 that I used for this project the `--style=scss` did not have any effect. This comes from the fact that we created an empty application and Angular has no option yet to predefine it (we will fix it soon in this tutorial).

We call the parent `design-system` to not get confused with its child module below.
Now your directory structure should look like this:

```
.
├── my-app
├── design-system
```

Inside your `design-system` directory we now create the actual library that will contain our components:

```bash
ng generate library pattern-lib --prefix=pl
```

First thing you want to do is to make the package *scoped* to easier identify it is your library. 

So go to `design-system/projects/pattern-lib/package.json` and change the `name` from `pattern-lib` to `@your-company/pattern-lib` .

As said above: We also have to fix `--style=scss` on our own since this is not yet possible to define for a `library` or empty `application` upfront. Do the following:

1. Go to `design-system/angular.json`
2. Search for your added library in `"projects": { ...}`
3. Add the `schematics` property to your `projects/pattern-lib` 

In my project it then looks somewhat like this:

```json
...
"projects": {
    "pattern-lib": {
      "projectType": "library",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
...
```

## 2.3. Creating a `button` component in the library

```bash
ng generate component button --project=pattern-lib
```

This command will create a component named `button`. Your directory structure should contain the following directories:

```
.
├── my-app
├── design-system
   ├── projects
      ├── pattern- lib
         ├── src
            ├── lib
               ├── button
```


---

You might want to say: **Yo David why so many subfolders? Can't I just put it in the root src?**

**Answer**: Angular allows to control multiple libraries / components / modules etc. in one Angular project root and the `@angular/cli` is optimized to suppor this exact structure. If we start building a pattern lib in a default `ng new my-pattern-lib` application -> that will work for sure but we are leaving the safe track to a slippery path because then we have to bend the tools to do what we want to do (expose and publish library parts).

---

Open up `button.component.ts` and make sure it has 2 properties defined in the class like this:

```typescript
import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'pl-button', // pl is our prefix
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.css']
})
export class ButtonComponent implements OnInit {
  @Input('label') label: string | null;
  @Input('pink') pink: boolean;

  constructor() { }

  ngOnInit(): void {
  }

}

```

Quick explainer: By setting the properties explicitly (e.g. `@Input('pink') pink: boolean;`) the component exposes its editable fields which will come in handy in Storybook later since Storybook can autodetect the field types.

---

Now open `button.component.html` and fill it with:

```html
<button [attr.is-pink]="pink" [ngClass]="{'make-pink': pink}">{{label ? "😎 " + label : "No Label provided 🧐"}}</button>
```

The `[attr.is-pink]="pink"` we use to debug if we run into errors. It is not needed though 😉

Also open `button.component.scss` and fill it with:

```scss
button {
  background: blue;
  padding: 1rem 2rem;
  border-radius: 3px;
  appearance: none;
  border: 0;
  -webkit-appearance: none;
  -moz-appearance: none;
  font-size: 1.5rem;
  letter-spacing: 1px;
  color: white;
  box-shadow: 0 4px 10px rgba(55, 55, 55, 0.3),
    0 6px 35px rgba(55, 55, 200, 0.7);
  cursor: pointer;

  &.make-pink {
    background: #ff00a2;
    box-shadow: 0 4px 10px rgba(55, 55, 55, 0.3), 0 6px 35px rgb(200 55 150 / 70%);
  }
}
```

**Now you want to build and see the button in action - but it is just a library, not an SPA, so HOW?**

![](https://media.giphy.com/media/kQOxxwjjuTB7O/giphy.gif)

## 2.4. Run it: **[Storybook](https://storybook.js.org/)** to the rescue. 

Adding storybook normally is quite easy as there are ready-to-go recipes. **However** these do not naturally apply for an Angular library.

### 2.4.1. Installing Storybook and binding it to our Library

1. Run `npx -p @storybook/cli sb init --type angular` within your `design-system` Angular project.

2. Open `.storybook/tsconfig.json` and change the extends line to `"extends": "../projects/pattern-lib/tsconfig.lib.json"` (if not already set)

### 2.4.2. Setting up our button story

1. Go to `design-system/stories` and DELETE EVERYTHING
2. Create a file named `Intro.stories.mdx` with following content:

```mdx
import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="Library Example/Intro" />

<style>{`
  .box {
    margin: 2rem 0;
    padding: 2rem;
    background: rgba(233,233,233,0.7);
    border-radius: 3px;
  }
`}</style>

# Hello you!

<div class="box">
  This is the Pattern Library Intro File to provide a guiding path or whatever else you want to do here. Feel free!
</div>

![](https://media.giphy.com/media/yJFeycRK2DB4c/giphy.gif)
```


2. Create a file named `Button.stories.mdx` with following content:

```typescript
import { Story, Meta } from '@storybook/angular/types-6-0';
import { ButtonComponent } from '../projects/pattern-lib/src/lib/button/button.component';

export default {
  title: 'Atomics/Buttons',
  component: ButtonComponent,
  argTypes: {
    label: { control: 'text' }, 
    // we need to override here since in Angular it could be null as well (see button.component.ts) and therefore it would become an ambigious data type for storybook
  }
} as Meta;

const Template: Story<ButtonComponent> = (args: ButtonComponent) => ({
  component: ButtonComponent,
  props: args,
});

export const FancyBlueButton = Template.bind({});
FancyBlueButton.args = {
  label: 'Button',
};

export const FancyPinkButton = Template.bind({});
FancyPinkButton.args = {
  label: 'Pink version',
  pink: true,
};
```

### 2.4.3. Run, run, run!

Trigger `npm run storybook` within your `design-system` project folder and wait for it to - hopefully successfully - boot up.

As a result you should see something like this:

<!-- ![Storybook](https://dev-to-uploads.s3.amazonaws.com/i/tafzsdg5kr46tyiexvdo.gif) -->

![Storybook](https://dev-to-uploads.s3.amazonaws.com/i/stmfb0qwclc21nbqejkj.gif)


Cool Stuff!


--- 

**Quick Summary**: Up to this point we created an Angular library with a button as a component and connected Storybook to it to be able to see and structure it without running an actual Angular SPA.


## 2.5. Global SCSS and Component Composition 

### 2.5.1. Global SCSS

1. Go to `design-system/projects/pattern-lib/src/` 
2. Create a directory `styles/`
3. Create a file named `global.scss` with some random content e.g. `body { background: red }`

### Use it in Storybook

To make sure that Storybook can apply the global scss head over to `.storybook/preview.js` and add it as a webpack import like this:

```js
import '!style-loader!css-loader!sass-loader!./../projects/pattern-lib/src/styles/global.scss';
```

That's it. Storybook will now always use that file as a global file for every view.

### Use it in Angular
Using a global SCSS in Angular shouldn't be a problem since you can always `@import` it in any of your SCSS files in your project e.g. like 

```scss
@import '../../styles/global';
```

However >> **WARNING**: Make sure not to import actual CSS definitions such as `body { background: red }` into your Angular components as those would get duplicated and would drastically decrease performance.

If you want a global CSS file for your Angular App you can (and should) surely define it in your library but you should export them with the npm package (see section 2.6.) such that your Angular SPA can then include it.

In any Angular application (!= Angular library) you can add this to the `angular.json` to define a global style for the App:

```json
"styles": [
  "node_modules/@your-company/pattern-lib/styles/style.scss"
],
```

### 2.5.2. Component Composition

Please refer to https://www.learnstorybook.com/intro-to-storybook/angular/en/composite-component/ if you wanted to combine multiple components in Storybook.


## 2.6. Using the `pattern-lib` in your Angular application `my-app`

Now what we want is to be able to use our patterns in any application that we create - as an npm package.

The following diagram visualizes that:

![](https://dev-to-uploads.s3.amazonaws.com/i/a91u0qq7zrpb99bpi9h4.png)


### Hold up! Important!

Before we use our library we need to import the `CommonModule` from `@angular/common` as otherwise our `[ngClass]` binding of our button will not work (more info here: https://stackoverflow.com/a/45573086).

So in `design-system/pattern-lib` open up `pattern-lib.module.ts` and adapt it such that you do:

```typescript
//... other imports
import { CommonModule } from '@angular/common';

@NgModule({
  ...
  imports: [
    CommonModule
  ],
  ...
})
```

### 2.6.1. Export the button for usage

If we want our button to be usable outside of the library we need to export it in our `pattern-lib.module.ts` like this:

```typescript
//.. other imports
import { ButtonComponent } from './button/button.component';


@NgModule({
  ...
  exports: [PatternLibComponent, ButtonComponent]
})

```

### 2.6.2. Using the library package through local binding

Go to the directory `design-system` and run 

```bash
ng build --project=pattern-lib
```

Head over to `design-system/dist/pattern-lib` and run [`npm link`](https://docs.npmjs.com/cli/link).

Now your package should be available on your computer under its package name `@your-company/pattern-lib`.

No go over to your Angular SPA `my-app` and run `npm link @your-company/pattern-lib`. This basically is like a local `npm install` - however it will not show up in your package.json. Alternatively you can also do `npm install ../design-system/dist/pattern-lib` if you prefer so.

### Add the linked library to our `my-app`

1. Go to `my-app/src/app/app.module.ts` and add the library import

```typescript
import { PatternLibModule } from '@your-company/pattern-lib';
```

2. Also add it to the `imports` field of `app.module.ts`:

```typescript
@NgModule({
  ...
  imports: [
    ...
    PatternLibModule,
  ],
  ...
})
```

3. Go to `my-app/src/app/app.component.html` and delete all the contents and put the following instead:

```html
<style>
  :host {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    font-size: 14px;
    color: #333;
    box-sizing: border-box;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
</style>


<div>
  <p>Hello I am the Angular SPA and following a button should be shown and it should be blue: </p>
  <dvpl-button label="hello"></dvpl-button>

  <p>And this one should be pink: </p>
  <dvpl-button label="Hello pink?" [pink]="true"></dvpl-button>
</div>

<!-- you only need this if you added a router: -->
<router-outlet></router-outlet>
```

Now you should be able to run `npm start` in your `my-app` SPA and you should see that your Angular application properly makes use of our library. 

![Running the app](https://dev-to-uploads.s3.amazonaws.com/i/xlfsg25u0xldstdirho7.png)


### 2.6.3. Using the package with an `npm` registry 

Now that you have it already running locally this should come with ease.

1. First things first. Choose a npm registry (e.g. Github or npmjs).

2. Create an `.npmrc` file in your `design-system` Angular root that looks similiar to the following:

```
registry=https://registry.npmjs.org/
always-auth=true
email=YOUR_EMAIL_GOES_HERE
```

3. Login with `npm login` - It should ask you for your credentials on the registry provided in the `.npmrc` file.

4. Once you are logged in you can simply run `npm publish` in your `design-system/dist/pattern-lib` directory to publish the library that you built! Make sure to NOT run it in your root directory but really in `dist` ! Because if you run `npm publish` in your root directory it will only publish all the source code and not the compiled library.

5. Now you can use it the same way you installed it locally but from the given repository with `npm install @your-company/pattern-lib` 🎉


![](https://media.giphy.com/media/RgfGmnVvt8Pfy/giphy.gif)


###

# 3. Testing Storybook,  atomic imports and Web Components, etc.

There is way more that we could discuss - but I don't wanna excess this here too much: How to not only test the components but test them with Storybook, how to export more atomically (a single Button import instead of the whole library if needed) and publishing for other Frameworks as a Web Component.

But this might be worth another Blog Post. Let me know!

# 4. Conclusion

You created a *Design System Repo* with Angular and you are able to publish it in different versions.
Also you can use them in whatever Angular app you want 😎.

Find the source code of the sample here: 
{% github activenode/angular-10-storybook-library %}

![](https://media.giphy.com/media/WS0OoLybvDIeuBLGxS/giphy.gif)


---

Sources:

- [1] https://angular.io/cli/generate
- [2] https://www.blexin.com/en-US/Article/Blog/Creating-Angular-components-libraries-68
- [3] Exporting global SCSS: https://medium.com/@Dor3nz/compiling-css-in-new-angular-6-libraries-26f80274d8e5
- [4] https://medium.com/@tomsu/how-to-build-a-library-for-angular-apps-4f9b38b0ed11


Design References:
Packaging Design Icon made [xnimrodx](https://www.flaticon.com/free-icon/packaging_2467521) from flaticon.com