Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / AngularJs

Series 3: Build an Angular 2 Multi-Step Wizard using UI-Router 1.0 and TypeScript – Part 1

2.87/5 (4 votes)
31 Jan 2017CPOL8 min read 20.8K  
This is the first part of Introducing Angular Multi-Step Wizard using UI-Router Series 3.

This is the first part of Introducing Angular Multi-Step Wizard using UI-Router Series 3. You will learn how to build a multi-step wizard using the following technologies:

The source code for this tutorial series is published on GitHub. Demo application is hosted in Microsoft Azure.

Part 1: Create a SPA in UI-Router 1.0 for Angular 2

In the previous tutorial series, we created the Multi-Step Wizard using UI-Router v 1.0 with Angular 1.5+ component-based architecture. In this tutorial, we will continue to use Visual Studio Code to build the initial structure of the Single-Page Application (SPA) using using UI-Router v 1.0, Angular 2, and TypeScript 2.0+.

Client-Side Technologies

The application structure, styles, and patterns follow the recommendations outlined in the official Angular 2 Style Guide. The application idea is inspired by Scotch.io’s tutorial.

I use the official Angular 2 Developer Guide and UI-Router for Angular 2 – Hello Solar System! as a reference to write this tutorial.

Prerequisites

The following are required to complete this tutorial:

  • Build the QuickStart app if you are new to Angular 2
  • Install Node.js and npm (Nods.js > 6.3.x and npm > 3.10.x) on your machine if you haven’t done so already

Task 1. Set up the Project Structure

I prefer to organize my project based on features/modules. Building a maintainable, scalable, and well-organized application should start from the beginning. Using DRY and SRP patterns, we will create smaller code files that each handle one specific job. This approach has helped me personally to locate, enhance, and debug requested features from clients quickly.

  1. Let’s creating folders and copying files to C:\_tutorials\ng2-multi-step-wizard-ui-router1 from GitHub as listed below:

     

    ng2-multi-step-wizard-ui-router1         // Project folder      
        |--app
            |--address                      // Address feature
            |--data                         // Data feature
            |--navbar                       // Navbar feature
            |--personal                     // Personal feature
            |--result                       // Result feature
            |--work                         // Work feature
        |--content                          
            |--css
                |--loading-bars.css 
                |--riliwan-rabo.css
                |--style.css
            |--images
                |--favicon.ico 
                |--loading-bars.svg
            |--js
                |--loading-bars.js
        |--package.json
        |--systemjs.config.js
        |--tsconfig.json

     

  2. Lines 19-21 list the package definition and configuration files:

    • package.json: defines scripts and serves as documentation for what packages the Employee Tracker depends on.
    • systemjs.config.js: loads application and library modules.
    • tsconfig.json: is a configuration file to guide the TypeScript compiler as it generates JavaScript files.

Task 2. Install the Packages

Open the terminal window, enter the npm install command to install all listed packages and libraries in package.json using npm.

Task 3. Create startup page

  1. Add the index.html file to the ng2-multi-step-wizard-ui-router1 folder.

  2. Replace the code in this file with the following:

     

    <!DOCTYPE html>
    <html>
    
      <head>
        <base href="." />
        <title>Multi-Step Wizard using Angular 2 And UI-Router 1.0 by Cathy Wun</title>
        <!-- CSS Files -->
        <link rel="icon" type="image/png" href="content/images/favicon.ico" />
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.css" />
        <link rel="stylesheet" href="content/css/riliwan-rabo.css" />
        <link rel="stylesheet" href="content/css/style.css" />
        <link rel="stylesheet" href="content/css/loading-bars.css" />
        
        <!-- IE required polyfills, in this exact order -->
        <script src="node_modules/core-js/client/shim.min.js"></script>
        <script src="node_modules/zone.js/dist/zone.js"></script>
        <script src="node_modules/systemjs/dist/system.src.js"></script>
        <script src="systemjs.config.js"></script>
        <script>
        System.import('app')
          .catch(console.error.bind(console));
      </script>
      </head>
    
      <body>
        <multi-step-wizard-app>
          <!-- Show simple splash screen-->
          <div class="splash">
              <div class="color-line"></div>
                  <div class="splash-title">
                      <h1>Angular 2 Multi-Step Wizard</h1>
                      <img src="content/images/loading-bars.svg" width="64" height="64" title="" alt="" />
                  </div>
              </div>
          </div>
        </multi-step-wizard-app>
      </body>
    
    </html>

     

    The index.html file serve as our startup page. It performs the following functions:

    • loads our resources (.css and .js)
    • configures SystemJS to load library modules and launch our application by running the AppModule in the main.ts file
    • renders our application’s component between the multi-step-wizard-app tags
    • shows our splash screen

Task 4. Bootstrap our application

  1. Add the main.ts file to the app folder.

  2. Replace the code in this file with the following:

     

    //main entry point
    // The browser platform with a compiler
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    
    // The app module
    import { AppModule }              from './app.module';
    
    // Compile and launch the module
    platformBrowserDynamic().bootstrapModule(AppModule);

     

    Line 9 bootstraps the imported AppModule from app.module.ts file using the Just in Time (JIT) complier to launch the application.

Task 5. Create the root module

By convention, every Angular app has a root module class called AppModule in app.module.ts file. The @NgModule decorator allows us to bundle components, services, pipes, and directives at the module level.

  1. Let’s add app.module.ts file to the app folder.

  2. Replace the code in this file with the following:

     

    import { NgModule }           from '@angular/core';
    import { BrowserModule }      from '@angular/platform-browser';
    import { UIRouterModule }     from "ui-router-ng2";
    import { FormsModule }        from '@angular/forms';
    
    /* App Root */
    import { AppComponent }       from './app.component';
    import { NavbarComponent }    from './navbar/navbar.component';
    
    /* Feature Components */
    import { PersonalComponent }  from './personal/personal.component';
    import { WorkComponent }      from './work/work.component';
    import { AddressComponent }   from './address/address.component';
    import { ResultComponent }    from './result/result.component';
    
    /* App Router */
    import { UIRouterConfigFn }   from "./app.router";
    import { appStates }          from "./app.states";
    
    /* Shared Service */
    import { FormDataService }    from './data/formData.service'
    
    @NgModule({
        imports:      [ BrowserModule, 
                        FormsModule,
                        UIRouterModule.forRoot({ 
                          states: appStates,
                          useHash: true,
                          config: UIRouterConfigFn
                        }) 
                      ],
        providers:    [{ provide: FormDataService, useClass: FormDataService }],
        declarations: [ AppComponent, NavbarComponent, PersonalComponent, WorkComponent, AddressComponent, ResultComponent ],
        bootstrap:    [ AppComponent ]
    })
    
    export class AppModule {}

     

  3. Lines 24 – 31 import the following supporting modules whose exported components, directives, or pipes are referenced by the component declared in the AppModule.

    • BrowserModule: provides critical services that are essential to launch and run our app in the browser
    • FormsModule: provides critical services and functionalities to create form
    • UIRouterModule: is a UIRouter module which provides the application-wide configured services with routes in the root module
  4. Line 32 registers the FormDataService with the injector in the root module so that the same instance of the FormDataService is available to all part of the application.

  5. Line 33 declares a list of components (AppComponent, NavbarComponent, PersonalComponent, WorkComponent, AddressComponent, ResultComponent) that belong to the AppModule.

  6. Line 34 bootstraps the root component named AppComponent when Angular starts the application.

  7. Line 37 exports the AppModule so that the main.ts file can import it.

Task 6. Configure routes for the Router

The application will have one Router which enables navigation from one view to the next. To learn more about Routing & Navigation in general, please see the official doc here.

  1. Add the app.states.ts file to the app folder.

  2. Replace the code in this file with the following:

     

    import { PersonalComponent }  from './personal/personal.component';
    import { WorkComponent }      from './work/work.component';
    import { AddressComponent }   from './address/address.component';
    import { ResultComponent }    from './result/result.component';
    
    export const appStates = [
        // 1st State
        { name: 'personal', url: '/personal',  component: PersonalComponent },
        // 2nd State
        { name: 'work', url: '/work',  component: WorkComponent },
        // 3rd State
        { name: 'address', url: '/address',  component: AddressComponent },
        // 4th State
        { name: 'result', url: '/result',  component: ResultComponent }
    ];

     

    We configure our Router with four states:

    • 1st State: when URL matches the path segment /personal, activate the personal state, route to the PersonalComponent
    • 2nd Route: when URL matches the path segment /work, activate the work state, route to the WorkComponent
    • 3rd State: when URL matches the path segment /address, activate the address state, route to the AddressComponent
    • 4th Route: when URL matches the path segment /result, activate the result state, route to the ResultComponent
  3. Add the app.router.ts file to the app folder.

  4. Replace the code in this file with the following:

     

    import { UIRouter }  from "ui-router-ng2";
    
    
    /** UIRouter Config  */
    export function UIRouterConfigFn(router: UIRouter) {
        // If no URL matches, go to the `personal` state's name by default
        router.urlService.rules.otherwise({ state: 'personal' });
    }

     

    The constructor function of the UIRouterConfig expects an injectable parameters named UIRouter. When URL doesn’t match any routes defined in our configuration, route to the personal state by default.

    NOTE: We passed our appStates configuration, and UIRouterConfigFn function into the UIRouterModule.forRoot method which returns a module containing the configured Router. We registered this configured Router with the root module named AppModule.

Task 7. Create our root component and its view

  1. Add the root component named app.component.ts file to the app folder.

  2. Replace the code in this file with the following:

     

    import { Component, OnInit, Input }   from '@angular/core';
    import { UIROUTER_DIRECTIVES }        from "ui-router-ng2";
    
    import { FormDataService }            from 'app/data/formData.service'
    
    @Component ({
        selector:     'multi-step-wizard-app'
        ,directives:  [ UIROUTER_DIRECTIVES ]
        ,templateUrl: 'app/app.component.html'
    })
    
    export class AppComponent implements OnInit {
        title = 'Multi-Step Wizard';
        @Input() formData;
        
        constructor(private formDataService: FormDataService) {
        }
    
        ngOnInit() {
            this.formData = this.formDataService.getData();
            console.log(this.title + ' loaded!');
        }
    }

     

    The AppComponent is our application shell. It imports the FormDataService as a service. Its constructor function expects an injectable parameter named FormDataService. When our AppComponent activates, we call our FormDataService.getData method to initialize formData with the original input data.

  3. Add the view named app.component.html file to the app folder.

  4. Replace the code in this file with the following:

     

    <section style="background:#efefe9;">
        <div class="container">
            <div class="board">
                <!-- Navigation Area (Circular Tabs) -->
                <msw-navbar></msw-navbar>
                <!-- End Navigation Area (Circular Tabs) -->
    
                <!-- Content Area -->
                <div class="tab-content">
                    <!-- Nested view  -->
                    <div ui-view></div>
                </div>
                <!-- End Content Area -->
            </div>
    
            <!-- For Debugging: show our formData as it is being typed -->
            <pre>{{ formData | json }}</pre>
    
        </div>
    </section>

     

    The app.component.html file contains the master layout for our HTML. It provides a shell with two regions: a navigation area and a content area. The navigation area renders the Navbar view between the msw-navbar tags. The content area uses ui-view directive to display the views produced by the UIRouter. In other words, when you click a circular icon in the tab menu, it’s corresponding view is loaded in the content area.

  5. Line 17 will always display our formData object in real time.

    Showing Form Data as it typed

Task 8. Create the Navbar Feature

The Navbar feature contains the following files:

  • navbar.component.ts: is a component contains the properties and functions that are bound to the Navbar view. It also contains the presentation logic for the Navbar view, and is the glue between the data and the view.
  • navbar.component.html: is an Angular 2 HTML template that defines the view for the Navbar

Task 9. Add component and view to the Navbar feature

  1. Add the navbar.component.ts file to the navbar folder.

  2. Replace the code in this file with the following:

     

    import { Component } from '@angular/core';
    
    @Component ({
        selector: 'msw-navbar'
        ,templateUrl: 'app/navbar/navbar.component.html'
    })
    
    export class NavbarComponent {}

     

    The navigation area of the AppComponent uses msw-navbar to display the Navbar view.

  3. Add the view named navbar.component.html file to the navbar folder.

  4. Replace the code in this file with the following:

     

    <div class="board-inner" id="status-buttons">
        <ul class="nav nav-tabs" id="myTab">
            <div class="liner"></div>
                        
            <!-- circular user icon -->
            <li>
                <a uiSrefActive="active" uiSref="personal" data-toggle="tab" title="personal">
                    <span class="round-tabs one">
                        
                    </span>
                </a>
            </li>
    
            <!-- circular tasks icon -->
             <li>
                <a uiSrefActive="active" uiSref="work" data-toggle="tab" title="work">
                    <span class="round-tabs two">
                        
                    </span> 
                </a>
            </li>
    
            <!-- circular home icon -->
            <li>
                <a uiSrefActive="active" uiSref="address" data-toggle="tab" title="address">
                    <span class="round-tabs three">
                        
                    </span>
                </a>
            </li>
    
            <!-- circular ok icon -->
            <li>
                <a uiSrefActive="active" uiSref="result" data-toggle="tab" title="completed">
                    <span class="round-tabs four">
                        
                    </span>
                </a>
            </li>
                    
        </ul>
        <div class="clearfix"></div>
    </div>
    
    <!-- Close the Splash screen -->
    <script src="content/js/loading-bars.js"></script>

     

    The navbar.component.html uses Bootstrap for quick styling and building responsive layouts. It provides circular tab menus to navigate to different view. To highlight active circular tab menus, we add uiSrefActive directive of UI-Router to them. The active class will be activated if the current state matches the state in uiSref.

  5. We have the following circular icons in tab menu:

    • user
    • tasks
    • home
    • ok

    Clicking Tab Bar

  6. Lines 6-12 assign the circular user icon to the Personal tab. When you click the user icon in the tab menu, the Personal view will be placed in the content area of the form view:

    Clicking Personal Tab

  7. Lines 15-21 assign the circular tasks icon to the Work tab. When you click the circular tasks icon in the tab menu, the Work view will be placed in the content area of the form view:

    Clicking Work Tab

  8. Lines 24-30 assign the circular home icon to the Address tab. When you click the circular home icon in the tab menu, the Address view will be placed in the content area of the form view:

    Clicking Address Tab

  9. Lines 33-39 assign the circular ok icon to the Result tab. When you click the circular ok icon in the tab menu, the Result view will be placed in the content area of the form view:

    Clicking Result Tab

  10. Line 46 stops the “loading bars” animation after finished loading the Navbar view.

Task 10: Create the Data Feature

The Data feature contains the following files:

  • formData.service.ts: is a service responsible for sharing the original input data among the PersonalComponent, WorkComponent, AddressComponent, and ResultComponent
  • formData.model.ts: contains input data

Task 11. Add shared service to the Data feature

  1. Add the formData.service.ts file to the data folder.

  2. Replace the code in this file with the following:

     

    import { Injectable }     from '@angular/core';
    
    import { FormData }       from './formData.model';
    
    @Injectable()
    export class FormDataService {
    
        private formData: FormData = new FormData();
        
        getData(): FormData {
            return this.formData;
        }
    
        setData(formData: FormData) {
            this.formData = formData;
        }
    }

     

    The root module class named AppModule imports the FormDataService as a service. The FormDataService shares its original input data across different components.

Task 12. Add data model to the Data feature

  1. Add the formData.model.ts file to the data folder.

  2. Replace the code in this file with the following:

     

    export class FormData {
        firstName: string = '';
        lastName : string = '';
        email: string = '';
        work: string = 'Code';
        street: string = '';
        city: string = '';
        state: string = '';
        zip: string = '';
    }

     

In the next tutorial, we will build the Personal, Work, Address, and Result Features using UI-Router 1.0 for Angular 2.

The source code for this tutorial series is published on GitHub. Demo application is hosted in Microsoft Azure.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)