Adobe XD, Storybook & Angular In Action – From Paper Sketch To A Running Application

In this article, I will show you how to design and develop application components for a SPA. We will start of from a paper sketch, head over to a designer tool, like Adobe XD, followed by an interactive testing environment, like Storybook, to finally result in a running Angular application.

In this article:

kd
Konstantin Denerz is architect and consultant at Thinktecture and focuses on Angular and User Experience improvement.

The demo application

An interactive live demo tells more than a thousand words. Here you can find the demo application and all Storybook stories demonstrating all ideas, concepts, as well as the code shown in this article.

The repository for the demo code is available on our Github team page.

As the demo is based on several node packages, the installation of dependencies with npm i is necessary after cloning. Please run the Storybook with npm run storybook and open the local URL http://localhost:6006 in your browser. Voila!

First ideas: Paper sketch

In various discussions with customers about requirements for a new application, I try to sketch user interface ideas to visualize different views of the possible target application. An important part of this step is to figure out the content that the user should see and to group it logically within the UI. In the next step, the content groups should be separated by screens, pages, or dialogs to avoid overloading the user interface.

In the end, we have different sketches like the following one on paper, flip chart, or whiteboard:

You can see a dashboard screen with different UI elements, like document cards, measure cards, app header, and a document tree.

 

In some projects, I like to use an iPad to create sketches and share my ideas:

After having a rough vision of where I want to go, I move to Adobe XD to design the screen.

Materializing sketches: Designing components in Adobe XD

In the past, I used a lot of tools to create visual designs: Photoshop, Gimp, Inkscape, Sketch, and so on. Finally, I have ended up with Adobe XD.

It a great designing and prototyping tool and works with SVGs and components. You can create a component with different states and describe the transition between states. A clickable and recordable preview enables the demonstration of the animated prototype. Another central feature is to share the clickable and animated prototype, for instance with co-workers or customers. XD creates an unique link to your prototype, that is deployed as a web application. Of course, this link can be password protected.

If you want to know more about XD, you can find a feature list here: Adobe XD – Features

Here is the Adobe XD – Shared Prototype of the dashboard draft.

Designing the dashboard with contents and groups from the sketches is a fast way to visualize the UI and test the user experience in an early project phase. Redesigning designs is easier than refactoring code, so I am going iterative in XD.

In the first step, I create an artboard that stands for a specific screen size – iPad screen size in my case.

The document card component is the first component I created in XD. You can see three component states in the panel on the right side.

The following video shows the transition between default, hover, and expanded state.

Let's code: Developing components in Storybook

After designing the first components, we could start implementing them. In the early phase of a project, it is an advantage to develop in an isolated environment. Isolation here refers to authentication, backend APIs, databases, and further. Storybook provides exactly this feature. You can write small pieces of code to describe a component in specific states.

Setup

We have to set up some things before we can start developing. This command-line instruction creates a new Angular application.

				
					npx ng new article-xd-storybook-angular -p labs
				
			

The following command adds Storybook and some demo stories to our project.

				
					npx -p @storybook/cli sb init --type angular
				
			

Storybook add-ons

Please install KnobsBackgrounds and Theming add-ons for Storybook, as they will support us with some powerful features.

				
					npm i --save-dev @storybook/addon-knobs @storybook/addon-backgrounds @storybook/theming
				
			

Add @storybook/addon-knobs/register and @storybook/addon-backgrounds/register to add-ons list in the .storybook/main.js file.

Theming: Enable dark theme

Everybody these days needs a dark them, right? So go ahead and configure the theme in .storybok/manager.js:

				
					import { addons } from '@storybook/addons';
import { themes } from '@storybook/theming';

addons.setConfig({
  theme: themes.dark,
});
				
			

Development steps

There are always several ways to skin a cat. Let me show you how I do it in my projects. Usually, I follow these steps when I create new components:

The first story

If you look at the design above, you can see that the frame of each card looks identical. Only the content of each card is different.

As a developer, you try to distribute responsibility across different classes or components. That is why I created two components here. The CardComponent and the DocumentContentComponent. The CardComponent is the generic frame for all cards, and the DocumentContentComponent provides content that belongs to a document in the target app.

Each story starts with the default export. You can use / separator to structure your stories in Storybook. The decorator’s array contains two decorators that enable knobs in our stories and provide module metadata that should be used in each story.

				
					// from card.component.ts
export default {
  title: 'Core / Card',
  decorators: [
    withKnobs,
    moduleMetadata({
      declarations: [CardComponent],
      imports: [BrowserAnimationsModule, MatRippleModule],
    }),
  ],
};
				
			

The default story for our card component provides a template instead of a component property. This is necessary because we use content projection to provide individual content. The show property is the input for the card. The boolean() from the knobs add-on is a function that creates a control in the knobs panel. So we change the show state in the UI during runtime.

				
					// from card.component.ts
export const Default = () => ({
  template: `
    <labs-card [show]="show">
        Content Content <br>
        Content Content <br>
        Content Content <br>
        Content Content <br>
</labs-card>
  `,
  props: {
    show: boolean('show', true),
  },
});
				
			

The second story looks similar to the first, but it contains more content to visualize and test the card component with large contents.

				
					// from card.component.ts
export const Large = () => ({
  template: `
    <labs-card [show]="show">
        Content Content Content Content Content Content Content Content <br>
        Content Content Content Content Content Content Content Content <br>
        Content Content Content Content Content Content Content Content <br>
        Content Content Content Content Content Content Content Content <br>
        Content Content Content Content Content Content Content Content <br>
</labs-card>
  `,
  props: {
    show: boolean('show', true),
  },
});
				
			

The card component with large contents looks like this:

The second story

In the following step, I want to create a card component with document content.

				
					// from document-content.component.stories.ts
export const Default = () => ({
  template: `
    <labs-card [show]="show">
        <labs-document-content [icon]="icon" [identifier]="identifier" [preview]="preview" [title]="title"></labs-document-content>
    </labs-card>
  `,
  props: {
    show: boolean('show', true),
    icon: text('icon', 'fa-file-medical-alt'),
    title: text('title', 'Lorem ipsum'),
    identifier: text('identifier', '2020-07-02#42'),
    preview: text('preview', 'Lorem ipsum dolor sit amet, co [...] a rebum. Stet clita kasd…'),
  },
});
				
			

The dashboard story

After developing small components like cards, we could create a complex layout component to render components as a list or grid. The dashboard component should do this job. The dashboard should be accessible via the corresponding route. For this case, I used the root component with router configuration in my story. The root components contain the app header, animated background, as well as the logo.

				
					// from root.component.stories.ts
import {RootComponent} from './root.component';
import {withKnobs} from '@storybook/addon-knobs';
import {moduleMetadata} from '@storybook/angular';
import {MatRippleModule} from '@angular/material/core';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MeasureContentComponent} from '../measure-content/measure-content.component';
import {AppHeaderComponent} from '../app-header/app-header.component';
import {BackgroundComponent} from '../background/background.component';
import {UserComponent} from '../user/user.component';
import {MatButtonModule} from '@angular/material/button';
import {CardComponent} from '../card/card.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {DocumentContentComponent} from '../document-content/document-content.component';
import {FormsModule} from '@angular/forms';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {ListContentComponent} from '../list-content/list-content.component';
import {FileTreeComponent} from '../file-tree/file-tree.component';
import {RouterModule} from '@angular/router';
import {DashboardComponent} from '../dashboard/dashboard.component';
import {APP_BASE_HREF} from '@angular/common';
import {DetailsComponent} from '../details/details.component';
import {TaskService} from '../../services/task.service';
import {of} from 'rxjs';

const tasks = [
  {title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', checked: true},
  {title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'},
  {title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'},
  {title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'},
  {title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'},
  {title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'},
  {title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'},
  {title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'},
  {title: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'},
];

export default {
  title: 'Demo',
  decorators: [
    withKnobs,
    moduleMetadata({
      declarations: [
        DocumentContentComponent,
        BackgroundComponent,
        AppHeaderComponent,
        MeasureContentComponent,
        UserComponent,
        CardComponent,
        ListContentComponent,
        FileTreeComponent,
        DashboardComponent,
        DetailsComponent,
      ],
      imports: [
        MatRippleModule,
        MatProgressSpinnerModule,
        MatButtonModule,
        FormsModule,
        BrowserAnimationsModule,
        MatCheckboxModule,
        RouterModule.forRoot([
          {path: '', redirectTo: '/dashboard', pathMatch: 'full'},
          {
            path: 'dashboard', component: DashboardComponent, children: [
              {path: ':id', component: DetailsComponent},
            ],
          },
        ], {useHash: true}),
      ],
      providers: [
        {provide: APP_BASE_HREF, useValue: '/'},
        // mock tasks service
        {
          provide: TaskService, useValue: {
            getAllTasks: () => of(tasks),
          },
        },
      ],
    }),
  ],
};

export const Default = () => ({
  component: RootComponent,
});

				
			

In this story, we use the root component and router configuration to enable the dashboard. The second important point is, that I mocked the tasks service to provide mock data.

Mocking enables the parallel development of frontend and backend.

Final touches: Creating the resulting Angular app

To get the Angular app ready, we should implement all mocked services and add authentication if required. The last configuration step is to import all required modules and declare all components, pipes, and so on in the app module. This step is easy because you could use the same module metadata like in the root.component.stories.ts, but without the mocked providers.

This command compiles the Angular app with production configuration to the dist folder:

				
					npx ng build --prod
				
			

Summary

This article hopefully provided you with an idea of my approach to design and implement user interfaces with customers in a lightweight requirement-based way. This component-centric way, and using the mentioned tools, makes it possible to quickly build UIs and evaluate ideas. Feel free to contact me if you need assistance in designing and developing your modern web applications.

Free
Newsletter

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

EN Newsletter Anmeldung (#7)
Related Articles
AI
sg
One of the more pragmatic ways to get going on the current AI hype, and to get some value out of it, is by leveraging semantic search. This is, in itself, a relatively simple concept: You have a bunch of documents and want to find the correct one based on a given query. The semantic part now allows you to find the correct document based on the meaning of its contents, in contrast to simply finding words or parts of words in it like we usually do with lexical search. In our last projects, we gathered some experience with search bots, and with this article, I'd love to share our insights with you.
17.05.2024
Angular
sl_300x300
If you previously wanted to integrate view transitions into your Angular application, this was only possible in a very cumbersome way that needed a lot of detailed knowledge about Angular internals. Now, Angular 17 introduced a feature to integrate the View Transition API with the router. In this two-part series, we will look at how to leverage the feature for route transitions and how we could use it for single-page animations.
15.04.2024
.NET
kp_300x300
.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.
15.11.2023