├── .editorconfig ├── .gitignore ├── .prettierrc ├── LICENSE.txt ├── README.md ├── angular.json ├── browserslist ├── docs ├── assets │ └── ng-wizard-transparent.png ├── favicon.ico ├── index.html ├── main-es2015.js ├── main-es2015.js.map ├── main-es5.js ├── main-es5.js.map ├── polyfills-es2015.js ├── polyfills-es2015.js.map ├── polyfills-es5.js ├── polyfills-es5.js.map ├── runtime-es2015.js ├── runtime-es2015.js.map ├── runtime-es5.js ├── runtime-es5.js.map ├── styles-es2015.js ├── styles-es2015.js.map ├── styles-es5.js ├── styles-es5.js.map ├── vendor-es2015.js ├── vendor-es2015.js.map ├── vendor-es5.js └── vendor-es5.js.map ├── package-lock.json ├── package.json ├── projects └── ng-wizard │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── ng-wizard-buttons │ │ │ ├── ng-wizard-buttons.component.html │ │ │ ├── ng-wizard-buttons.component.scss │ │ │ ├── ng-wizard-buttons.component.spec.ts │ │ │ └── ng-wizard-buttons.component.ts │ │ ├── ng-wizard-error │ │ │ ├── ng-wizard-error-type.enum.ts │ │ │ ├── ng-wizard-error.component.html │ │ │ ├── ng-wizard-error.component.scss │ │ │ ├── ng-wizard-error.component.spec.ts │ │ │ ├── ng-wizard-error.component.ts │ │ │ └── ng-wizard.error.ts │ │ ├── ng-wizard-navigation │ │ │ ├── ng-wizard-navigation.component.html │ │ │ ├── ng-wizard-navigation.component.scss │ │ │ ├── ng-wizard-navigation.component.spec.ts │ │ │ └── ng-wizard-navigation.component.ts │ │ ├── ng-wizard-options │ │ │ └── ng-wizard-options.interface.ts │ │ ├── ng-wizard-step │ │ │ ├── ng-wizard-step-data.interface.ts │ │ │ ├── ng-wizard-step-options.ts │ │ │ ├── ng-wizard-step.interface.ts │ │ │ └── ng-wizard-step.ts │ │ ├── ng-wizard.component.html │ │ ├── ng-wizard.component.spec.ts │ │ ├── ng-wizard.component.ts │ │ ├── ng-wizard.module.ts │ │ ├── ng-wizard.service.spec.ts │ │ ├── ng-wizard.service.ts │ │ ├── ng-wizard.utils.spec.ts │ │ └── ng-wizard.utils.ts │ ├── public_api.ts │ ├── test.ts │ └── themes │ │ ├── arrows.scss │ │ ├── common.scss │ │ ├── default.scss │ │ ├── dots.scss │ │ └── line.scss │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ └── tslint.json ├── readme-img ├── arrows-theme.png ├── default-theme.png ├── dots-theme.png └── ng-wizard-200.png ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ ├── app.module.ts │ ├── app.service.spec.ts │ ├── app.service.ts │ ├── nested-example │ │ ├── nested-example.component.html │ │ └── nested-example.component.ts │ ├── step1 │ │ ├── step1.component.html │ │ └── step1.component.ts │ ├── step2 │ │ ├── step2.component.html │ │ └── step2.component.ts │ ├── step3 │ │ ├── step3.component.html │ │ └── step3.component.ts │ ├── step4 │ │ ├── step4.component.html │ │ └── step4.component.ts │ └── step5 │ │ ├── step5.component.html │ │ └── step5.component.ts ├── assets │ ├── .gitkeep │ └── ng-wizard-transparent.png ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | /dist 4 | /node_modules 5 | /coverage 6 | .idea 7 | yarn.lock 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120, 4 | "proseWrap": "always", 5 | "semi": true, 6 | "bracketSpacing": true, 7 | "arrowParens": "always", 8 | "parser": "typescript", 9 | "trailingComma": "all" 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jonas Brems 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NgWizard 2 | ![ng-wizard logo](./readme-img/ng-wizard-200.png) 3 | 4 | The NgWizard component is a simple wizard/stepper component for 5 | [Angular](https://angular.io) 9 utilizing 6 | [Angular Routing](https://angular.io/guide/router) for navigation. 7 | 8 | A demo can be found at 9 | [https://cmdap.github.io/ng-wizard/](https://cmdap.github.io/ng-wizard/). 10 | 11 | ## Installation 12 | 1. The NgWizard component is available as an NPM package. To install the 13 | NgWizard package in your Angular project directory run: 14 | ``` 15 | $ npm install @cmdap/ng-wizard 16 | ``` 17 | 2. In your `app.module.ts` add `NgWizardModule` to your imports array. 18 | ``` 19 | import { NgWizardModule } from '@cmdap/ng-wizard'; 20 | 21 | @NgModule({ 22 | declarations: [...], 23 | imports: [ 24 | ..., 25 | NgWizardModule, 26 | ], 27 | providers: [...], 28 | bootstrap: [...] 29 | }) 30 | ``` 31 | 32 | 3. Then add a route for the `NgWizardComponent` to your Angular 33 | router configuration with each step in the wizard as a child route. 34 | 35 | For example, your `app-routing.module.ts` file for a wizard with 2 steps can look like this (import statements hidden): 36 | ```typescript 37 | import { NgWizardComponent } from '@cmdap/ng-wizard'; 38 | 39 | const routes: Routes = [ 40 | { path: '', component: NgWizardComponent, children: [ 41 | { path: 'step-1', component: Step1Component }, 42 | { path: 'step-2', component: Step2Component }, 43 | { path: '**', redirectTo: 'step-1' }, 44 | ], data: { name: 'myWizard' }}, 45 | { path: '**', redirectTo: '' }, 46 | ]; 47 | 48 | @NgModule({ 49 | imports: [RouterModule.forRoot(routes)], 50 | exports: [RouterModule] 51 | }) 52 | export class AppRoutingModule { } 53 | ``` 54 | 55 | 4. Finally, have your step components extend the `NgWizardStep` class or 56 | implement the `NgWizardStepInterface`. 57 | 58 | A minimal step component file can look like this: 59 | ```typescript 60 | import { Component } from '@angular/core'; 61 | import { NgWizardStep } from '@cmdap/ng-wizard'; 62 | 63 | @Component({ 64 | selector: 'app-step1', 65 | templateUrl: './step1.component.html', 66 | }) 67 | export class Step1Component extends NgWizardStep { 68 | constructor() { 69 | super(); 70 | } 71 | } 72 | ``` 73 | 74 | 5. If you want to use the NgWizard's default 75 | [Material icons](https://material.io/tools/icons) in your project you 76 | have to import the 77 | [material icons stylesheet](https://fonts.googleapis.com/icon?family=Material+Icons) 78 | in your project. 79 | For example, add the following link to your `index.html`'s `` tag. 80 | ```html 81 | 82 | ``` 83 | 84 | 85 | ### Wizard options 86 | Custom options can be passed to the NgWizard component via the `data` attribute of the wizard route. 87 | For example: 88 | ```typescript 89 | const wizardConfig = { 90 | name: 'MyWizard', 91 | navBar: { 92 | icons: { 93 | previous: 'cake', 94 | current: 'star', 95 | next: 'pool', 96 | }, 97 | }, 98 | }; 99 | 100 | const routes: Routes = [ 101 | { path: '', component: NgWizardComponent, children: [], data: wizardConfig }, 102 | { path: '**', redirectTo: '' }, 103 | ]; 104 | ``` 105 | The `name` option is the only mandatory option. Every wizard needs to have a unique `name` defined on its route. 106 | 107 | Currently the supported configuration options which can be overwritten are (with their default values): 108 | ```typescript 109 | { 110 | name: '', 111 | navBar: { 112 | icons: { 113 | previous: 'done', 114 | current: 'create', 115 | next: 'lock', 116 | }, 117 | }, 118 | buttons: { 119 | previous: { 120 | label: 'chevron_left Previous', 121 | }, 122 | next: { 123 | label: 'Next chevron_right', 124 | }, 125 | } 126 | } 127 | ``` 128 | 129 | ### Wizard step options 130 | Custom options for a specific step can be passed as the `data` attribute 131 | of the corresponding child route. 132 | For example: 133 | ```typescript 134 | const doneStepOptions = { 135 | icon: 'done_all', 136 | buttons: { 137 | previous: { 138 | hidden: true, 139 | }, 140 | }, 141 | cleanQueryParameters: false, 142 | disableNavigation: true, 143 | }; 144 | 145 | const routes: Routes = [ 146 | { path: '', component: NgWizardComponent, children: [ 147 | { path: 'done', component: Step5Component, data: doneStepOptions }, 148 | ] }, 149 | ]; 150 | ``` 151 | Currently the supported step configuration options which can be 152 | overwritten are: 153 | ```typescript 154 | { 155 | title: string; // By default a human readable version of the path is used 156 | icon: string; // This icon will be used for all stages of the step (previous/current/next) 157 | buttons: { 158 | previous: { 159 | label: string; 160 | hidden: boolean; 161 | }; 162 | next: { 163 | label: string; 164 | hidden: boolean; 165 | }; 166 | }; 167 | cleanQueryParameters: boolean; // Remove all existing parameters present in the route 168 | disableNavigation: boolean; // Disables navigation from the wizard's navigation bar 169 | } 170 | ``` 171 | 172 | ### Hooks 173 | Before navigating, the NgWizard component will call the active step's `wsOnNext` or `wsOnPrevious` method. 174 | Use these methods to save the current state of the step to a service or to perform any other logic you want to execture before leaving the active step. 175 | 176 | When a new step is displayed the `ngOnInit` method will be called by Angular. 177 | Use this method to initialize the step's data and/or check the user's access rights to this step. 178 | 179 | ### Cancel navigation 180 | If your step component's state is invalid return `false` from your 181 | `wsIsValid` method. This will cancel the navigation to the next step but 182 | will allow navigating to previous steps. 183 | 184 | For any other reason you want to cancel the next or 185 | previous navigation make sure your `wsOnNext` and/or `wsOnPrevious` 186 | method returns `false`. 187 | This will cancel the Wizard's navigation. 188 | 189 | _Your step component is responsible for displaying an error message or 190 | other reason why the navigation is cancelled._ 191 | 192 | ### Entry via URL 193 | Since the NgWizard component utilizes Angular's Routing it is possible 194 | for a user to access any step in the wizard via the URL. If the user is not allowed to access a specific step you can check the 195 | conditions and redirect the user in the step component's `ngOnInit` 196 | method. 197 | 198 | ### Themes 199 | The NgWizard component contains 3 themes you can use and extend, or you can write your own style rules. 200 | The themes are based on Dipu Raj's SmartWizard 4 themes and are called `default`, `arrows` and `dots`. 201 | Because this library uses Angular's style encapsulation you must import the theme or your style rules in the root `styles` file of your 202 | Angular project. 203 | 204 | You can use one of the provided themes by importing it in your `styles.scss` file: 205 | ``` 206 | @import './node_modules/@cmdap/ng-wizard/themes/default'; 207 | ``` 208 | 209 | **Default** 210 | ![default theme showcase](./readme-img/default-theme.png) 211 | 212 | **Arrows** 213 | ![default theme showcase](./readme-img/arrows-theme.png) 214 | 215 | **Dots** 216 | ![default theme showcase](./readme-img/dots-theme.png) 217 | 218 | *The provided themes are currently not optimized for responsive designs.* 219 | 220 | ## Changelog 221 | ### [10.0.0] - 2020-10-28 222 | #### Added 223 | - [BREAKING CHANGE] `name` field in `NgWizardOptions`. This field is mandatory for all NgWizard in your project. See [Wizard options](#Wizard-options) 224 | 225 | ## Contributing 226 | If you are willing to contribute to this project you can clone the source code from our [github repository](https://github.com/cmdap/ng-wizard). 227 | ``` 228 | $ git clone https://github.com/cmdap/ng-wizard.git 229 | ``` 230 | You will find a `src` folder containing the NgWizard demo project as seen on [https://cmdap.github.io/ng-wizard](https://cmdap.github.io/ng-wizard) as well as a `projects\ng-wizard` folder containing the source code for the ng-wizard component. 231 | 232 | ### Useful commands 233 | In addition to the default Angular commands some useful NPM scripts have been added to the root `package.json` file. 234 | 235 | | Useful command | Description | 236 | | ------- | ----------- | 237 | | `npm start` | Starts a development server for the **demo project**. The server will start on [http://localhost:4200](http://localhost:4200). | 238 | | `ng build`| Builds the **demo project** to the `docs` folder. This folder will be published as the NgWizard's demo at [https://cmdap.github.io/ng-wizard](https://cmdap.github.io/ng-wizard). | 239 | | `npm test` | Runs the Karma/Jasmine tests for the **ng-wizard component** with code coverage enabled. | 240 | | `npm run lint` | Runs the linter on the **ng-wizard component**'s source code. | 241 | | `npm run build` | Builds the **ng-wizard component**'s source code to an NPM package in the `dist\ng-wizard` folder. Also copies the `README.md` (with assets) and `LICENSE.txt` files as well as the ng-wizard's `themes` folder to that folder. | 242 | | `npm publish dist\ng-wizard --access public` | **Only for project owners**. Publishes the **ng-wizard package** to the NPM repository at [https://www.npmjs.com/package/@cmdap/ng-wizard](https://www.npmjs.com/package/@cmdap/ng-wizard). | 243 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ng-wizard-demo": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "aot": true, 21 | "outputPath": "docs", 22 | "index": "src/index.html", 23 | "main": "src/main.ts", 24 | "polyfills": "src/polyfills.ts", 25 | "tsConfig": "src/tsconfig.app.json", 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | "src/styles.scss" 32 | ], 33 | "scripts": [] 34 | }, 35 | "configurations": { 36 | "production": { 37 | "fileReplacements": [ 38 | { 39 | "replace": "src/environments/environment.ts", 40 | "with": "src/environments/environment.prod.ts" 41 | } 42 | ], 43 | "optimization": true, 44 | "outputHashing": "all", 45 | "sourceMap": false, 46 | "extractCss": true, 47 | "namedChunks": false, 48 | "aot": true, 49 | "extractLicenses": true, 50 | "vendorChunk": false, 51 | "buildOptimizer": true, 52 | "budgets": [ 53 | { 54 | "type": "initial", 55 | "maximumWarning": "2mb", 56 | "maximumError": "5mb" 57 | }, 58 | { 59 | "type": "anyComponentStyle", 60 | "maximumWarning": "6kb" 61 | } 62 | ] 63 | } 64 | } 65 | }, 66 | "serve": { 67 | "builder": "@angular-devkit/build-angular:dev-server", 68 | "options": { 69 | "browserTarget": "ng-wizard-demo:build" 70 | }, 71 | "configurations": { 72 | "production": { 73 | "browserTarget": "ng-wizard-demo:build:production" 74 | } 75 | } 76 | }, 77 | "extract-i18n": { 78 | "builder": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "browserTarget": "ng-wizard-demo:build" 81 | } 82 | }, 83 | "test": { 84 | "builder": "@angular-devkit/build-angular:karma", 85 | "options": { 86 | "main": "src/test.ts", 87 | "polyfills": "src/polyfills.ts", 88 | "tsConfig": "src/tsconfig.spec.json", 89 | "karmaConfig": "src/karma.conf.js", 90 | "styles": [ 91 | "src/styles.scss" 92 | ], 93 | "scripts": [], 94 | "assets": [ 95 | "src/favicon.ico", 96 | "src/assets" 97 | ] 98 | } 99 | }, 100 | "lint": { 101 | "builder": "@angular-devkit/build-angular:tslint", 102 | "options": { 103 | "tsConfig": [ 104 | "src/tsconfig.app.json", 105 | "src/tsconfig.spec.json" 106 | ], 107 | "exclude": [ 108 | "**/node_modules/**" 109 | ] 110 | } 111 | } 112 | } 113 | }, 114 | "ng-wizard": { 115 | "root": "projects/ng-wizard", 116 | "sourceRoot": "projects/ng-wizard/src", 117 | "projectType": "library", 118 | "prefix": "ng", 119 | "architect": { 120 | "build": { 121 | "builder": "@angular-devkit/build-ng-packagr:build", 122 | "options": { 123 | "tsConfig": "projects/ng-wizard/tsconfig.lib.json", 124 | "project": "projects/ng-wizard/ng-package.json" 125 | } 126 | , "configurations": { 127 | "production": { 128 | "tsConfig": "projects/ng-wizard/tsconfig.lib.prod.json" 129 | } 130 | } 131 | }, 132 | "test": { 133 | "builder": "@angular-devkit/build-angular:karma", 134 | "options": { 135 | "main": "projects/ng-wizard/src/test.ts", 136 | "tsConfig": "projects/ng-wizard/tsconfig.spec.json", 137 | "karmaConfig": "projects/ng-wizard/karma.conf.js" 138 | } 139 | }, 140 | "lint": { 141 | "builder": "@angular-devkit/build-angular:tslint", 142 | "options": { 143 | "tsConfig": [ 144 | "projects/ng-wizard/tsconfig.lib.json", 145 | "projects/ng-wizard/tsconfig.spec.json" 146 | ], 147 | "exclude": [ 148 | "**/node_modules/**" 149 | ] 150 | } 151 | } 152 | } 153 | } 154 | }, 155 | "defaultProject": "ng-wizard-demo" 156 | } 157 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /docs/assets/ng-wizard-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/docs/assets/ng-wizard-transparent.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NgWizard 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/runtime-es2015.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // install a JSONP callback for chunk loading 3 | /******/ function webpackJsonpCallback(data) { 4 | /******/ var chunkIds = data[0]; 5 | /******/ var moreModules = data[1]; 6 | /******/ var executeModules = data[2]; 7 | /******/ 8 | /******/ // add "moreModules" to the modules object, 9 | /******/ // then flag all "chunkIds" as loaded and fire callback 10 | /******/ var moduleId, chunkId, i = 0, resolves = []; 11 | /******/ for(;i < chunkIds.length; i++) { 12 | /******/ chunkId = chunkIds[i]; 13 | /******/ if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) { 14 | /******/ resolves.push(installedChunks[chunkId][0]); 15 | /******/ } 16 | /******/ installedChunks[chunkId] = 0; 17 | /******/ } 18 | /******/ for(moduleId in moreModules) { 19 | /******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { 20 | /******/ modules[moduleId] = moreModules[moduleId]; 21 | /******/ } 22 | /******/ } 23 | /******/ if(parentJsonpFunction) parentJsonpFunction(data); 24 | /******/ 25 | /******/ while(resolves.length) { 26 | /******/ resolves.shift()(); 27 | /******/ } 28 | /******/ 29 | /******/ // add entry modules from loaded chunk to deferred list 30 | /******/ deferredModules.push.apply(deferredModules, executeModules || []); 31 | /******/ 32 | /******/ // run deferred modules when all chunks ready 33 | /******/ return checkDeferredModules(); 34 | /******/ }; 35 | /******/ function checkDeferredModules() { 36 | /******/ var result; 37 | /******/ for(var i = 0; i < deferredModules.length; i++) { 38 | /******/ var deferredModule = deferredModules[i]; 39 | /******/ var fulfilled = true; 40 | /******/ for(var j = 1; j < deferredModule.length; j++) { 41 | /******/ var depId = deferredModule[j]; 42 | /******/ if(installedChunks[depId] !== 0) fulfilled = false; 43 | /******/ } 44 | /******/ if(fulfilled) { 45 | /******/ deferredModules.splice(i--, 1); 46 | /******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); 47 | /******/ } 48 | /******/ } 49 | /******/ 50 | /******/ return result; 51 | /******/ } 52 | /******/ 53 | /******/ // The module cache 54 | /******/ var installedModules = {}; 55 | /******/ 56 | /******/ // object to store loaded and loading chunks 57 | /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 58 | /******/ // Promise = chunk loading, 0 = chunk loaded 59 | /******/ var installedChunks = { 60 | /******/ "runtime": 0 61 | /******/ }; 62 | /******/ 63 | /******/ var deferredModules = []; 64 | /******/ 65 | /******/ // The require function 66 | /******/ function __webpack_require__(moduleId) { 67 | /******/ 68 | /******/ // Check if module is in cache 69 | /******/ if(installedModules[moduleId]) { 70 | /******/ return installedModules[moduleId].exports; 71 | /******/ } 72 | /******/ // Create a new module (and put it into the cache) 73 | /******/ var module = installedModules[moduleId] = { 74 | /******/ i: moduleId, 75 | /******/ l: false, 76 | /******/ exports: {} 77 | /******/ }; 78 | /******/ 79 | /******/ // Execute the module function 80 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 81 | /******/ 82 | /******/ // Flag the module as loaded 83 | /******/ module.l = true; 84 | /******/ 85 | /******/ // Return the exports of the module 86 | /******/ return module.exports; 87 | /******/ } 88 | /******/ 89 | /******/ 90 | /******/ // expose the modules object (__webpack_modules__) 91 | /******/ __webpack_require__.m = modules; 92 | /******/ 93 | /******/ // expose the module cache 94 | /******/ __webpack_require__.c = installedModules; 95 | /******/ 96 | /******/ // define getter function for harmony exports 97 | /******/ __webpack_require__.d = function(exports, name, getter) { 98 | /******/ if(!__webpack_require__.o(exports, name)) { 99 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 100 | /******/ } 101 | /******/ }; 102 | /******/ 103 | /******/ // define __esModule on exports 104 | /******/ __webpack_require__.r = function(exports) { 105 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 106 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 107 | /******/ } 108 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 109 | /******/ }; 110 | /******/ 111 | /******/ // create a fake namespace object 112 | /******/ // mode & 1: value is a module id, require it 113 | /******/ // mode & 2: merge all properties of value into the ns 114 | /******/ // mode & 4: return value when already ns object 115 | /******/ // mode & 8|1: behave like require 116 | /******/ __webpack_require__.t = function(value, mode) { 117 | /******/ if(mode & 1) value = __webpack_require__(value); 118 | /******/ if(mode & 8) return value; 119 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 120 | /******/ var ns = Object.create(null); 121 | /******/ __webpack_require__.r(ns); 122 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 123 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 124 | /******/ return ns; 125 | /******/ }; 126 | /******/ 127 | /******/ // getDefaultExport function for compatibility with non-harmony modules 128 | /******/ __webpack_require__.n = function(module) { 129 | /******/ var getter = module && module.__esModule ? 130 | /******/ function getDefault() { return module['default']; } : 131 | /******/ function getModuleExports() { return module; }; 132 | /******/ __webpack_require__.d(getter, 'a', getter); 133 | /******/ return getter; 134 | /******/ }; 135 | /******/ 136 | /******/ // Object.prototype.hasOwnProperty.call 137 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 138 | /******/ 139 | /******/ // __webpack_public_path__ 140 | /******/ __webpack_require__.p = ""; 141 | /******/ 142 | /******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; 143 | /******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); 144 | /******/ jsonpArray.push = webpackJsonpCallback; 145 | /******/ jsonpArray = jsonpArray.slice(); 146 | /******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); 147 | /******/ var parentJsonpFunction = oldJsonpFunction; 148 | /******/ 149 | /******/ 150 | /******/ // run deferred modules from other chunks 151 | /******/ checkDeferredModules(); 152 | /******/ }) 153 | /************************************************************************/ 154 | /******/ ([]); 155 | //# sourceMappingURL=runtime-es2015.js.map -------------------------------------------------------------------------------- /docs/runtime-es2015.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack/bootstrap"],"names":[],"mappings":";QAAA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;QACA,QAAQ,oBAAoB;QAC5B;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA,iBAAiB,4BAA4B;QAC7C;QACA;QACA,kBAAkB,2BAA2B;QAC7C;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;;QAEA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;;;QAGA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA,0CAA0C,gCAAgC;QAC1E;QACA;;QAEA;QACA;QACA;QACA,wDAAwD,kBAAkB;QAC1E;QACA,iDAAiD,cAAc;QAC/D;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,yCAAyC,iCAAiC;QAC1E,gHAAgH,mBAAmB,EAAE;QACrI;QACA;;QAEA;QACA;QACA;QACA,2BAA2B,0BAA0B,EAAE;QACvD,iCAAiC,eAAe;QAChD;QACA;QACA;;QAEA;QACA,sDAAsD,+DAA+D;;QAErH;QACA;;QAEA;QACA;QACA;QACA;QACA,gBAAgB,uBAAuB;QACvC;;;QAGA;QACA","file":"runtime-es2015.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t\"runtime\": 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \tvar jsonpArray = window[\"webpackJsonp\"] = window[\"webpackJsonp\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":"webpack:///"} -------------------------------------------------------------------------------- /docs/runtime-es5.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // install a JSONP callback for chunk loading 3 | /******/ function webpackJsonpCallback(data) { 4 | /******/ var chunkIds = data[0]; 5 | /******/ var moreModules = data[1]; 6 | /******/ var executeModules = data[2]; 7 | /******/ 8 | /******/ // add "moreModules" to the modules object, 9 | /******/ // then flag all "chunkIds" as loaded and fire callback 10 | /******/ var moduleId, chunkId, i = 0, resolves = []; 11 | /******/ for(;i < chunkIds.length; i++) { 12 | /******/ chunkId = chunkIds[i]; 13 | /******/ if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) { 14 | /******/ resolves.push(installedChunks[chunkId][0]); 15 | /******/ } 16 | /******/ installedChunks[chunkId] = 0; 17 | /******/ } 18 | /******/ for(moduleId in moreModules) { 19 | /******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { 20 | /******/ modules[moduleId] = moreModules[moduleId]; 21 | /******/ } 22 | /******/ } 23 | /******/ if(parentJsonpFunction) parentJsonpFunction(data); 24 | /******/ 25 | /******/ while(resolves.length) { 26 | /******/ resolves.shift()(); 27 | /******/ } 28 | /******/ 29 | /******/ // add entry modules from loaded chunk to deferred list 30 | /******/ deferredModules.push.apply(deferredModules, executeModules || []); 31 | /******/ 32 | /******/ // run deferred modules when all chunks ready 33 | /******/ return checkDeferredModules(); 34 | /******/ }; 35 | /******/ function checkDeferredModules() { 36 | /******/ var result; 37 | /******/ for(var i = 0; i < deferredModules.length; i++) { 38 | /******/ var deferredModule = deferredModules[i]; 39 | /******/ var fulfilled = true; 40 | /******/ for(var j = 1; j < deferredModule.length; j++) { 41 | /******/ var depId = deferredModule[j]; 42 | /******/ if(installedChunks[depId] !== 0) fulfilled = false; 43 | /******/ } 44 | /******/ if(fulfilled) { 45 | /******/ deferredModules.splice(i--, 1); 46 | /******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); 47 | /******/ } 48 | /******/ } 49 | /******/ 50 | /******/ return result; 51 | /******/ } 52 | /******/ 53 | /******/ // The module cache 54 | /******/ var installedModules = {}; 55 | /******/ 56 | /******/ // object to store loaded and loading chunks 57 | /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 58 | /******/ // Promise = chunk loading, 0 = chunk loaded 59 | /******/ var installedChunks = { 60 | /******/ "runtime": 0 61 | /******/ }; 62 | /******/ 63 | /******/ var deferredModules = []; 64 | /******/ 65 | /******/ // The require function 66 | /******/ function __webpack_require__(moduleId) { 67 | /******/ 68 | /******/ // Check if module is in cache 69 | /******/ if(installedModules[moduleId]) { 70 | /******/ return installedModules[moduleId].exports; 71 | /******/ } 72 | /******/ // Create a new module (and put it into the cache) 73 | /******/ var module = installedModules[moduleId] = { 74 | /******/ i: moduleId, 75 | /******/ l: false, 76 | /******/ exports: {} 77 | /******/ }; 78 | /******/ 79 | /******/ // Execute the module function 80 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 81 | /******/ 82 | /******/ // Flag the module as loaded 83 | /******/ module.l = true; 84 | /******/ 85 | /******/ // Return the exports of the module 86 | /******/ return module.exports; 87 | /******/ } 88 | /******/ 89 | /******/ 90 | /******/ // expose the modules object (__webpack_modules__) 91 | /******/ __webpack_require__.m = modules; 92 | /******/ 93 | /******/ // expose the module cache 94 | /******/ __webpack_require__.c = installedModules; 95 | /******/ 96 | /******/ // define getter function for harmony exports 97 | /******/ __webpack_require__.d = function(exports, name, getter) { 98 | /******/ if(!__webpack_require__.o(exports, name)) { 99 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 100 | /******/ } 101 | /******/ }; 102 | /******/ 103 | /******/ // define __esModule on exports 104 | /******/ __webpack_require__.r = function(exports) { 105 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 106 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 107 | /******/ } 108 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 109 | /******/ }; 110 | /******/ 111 | /******/ // create a fake namespace object 112 | /******/ // mode & 1: value is a module id, require it 113 | /******/ // mode & 2: merge all properties of value into the ns 114 | /******/ // mode & 4: return value when already ns object 115 | /******/ // mode & 8|1: behave like require 116 | /******/ __webpack_require__.t = function(value, mode) { 117 | /******/ if(mode & 1) value = __webpack_require__(value); 118 | /******/ if(mode & 8) return value; 119 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 120 | /******/ var ns = Object.create(null); 121 | /******/ __webpack_require__.r(ns); 122 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 123 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 124 | /******/ return ns; 125 | /******/ }; 126 | /******/ 127 | /******/ // getDefaultExport function for compatibility with non-harmony modules 128 | /******/ __webpack_require__.n = function(module) { 129 | /******/ var getter = module && module.__esModule ? 130 | /******/ function getDefault() { return module['default']; } : 131 | /******/ function getModuleExports() { return module; }; 132 | /******/ __webpack_require__.d(getter, 'a', getter); 133 | /******/ return getter; 134 | /******/ }; 135 | /******/ 136 | /******/ // Object.prototype.hasOwnProperty.call 137 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 138 | /******/ 139 | /******/ // __webpack_public_path__ 140 | /******/ __webpack_require__.p = ""; 141 | /******/ 142 | /******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; 143 | /******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); 144 | /******/ jsonpArray.push = webpackJsonpCallback; 145 | /******/ jsonpArray = jsonpArray.slice(); 146 | /******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); 147 | /******/ var parentJsonpFunction = oldJsonpFunction; 148 | /******/ 149 | /******/ 150 | /******/ // run deferred modules from other chunks 151 | /******/ checkDeferredModules(); 152 | /******/ }) 153 | /************************************************************************/ 154 | /******/ ([]); 155 | //# sourceMappingURL=runtime-es5.js.map -------------------------------------------------------------------------------- /docs/runtime-es5.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack/bootstrap"],"names":[],"mappings":";QAAA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;QACA,QAAQ,oBAAoB;QAC5B;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA,iBAAiB,4BAA4B;QAC7C;QACA;QACA,kBAAkB,2BAA2B;QAC7C;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;;QAEA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;;;QAGA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA,0CAA0C,gCAAgC;QAC1E;QACA;;QAEA;QACA;QACA;QACA,wDAAwD,kBAAkB;QAC1E;QACA,iDAAiD,cAAc;QAC/D;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,yCAAyC,iCAAiC;QAC1E,gHAAgH,mBAAmB,EAAE;QACrI;QACA;;QAEA;QACA;QACA;QACA,2BAA2B,0BAA0B,EAAE;QACvD,iCAAiC,eAAe;QAChD;QACA;QACA;;QAEA;QACA,sDAAsD,+DAA+D;;QAErH;QACA;;QAEA;QACA;QACA;QACA;QACA,gBAAgB,uBAAuB;QACvC;;;QAGA;QACA","file":"runtime-es5.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t\"runtime\": 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \tvar jsonpArray = window[\"webpackJsonp\"] = window[\"webpackJsonp\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":"webpack:///"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-wizard-demo", 3 | "version": "2.0.2", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build ng-wizard --prod && npm run copyFiles", 8 | "copyFiles": "cpy README.md dist/ng-wizard && cpy readme-img/*.png dist/ng-wizard/readme-img && cpy LICENSE.txt dist/ng-wizard && npm run copyThemes", 9 | "copyThemes": "cpy projects/ng-wizard/src/themes/*.scss dist/ng-wizard/themes", 10 | "test": "ng test ng-wizard --code-coverage", 11 | "lint": "ng lint ng-wizard" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "~9.0.5", 16 | "@angular/common": "~9.0.5", 17 | "@angular/compiler": "~9.0.5", 18 | "@angular/core": "~9.0.5", 19 | "@angular/forms": "~9.0.5", 20 | "@angular/platform-browser": "~9.0.5", 21 | "@angular/platform-browser-dynamic": "~9.0.5", 22 | "@angular/router": "~9.0.5", 23 | "core-js": "^3.6.4", 24 | "rxjs": "~6.5.2", 25 | "tslib": "^1.10.0", 26 | "zone.js": "~0.10.2" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "0.901.9", 30 | "@angular-devkit/build-ng-packagr": "~0.900.5", 31 | "@angular/cli": "^9.0.5", 32 | "@angular/compiler-cli": "~9.0.5", 33 | "@angular/language-service": "~9.0.5", 34 | "@types/jasmine": "~2.8.8", 35 | "@types/jasminewd2": "~2.0.3", 36 | "@types/node": "^12.11.1", 37 | "codelyzer": "^6.0.2", 38 | "cpy-cli": "^3.1.1", 39 | "jasmine-core": "^3.7.1", 40 | "jasmine-spec-reporter": "^7.0.0", 41 | "karma": "^6.3.2", 42 | "karma-chrome-launcher": "^3.1.0", 43 | "karma-coverage-istanbul-reporter": "^3.0.3", 44 | "karma-jasmine": "^4.0.1", 45 | "karma-jasmine-html-reporter": "^1.6.0", 46 | "ng-packagr": "^11.2.4", 47 | "prettier": "^2.2.1", 48 | "protractor": "^7.0.0", 49 | "ts-node": "^9.1.1", 50 | "tslint": "^6.1.3", 51 | "typescript": "~3.7.5" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /projects/ng-wizard/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/ng-wizard/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ng-wizard", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/ng-wizard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cmdap/ng-wizard", 3 | "version": "10.1.0", 4 | "description": "A simple wizard/stepper component for Angular 9 utilizing Angular Routing for navigation.", 5 | "keywords": [ 6 | "Angular", 7 | "Angular 9", 8 | "Wizard", 9 | "Stepper", 10 | "Router" 11 | ], 12 | "bugs": { 13 | "url": "https://github.com/cmdap/ng-wizard/issues", 14 | "email": "jonasbrems@gmail.com" 15 | }, 16 | "license": "MIT", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/cmdap/ng-wizard.git" 20 | }, 21 | "author": "Jonas Brems", 22 | "contributors": [], 23 | "private": false, 24 | "peerDependencies": { 25 | "@angular/common": "~9.0.5", 26 | "@angular/core": "~9.0.5", 27 | "@angular/router": "~9.0.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-buttons/ng-wizard-buttons.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 10 | 17 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-buttons/ng-wizard-buttons.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/projects/ng-wizard/src/lib/ng-wizard-buttons/ng-wizard-buttons.component.scss -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-buttons/ng-wizard-buttons.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed, tick } from '@angular/core/testing'; 2 | import { Component } from '@angular/core'; 3 | import { Router } from '@angular/router'; 4 | 5 | import { NgWizardButtonsComponent } from './ng-wizard-buttons.component'; 6 | import { NgWizardService } from '../ng-wizard.service'; 7 | import { RouterTestingModule } from '@angular/router/testing'; 8 | import { NgWizardStepData } from '../ng-wizard-step/ng-wizard-step-data.interface'; 9 | import { Subject } from 'rxjs'; 10 | import { getDefaultWizardOptions } from '../ng-wizard.utils'; 11 | 12 | @Component({ 13 | selector: 'dummy-component', 14 | template: `
dummy
`, 15 | }) 16 | export class DummyComponent { 17 | } 18 | 19 | describe('NgWizardButtonsComponent', () => { 20 | let component: NgWizardButtonsComponent; 21 | let fixture: ComponentFixture; 22 | let element; 23 | 24 | let service: NgWizardService; 25 | const currentStepDataSubject = new Subject(); 26 | 27 | beforeEach(async(() => { 28 | TestBed.configureTestingModule({ 29 | declarations: [NgWizardButtonsComponent, DummyComponent], 30 | providers: [NgWizardService], 31 | imports: [RouterTestingModule.withRoutes([ 32 | { path: 'extra_path', component: DummyComponent } 33 | ])], 34 | }).compileComponents(); 35 | })); 36 | 37 | beforeEach(() => { 38 | fixture = TestBed.createComponent(NgWizardButtonsComponent); 39 | component = fixture.componentInstance; 40 | element = fixture.nativeElement; 41 | 42 | service = TestBed.get(NgWizardService); 43 | spyOn(service, 'getCurrentStepDataAsObservable').and.returnValue(currentStepDataSubject.asObservable()); 44 | spyOn(service, 'navigateToNextStep'); 45 | spyOn(service, 'navigateToPreviousStep'); 46 | service.wizardOptions = getDefaultWizardOptions(); 47 | 48 | fixture.detectChanges(); 49 | }); 50 | 51 | it('should show no buttons if there is no current step data', () => { 52 | expect(element.querySelector('.ng-wizard-buttons')).toBeNull(); 53 | }); 54 | 55 | it('should show the previous and next button if the current step data has a defined previousStep and nextStep', () => { 56 | currentStepDataSubject.next({ previousStep: 'prev', nextStep: 'nxt', options: {} } as NgWizardStepData); 57 | fixture.detectChanges(); 58 | expect(element.querySelector('.ng-wizard-buttons').children.length).toBe(2); 59 | expect(element.querySelectorAll('.ng-wizard-buttons button')[0].classList).toContain('ng-wizard-button-previous'); 60 | expect(element.querySelectorAll('.ng-wizard-buttons button')[1].classList).toContain('ng-wizard-button-next'); 61 | }); 62 | 63 | it('should show only the previous button if the current step data has a defined previousStep but undefined nextStep', () => { 64 | currentStepDataSubject.next({ previousStep: 'prev', nextStep: undefined, options: {} } as NgWizardStepData); 65 | fixture.detectChanges(); 66 | expect(element.querySelector('.ng-wizard-buttons').children.length).toBe(1); 67 | expect(element.querySelectorAll('.ng-wizard-buttons button')[0].classList).toContain('ng-wizard-button-previous'); 68 | }); 69 | 70 | it('should show only the next button if the current step data has a defined nextStep but undefined previousStep', () => { 71 | currentStepDataSubject.next({ previousStep: undefined, nextStep: 'next', options: {} } as NgWizardStepData); 72 | fixture.detectChanges(); 73 | expect(element.querySelector('.ng-wizard-buttons').children.length).toBe(1); 74 | expect(element.querySelectorAll('.ng-wizard-buttons button')[0].classList).toContain('ng-wizard-button-next'); 75 | }); 76 | 77 | it('should show no buttons if the current step data has an undefined previousStep and nextStep', () => { 78 | currentStepDataSubject.next({ 79 | previousStep: undefined, 80 | nextStep: undefined, 81 | options: {}, 82 | } as NgWizardStepData); 83 | fixture.detectChanges(); 84 | expect(element.querySelector('.ng-wizard-buttons').children.length).toBe(0); 85 | }); 86 | 87 | it("should call the service's navigateToNextStep() method when the next button is clicked", () => { 88 | currentStepDataSubject.next({ previousStep: 'prev', nextStep: 'next', options: {} } as NgWizardStepData); 89 | fixture.detectChanges(); 90 | element.querySelector('.ng-wizard-button-next').click(); 91 | expect(service.navigateToNextStep).toHaveBeenCalled(); 92 | }); 93 | 94 | it("should call the service's navigateToPreviousStep() method when the previous button is clicked", () => { 95 | currentStepDataSubject.next({ previousStep: 'prev', nextStep: 'next', options: {} } as NgWizardStepData); 96 | fixture.detectChanges(); 97 | element.querySelector('.ng-wizard-button-previous').click(); 98 | expect(service.navigateToPreviousStep).toHaveBeenCalled(); 99 | }); 100 | 101 | it('should show the custom button labels when defined in the wizard options', () => { 102 | currentStepDataSubject.next({ previousStep: 'prev', nextStep: 'next', options: {} } as NgWizardStepData); 103 | component.wizardOptions = { 104 | ...getDefaultWizardOptions(), 105 | ...{ 106 | buttons: { 107 | previous: { label: 'PREVIOUS' }, 108 | next: { label: 'NEXT' }, 109 | }, 110 | }, 111 | }; 112 | fixture.detectChanges(); 113 | expect(element.querySelector('.ng-wizard-button-previous .ng-wizard-button-label').innerHTML).toBe('PREVIOUS'); 114 | expect(element.querySelector('.ng-wizard-button-next .ng-wizard-button-label').innerHTML).toBe('NEXT'); 115 | }); 116 | 117 | it('should show the custom button labels when defined in the wizard step options', () => { 118 | currentStepDataSubject.next({ 119 | previousStep: 'prev', 120 | nextStep: 'next', 121 | options: { 122 | buttons: { 123 | previous: { 124 | label: '🔙 PREV', 125 | }, 126 | next: { 127 | label: 'NXT ➡', 128 | }, 129 | }, 130 | }, 131 | } as NgWizardStepData); 132 | component.wizardOptions = getDefaultWizardOptions(); 133 | fixture.detectChanges(); 134 | expect(element.querySelector('.ng-wizard-button-previous .ng-wizard-button-label').innerHTML).toBe('🔙 PREV'); 135 | expect(element.querySelector('.ng-wizard-button-next .ng-wizard-button-label').innerHTML).toBe('NXT ➡'); 136 | }); 137 | 138 | it('should hide the previous and next buttons if defined in the wizard step options', () => { 139 | currentStepDataSubject.next({ 140 | previousStep: 'prev', 141 | nextStep: 'next', 142 | options: { 143 | buttons: { 144 | previous: { 145 | hidden: true, 146 | }, 147 | next: { 148 | hidden: true, 149 | }, 150 | }, 151 | }, 152 | } as NgWizardStepData); 153 | component.wizardOptions = getDefaultWizardOptions(); 154 | fixture.detectChanges(); 155 | expect(element.querySelector('.ng-wizard-button-previous')).toBeNull(); 156 | expect(element.querySelector('.ng-wizard-button-next')).toBeNull(); 157 | }); 158 | 159 | it('should show the extra button labels when defined in the wizard step options', () => { 160 | currentStepDataSubject.next({ 161 | previousStep: 'prev', 162 | nextStep: 'next', 163 | options: { 164 | buttons: { 165 | extra: { 166 | label: 'EXTRA', 167 | path: '/extra_path', 168 | } 169 | }, 170 | }, 171 | } as NgWizardStepData); 172 | component.wizardOptions = getDefaultWizardOptions(); 173 | fixture.detectChanges(); 174 | expect(element.querySelector('.ng-wizard-button-extra .ng-wizard-button-label').innerHTML).toBe('EXTRA'); 175 | }); 176 | 177 | it('should go to the given path when the extra button is pressed', () => { 178 | const path = '/extra_path'; 179 | currentStepDataSubject.next({ 180 | previousStep: 'prev', 181 | nextStep: 'next', 182 | options: { 183 | buttons: { 184 | extra: { 185 | label: 'EXTRA', 186 | path, 187 | } 188 | }, 189 | }, 190 | } as NgWizardStepData); 191 | component.wizardOptions = getDefaultWizardOptions(); 192 | const router = TestBed.get(Router); 193 | spyOn(router, 'navigate'); 194 | fixture.detectChanges(); 195 | const button = element.querySelector('.ng-wizard-button-extra'); 196 | button.click(); 197 | expect(router.navigate).toHaveBeenCalledWith([ path ], Object({ queryParamsHandling: 'merge' })); 198 | }); 199 | 200 | }); 201 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-buttons/ng-wizard-buttons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgWizardService } from '../ng-wizard.service'; 3 | import { NgWizardStepData } from '../ng-wizard-step/ng-wizard-step-data.interface'; 4 | import { Observable } from 'rxjs'; 5 | import { NgWizardOptions } from '../ng-wizard-options/ng-wizard-options.interface'; 6 | 7 | @Component({ 8 | selector: 'ng-wizard-buttons', 9 | templateUrl: './ng-wizard-buttons.component.html', 10 | styleUrls: ['./ng-wizard-buttons.component.scss'], 11 | }) 12 | export class NgWizardButtonsComponent implements OnInit { 13 | public currentStepData$: Observable; 14 | 15 | public wizardOptions: NgWizardOptions; 16 | 17 | constructor(private service: NgWizardService) { } 18 | 19 | ngOnInit() { 20 | this.currentStepData$ = this.service.getCurrentStepDataAsObservable(); 21 | this.wizardOptions = this.service.wizardOptions; 22 | } 23 | 24 | goToNextStep() { 25 | this.service.navigateToNextStep(); 26 | } 27 | 28 | goToPreviousStep() { 29 | this.service.navigateToPreviousStep(); 30 | } 31 | 32 | goToPath(path: string) { 33 | this.service.navigateToPath(path); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-error/ng-wizard-error-type.enum.ts: -------------------------------------------------------------------------------- 1 | // TODO: evaluate if this is useful 2 | export enum NgWizardErrorType { 3 | NO_WIZARD_ROUTE, 4 | NO_CHILD_ROUTES, 5 | NO_WS_INTERFACE, 6 | } 7 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-error/ng-wizard-error.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | No route configuration for the {{ error.wizardComponentName }} found.
8 | Add a route for the {{ error.wizardComponentName }} to your AppRoutingModule.
9 |
10 | const routes: Routes = [
11 |     { path: '', component: {{ error.wizardComponentName }} },
12 | ];
13 | 
14 | @NgModule({
15 |   imports: [RouterModule.forRoot(routes)],
16 |   exports: [RouterModule]
17 | })
18 | export class AppRoutingModule { }
19 |
20 | 21 | 22 | 23 | 24 | No child routes for the {{ error.wizardComponentName }} found.
25 | Add at least one child route for the {{ error.wizardComponentName }} to your AppRoutingModule.
26 |
27 | { path: '{{ error.wizardPath }}', component: {{ error.wizardComponentName }}, children: [
28 |     { path: 'step1', component: Step1Component },
29 |     { path: '**', redirectTo: 'step1' },
30 | ] },
31 |
32 | 33 | 34 | 35 | 36 | The {{ error.stepComponentName}} does not extend the NgWizardStep class or implement the 37 | NgWizardStepInterface.
38 | Extend the NgWizardStep class in your {{ error.stepComponentName}} or implement the 39 | NgWizardStepInterface.
40 |
41 | @NgComponent(...)
42 | export class {{ error.stepComponentName }} extends NgWizardStep {
43 |     constructor() {
44 |         super();
45 |     }
46 | }

47 | or
48 |
49 | @NgComponent(...)
50 | export class {{ error.stepComponentName }} implements NgWizardStepInterface {
51 |     wsIsValid() {
52 |         return true;
53 |     }
54 |     wsOnNext() { }
55 |     wsOnPrevious() { }
56 | }
57 |
58 | 59 |
60 |
61 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-error/ng-wizard-error.component.scss: -------------------------------------------------------------------------------- 1 | $error-red: #D71117; 2 | 3 | .ng-wizard-error { 4 | border: solid 1px $error-red; 5 | border-radius: 5px; 6 | 7 | color: $error-red; 8 | padding: 10px 10px 0 10px; 9 | margin-top: 10px; 10 | 11 | .ng-wizard-error-message pre { 12 | display: inline-block; 13 | border-radius: 3px; 14 | background-color: rgba(218,215,197,.4); 15 | color: #4D4D4D; 16 | padding: 15px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-error/ng-wizard-error.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NgWizardErrorComponent } from './ng-wizard-error.component'; 4 | import { CommonModule } from '@angular/common'; 5 | import { NoChildRoutes, NoWizardRoute, NoWsInterface } from './ng-wizard.error'; 6 | 7 | describe('NgWizardErrorComponent', () => { 8 | let component: NgWizardErrorComponent; 9 | let fixture: ComponentFixture; 10 | let element; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [NgWizardErrorComponent], 15 | imports: [CommonModule], 16 | }).compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(NgWizardErrorComponent); 21 | component = fixture.componentInstance; 22 | element = fixture.nativeElement; 23 | 24 | component.error = new NoWizardRoute('TestComponent'); 25 | fixture.detectChanges(); 26 | }); 27 | 28 | it('should show nothing if there is no error', () => { 29 | component.error = undefined; 30 | fixture.detectChanges(); 31 | expect(element.querySelector('.ng-wizard-error')).toBeNull(); 32 | }); 33 | 34 | it('should show the no wizard route error message when there is a NO_WIZARD_ROUTE error', () => { 35 | component.error = new NoWizardRoute(''); 36 | fixture.detectChanges(); 37 | expect(element.querySelector('.ng-wizard-error-message').children.length).toBe(1); 38 | expect(element.querySelector('.ng-wizard-error-message').children[0].classList).toContain('no-wizard-route'); 39 | }); 40 | 41 | it('should show the no child routes error message when there is a NO_CHILD_ROUTES error', () => { 42 | component.error = new NoChildRoutes('', ''); 43 | fixture.detectChanges(); 44 | expect(element.querySelector('.ng-wizard-error-message').children.length).toBe(1); 45 | expect(element.querySelector('.ng-wizard-error-message').children[0].classList).toContain('no-child-routes'); 46 | }); 47 | 48 | it('should show the no ws interface error message when there is a NO_WS_INTERFACE error', () => { 49 | component.error = new NoWsInterface(''); 50 | fixture.detectChanges(); 51 | expect(element.querySelector('.ng-wizard-error-message').children.length).toBe(1); 52 | expect(element.querySelector('.ng-wizard-error-message').children[0].classList).toContain('no-ws-interface'); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-error/ng-wizard-error.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { NgWizardErrorType } from './ng-wizard-error-type.enum'; 3 | import { NgWizardError } from './ng-wizard.error'; 4 | 5 | @Component({ 6 | selector: 'ng-wizard-error', 7 | templateUrl: './ng-wizard-error.component.html', 8 | styleUrls: ['./ng-wizard-error.component.scss'] 9 | }) 10 | export class NgWizardErrorComponent { 11 | @Input() public error: NgWizardError; 12 | 13 | public NgWizardErrorType = NgWizardErrorType; 14 | } 15 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-error/ng-wizard.error.ts: -------------------------------------------------------------------------------- 1 | import { NgWizardErrorType } from './ng-wizard-error-type.enum'; 2 | 3 | export abstract class NgWizardError extends Error { 4 | public type: NgWizardErrorType; 5 | 6 | public wizardComponentName = ''; 7 | public wizardPath = ''; 8 | public stepComponentName = ''; 9 | 10 | protected constructor(type: NgWizardErrorType, message: string) { 11 | super(message); 12 | this.type = type; 13 | } 14 | } 15 | 16 | export class NoWizardRoute extends NgWizardError { 17 | constructor(public wizardComponentName: string) { 18 | super( 19 | NgWizardErrorType.NO_WIZARD_ROUTE, 20 | `No route configuration for the ${wizardComponentName} found.`, 21 | ); 22 | } 23 | } 24 | 25 | export class NoChildRoutes extends NgWizardError { 26 | constructor(public wizardComponentName: string, public wizardPath: string) { 27 | super( 28 | NgWizardErrorType.NO_CHILD_ROUTES, 29 | `No child routes for the ${wizardComponentName} found.`, 30 | ); 31 | } 32 | } 33 | 34 | export class NoWsInterface extends NgWizardError { 35 | constructor(public stepComponentName: string) { 36 | super( 37 | NgWizardErrorType.NO_WS_INTERFACE, 38 | `The ${stepComponentName} does not extend the NgWizardStep class or implement the NgWizardStepInterface.`, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-navigation/ng-wizard-navigation.component.html: -------------------------------------------------------------------------------- 1 |
2 | 35 |
36 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-navigation/ng-wizard-navigation.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/projects/ng-wizard/src/lib/ng-wizard-navigation/ng-wizard-navigation.component.scss -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-navigation/ng-wizard-navigation.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NgWizardNavigationComponent } from './ng-wizard-navigation.component'; 4 | import { NgWizardService } from '../ng-wizard.service'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | import { NgWizardStepData } from '../ng-wizard-step/ng-wizard-step-data.interface'; 7 | import { Observable } from 'rxjs'; 8 | import { getDefaultWizardOptions } from '../ng-wizard.utils'; 9 | 10 | describe('NgWizardNavigationComponent', () => { 11 | const stepData: NgWizardStepData[] = [ 12 | { order: 1, options: { title: 'Step 1' } } as NgWizardStepData, 13 | { order: 2, options: { title: 'Step 2' } } as NgWizardStepData, 14 | { order: 3, options: { title: 'Step 3' } } as NgWizardStepData, 15 | { order: 4, options: { title: 'Step 4' } } as NgWizardStepData, 16 | { order: 5, options: { title: 'Step 5' } } as NgWizardStepData, 17 | ]; 18 | 19 | const currentStepData: NgWizardStepData = { order: 3, options: {} } as NgWizardStepData; 20 | 21 | let component: NgWizardNavigationComponent; 22 | let fixture: ComponentFixture; 23 | let element; 24 | 25 | let service: NgWizardService; 26 | 27 | beforeEach(async(() => { 28 | TestBed.configureTestingModule({ 29 | declarations: [NgWizardNavigationComponent], 30 | providers: [NgWizardService], 31 | imports: [RouterTestingModule], 32 | }).compileComponents(); 33 | })); 34 | 35 | beforeEach(() => { 36 | fixture = TestBed.createComponent(NgWizardNavigationComponent); 37 | component = fixture.componentInstance; 38 | element = fixture.nativeElement; 39 | 40 | service = TestBed.get(NgWizardService); 41 | spyOn(service, 'getStepDataChangesAsObservable').and.returnValue( 42 | new Observable((observer) => { 43 | observer.next(stepData); 44 | }), 45 | ); 46 | spyOn(service, 'getCurrentStepDataAsObservable').and.returnValue( 47 | new Observable((observer) => { 48 | observer.next(currentStepData); 49 | }), 50 | ); 51 | spyOn(service, 'navigateToStep'); 52 | service.wizardOptions = getDefaultWizardOptions(); 53 | 54 | fixture.detectChanges(); 55 | }); 56 | 57 | it('should display all steps in the list of step data', () => { 58 | const listItems = element.querySelectorAll('.ng-wizard-navigation-list-item'); 59 | expect(listItems.length).toBe(5); 60 | expect(listItems[0].querySelector('.ng-wizard-navigation-label').innerHTML).toEqual( 61 | ` ${stepData[0].options.title} `, 62 | ); 63 | expect(listItems[1].querySelector('.ng-wizard-navigation-label').innerHTML).toEqual( 64 | ` ${stepData[1].options.title} `, 65 | ); 66 | expect(listItems[2].querySelector('.ng-wizard-navigation-label').innerHTML).toEqual( 67 | ` ${stepData[2].options.title} `, 68 | ); 69 | expect(listItems[3].querySelector('.ng-wizard-navigation-label').innerHTML).toEqual( 70 | ` ${stepData[3].options.title} `, 71 | ); 72 | expect(listItems[4].querySelector('.ng-wizard-navigation-label').innerHTML).toEqual( 73 | ` ${stepData[4].options.title} `, 74 | ); 75 | }); 76 | 77 | it('should mark the steps before the current step as complete', () => { 78 | const listItems = element.querySelectorAll('.ng-wizard-navigation-list-item'); 79 | expect(listItems[0].querySelector('i.ng-wizard-icon').innerHTML).toEqual('done'); 80 | expect(listItems[0].children[0].classList).toContain('ng-wizard-navigation-link'); 81 | expect(listItems[1].querySelector('i.ng-wizard-icon').innerHTML).toEqual('done'); 82 | expect(listItems[1].children[0].classList).toContain('ng-wizard-navigation-link'); 83 | }); 84 | 85 | it('should mark the current step as in progress', () => { 86 | const listItems = element.querySelectorAll('.ng-wizard-navigation-list-item'); 87 | expect(listItems[2].querySelector('i.ng-wizard-icon').innerHTML).toEqual('create'); 88 | expect(listItems[2].children[0].classList).toContain('ng-wizard-navigation-active'); 89 | }); 90 | 91 | it('should mark the steps after the current step as locked', () => { 92 | const listItems = element.querySelectorAll('.ng-wizard-navigation-list-item'); 93 | expect(listItems[3].querySelector('i.ng-wizard-icon').innerHTML).toEqual('lock'); 94 | expect(listItems[3].children[0].classList).toContain('ng-wizard-navigation-disabled'); 95 | expect(listItems[4].querySelector('i.ng-wizard-icon').innerHTML).toEqual('lock'); 96 | expect(listItems[4].children[0].classList).toContain('ng-wizard-navigation-disabled'); 97 | }); 98 | 99 | it("should call the service's navigateToStep method when a previous step is clicked", () => { 100 | element.querySelectorAll('.ng-wizard-navigation-link')[0].click(); 101 | expect(service.navigateToStep).toHaveBeenCalledWith(stepData[0]); 102 | element.querySelectorAll('.ng-wizard-navigation-link')[1].click(); 103 | expect(service.navigateToStep).toHaveBeenCalledWith(stepData[1]); 104 | }); 105 | 106 | it('should do nothing when the current step is clicked', () => { 107 | element.querySelector('.ng-wizard-navigation-active').click(); 108 | expect(service.navigateToStep).not.toHaveBeenCalled(); 109 | }); 110 | 111 | it('should do nothing when a locked step is clicked', () => { 112 | element.querySelectorAll('.ng-wizard-navigation-disabled')[0].click(); 113 | element.querySelectorAll('.ng-wizard-navigation-disabled')[1].click(); 114 | expect(service.navigateToStep).not.toHaveBeenCalled(); 115 | }); 116 | 117 | it('should show the custom icons if defined in the wizard options', () => { 118 | component.wizardOptions = { 119 | ...getDefaultWizardOptions(), 120 | ...{ navBar: { icons: { previous: 'P', current: 'C', next: 'N' } } }, 121 | }; 122 | fixture.detectChanges(); 123 | expect(element.querySelectorAll('.ng-wizard-navigation-link span')[0].innerHTML).toBe('P'); 124 | expect(element.querySelectorAll('.ng-wizard-navigation-active span')[0].innerHTML).toBe('C'); 125 | expect(element.querySelectorAll('.ng-wizard-navigation-disabled span')[0].innerHTML).toBe('N'); 126 | }); 127 | }); 128 | 129 | describe('NgWizardNavigationComponent with custom step icons and disabled navigation', () => { 130 | const stepData: NgWizardStepData[] = [ 131 | { 132 | order: 1, 133 | options: { title: 'Step 1', icon: 'cake' }, 134 | } as NgWizardStepData, 135 | { 136 | order: 2, 137 | options: { title: 'Step 2', icon: 'star' }, 138 | } as NgWizardStepData, 139 | { 140 | order: 3, 141 | options: { title: 'Step 3', icon: 'pool' }, 142 | } as NgWizardStepData, 143 | ]; 144 | 145 | const currentStepData: NgWizardStepData = { order: 2, options: { disableNavigation: true } } as NgWizardStepData; 146 | 147 | let element; 148 | let service; 149 | 150 | beforeEach(async(() => { 151 | TestBed.configureTestingModule({ 152 | declarations: [NgWizardNavigationComponent], 153 | providers: [NgWizardService], 154 | imports: [RouterTestingModule], 155 | }).compileComponents(); 156 | })); 157 | 158 | beforeEach(() => { 159 | const fixture = TestBed.createComponent(NgWizardNavigationComponent); 160 | element = fixture.nativeElement; 161 | 162 | service = TestBed.get(NgWizardService); 163 | spyOn(service, 'getStepDataChangesAsObservable').and.returnValue( 164 | new Observable((observer) => { 165 | observer.next(stepData); 166 | }), 167 | ); 168 | spyOn(service, 'getCurrentStepDataAsObservable').and.returnValue( 169 | new Observable((observer) => { 170 | observer.next(currentStepData); 171 | }), 172 | ); 173 | spyOn(service, 'navigateToStep'); 174 | service.wizardOptions = getDefaultWizardOptions(); 175 | 176 | fixture.detectChanges(); 177 | }); 178 | 179 | it("should show the custom icon if defined in the wizard step's options", () => { 180 | const listItems = element.querySelectorAll('.ng-wizard-navigation-list-item'); 181 | expect(listItems[0].querySelector('.ng-wizard-icon').innerHTML).toEqual('cake'); 182 | expect(listItems[1].querySelector('.ng-wizard-icon').innerHTML).toEqual('star'); 183 | expect(listItems[2].querySelector('.ng-wizard-icon').innerHTML).toEqual('pool'); 184 | }); 185 | 186 | it("should disable the navigation if defined in the wizard step's options", () => { 187 | const listItems = element.querySelectorAll('.ng-wizard-navigation-list-item'); 188 | expect(listItems[0].children[0].classList).toContain('ng-wizard-navigation-disabled'); 189 | listItems[0].children[0].click(); 190 | expect(service.navigateToStep).not.toHaveBeenCalled(); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-navigation/ng-wizard-navigation.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgWizardService } from '../ng-wizard.service'; 3 | import { NgWizardStepData } from '../ng-wizard-step/ng-wizard-step-data.interface'; 4 | import { NgWizardOptions } from '../ng-wizard-options/ng-wizard-options.interface'; 5 | 6 | @Component({ 7 | selector: 'ng-wizard-navigation', 8 | templateUrl: './ng-wizard-navigation.component.html', 9 | styleUrls: ['./ng-wizard-navigation.component.scss'] 10 | }) 11 | export class NgWizardNavigationComponent implements OnInit { 12 | public stepData$; 13 | public currentStepData; 14 | 15 | public wizardOptions: NgWizardOptions; 16 | 17 | constructor(private service: NgWizardService) { } 18 | 19 | ngOnInit() { 20 | this.stepData$ = this.service.getStepDataChangesAsObservable(); 21 | this.service.getCurrentStepDataAsObservable().subscribe(stepData => this.currentStepData = stepData); 22 | this.wizardOptions = this.service.wizardOptions; 23 | } 24 | 25 | public goToStep(stepData: NgWizardStepData) { 26 | if ((this.currentStepData && this.currentStepData.options && this.currentStepData.options.disableNavigation) 27 | || stepData.order >= this.currentStepData.order) { 28 | return; 29 | } 30 | this.service.navigateToStep(stepData); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-options/ng-wizard-options.interface.ts: -------------------------------------------------------------------------------- 1 | export interface NgWizardOptions { 2 | name: string; 3 | navBar: { 4 | icons: { 5 | previous: string; 6 | current: string; 7 | next: string; 8 | }; 9 | }; 10 | buttons: { 11 | previous: { 12 | label: string; 13 | }; 14 | next: { 15 | label: string; 16 | }; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-step/ng-wizard-step-data.interface.ts: -------------------------------------------------------------------------------- 1 | import { ComponentRef } from '@angular/core'; 2 | import { NgWizardStepOptions } from './ng-wizard-step-options'; 3 | 4 | export interface NgWizardStepData { 5 | order: number; 6 | componentName: string; 7 | componentRef?: ComponentRef; 8 | path: string; 9 | previousStep?: string; 10 | nextStep?: string; 11 | isCurrent: boolean; 12 | options: NgWizardStepOptions; 13 | } 14 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-step/ng-wizard-step-options.ts: -------------------------------------------------------------------------------- 1 | export interface NgWizardStepOptions { 2 | title: string; 3 | icon?: string; 4 | buttons?: { 5 | previous?: { 6 | label?: string; 7 | hidden?: boolean; 8 | }; 9 | next?: { 10 | label?: string; 11 | hidden?: boolean; 12 | }; 13 | extra?: { 14 | label?: string; 15 | path?: string; 16 | } 17 | }; 18 | cleanQueryParameters?: boolean; 19 | disableNavigation?: boolean; 20 | } 21 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-step/ng-wizard-step.interface.ts: -------------------------------------------------------------------------------- 1 | export interface NgWizardStepInterface { 2 | /** 3 | * Return false if your step component's state is invalid and navigation to the next step should 4 | * be denied. 5 | * When returning false make sure to display a relevant (error) message telling the user why the 6 | * navigation is denied. 7 | */ 8 | wsIsValid(): boolean; 9 | 10 | /** 11 | * Perform any task before the wizard goes to a next step. 12 | * Return false to cancel the wizard's navigation. 13 | */ 14 | wsOnNext(): void | boolean; 15 | 16 | /** 17 | * Perform any task before the wizard goes to a previous step. 18 | * Return false to cancel the wizard's navigation. 19 | */ 20 | wsOnPrevious(): void | boolean; 21 | } 22 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard-step/ng-wizard-step.ts: -------------------------------------------------------------------------------- 1 | import { NgWizardStepInterface } from './ng-wizard-step.interface'; 2 | 3 | export abstract class NgWizardStep implements NgWizardStepInterface { 4 | wsIsValid(): boolean { 5 | return true; 6 | } 7 | 8 | wsOnNext(): void | boolean { 9 | return; 10 | } 11 | 12 | wsOnPrevious(): void | boolean { 13 | return; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 |
7 | 8 |
9 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; 2 | 3 | import { NgWizardComponent } from './ng-wizard.component'; 4 | import { NgWizardErrorComponent } from './ng-wizard-error/ng-wizard-error.component'; 5 | import { NgWizardNavigationComponent } from './ng-wizard-navigation/ng-wizard-navigation.component'; 6 | import { NgWizardButtonsComponent } from './ng-wizard-buttons/ng-wizard-buttons.component'; 7 | import { NgWizardService } from './ng-wizard.service'; 8 | import { ComponentRef } from '@angular/core'; 9 | import { RouterTestingModule } from '@angular/router/testing'; 10 | import { NoWizardRoute, NoWsInterface } from './ng-wizard-error/ng-wizard.error'; 11 | 12 | describe('NgWizardComponent', () => { 13 | let component: NgWizardComponent; 14 | let fixture: ComponentFixture; 15 | 16 | let service: NgWizardService; 17 | 18 | beforeEach(async(() => { 19 | TestBed.configureTestingModule({ 20 | declarations: [ 21 | NgWizardComponent, 22 | NgWizardErrorComponent, 23 | NgWizardNavigationComponent, 24 | NgWizardButtonsComponent, 25 | ], 26 | imports: [RouterTestingModule], 27 | providers: [NgWizardService], 28 | }).compileComponents(); 29 | })); 30 | 31 | beforeEach(() => { 32 | service = TestBed.inject(NgWizardService); 33 | }); 34 | 35 | describe('no errors', () => { 36 | beforeEach(() => { 37 | spyOn(service, 'loadWizardRoutes'); 38 | spyOn(service, 'registerActiveComponent'); 39 | 40 | fixture = TestBed.createComponent(NgWizardComponent); 41 | component = fixture.componentInstance; 42 | 43 | fixture.detectChanges(); 44 | }); 45 | 46 | it('should call the service\'s loadWizardRoutes method on creation', () => { 47 | expect(service.loadWizardRoutes).toHaveBeenCalledWith(undefined); 48 | }); 49 | 50 | it('should call the service\'s registerActiveComponent method when a child component gets displayed', fakeAsync(() => { 51 | component.onActivate({ success: true } as unknown as ComponentRef); 52 | expect(service.registerActiveComponent).toHaveBeenCalledWith({ success: true }); 53 | })); 54 | }); 55 | 56 | describe('with errors', () => { 57 | beforeEach(() => { 58 | spyOn(service, 'loadWizardRoutes').and.callFake(() => { 59 | throw new NoWizardRoute(''); 60 | }); 61 | spyOn(service, 'registerActiveComponent').and.callFake(() => { 62 | throw new NoWsInterface(''); 63 | }); 64 | 65 | fixture = TestBed.createComponent(NgWizardComponent); 66 | component = fixture.componentInstance; 67 | 68 | fixture.detectChanges(); 69 | }); 70 | 71 | it('should catch the service\'s loadWizardRoutes error', () => { 72 | expect(service.loadWizardRoutes).toHaveBeenCalledWith(undefined); 73 | expect(component.error).toEqual(new NoWizardRoute('')); 74 | }); 75 | 76 | it('should catch the service\'s registerActiveComponent error when a child component gets displayed', fakeAsync(() => { 77 | component.onActivate({ success: true } as unknown as ComponentRef); 78 | expect(service.registerActiveComponent).toHaveBeenCalledWith({ success: true }); 79 | expect(component.error).toEqual(new NoWsInterface('')); 80 | })); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentRef } from '@angular/core'; 2 | import { NgWizardService } from './ng-wizard.service'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'ng-wizard', 7 | templateUrl: './ng-wizard.component.html', 8 | }) 9 | export class NgWizardComponent { 10 | public error: Error; 11 | private wizardName: string; 12 | 13 | constructor( 14 | private service: NgWizardService, 15 | private route: ActivatedRoute, 16 | ) { 17 | try { 18 | this.route.data.subscribe(data => { 19 | this.wizardName = data.name; 20 | }); 21 | this.service.loadWizardRoutes(this.wizardName); 22 | } catch (error) { 23 | this.error = error; 24 | } 25 | } 26 | 27 | public onActivate(componentRef: ComponentRef) { 28 | try { 29 | this.service.registerActiveComponent(componentRef); 30 | } catch (error) { 31 | this.error = error; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { NgWizardComponent } from './ng-wizard.component'; 3 | import { CommonModule } from '@angular/common'; 4 | import { RouterModule } from '@angular/router'; 5 | import { NgWizardErrorComponent } from './ng-wizard-error/ng-wizard-error.component'; 6 | import { NgWizardNavigationComponent } from './ng-wizard-navigation/ng-wizard-navigation.component'; 7 | import { NgWizardButtonsComponent } from './ng-wizard-buttons/ng-wizard-buttons.component'; 8 | import { NgWizardService } from './ng-wizard.service'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | NgWizardComponent, 13 | NgWizardErrorComponent, 14 | NgWizardNavigationComponent, 15 | NgWizardButtonsComponent, 16 | ], 17 | imports: [CommonModule, RouterModule], 18 | providers: [NgWizardService], 19 | exports: [NgWizardComponent], 20 | }) 21 | export class NgWizardModule {} 22 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, fakeAsync, TestBed, tick } from '@angular/core/testing'; 2 | 3 | import { NgWizardService } from './ng-wizard.service'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | import { NgWizardComponent } from './ng-wizard.component'; 6 | import { NgWizardModule } from './ng-wizard.module'; 7 | import { Component, ComponentRef } from '@angular/core'; 8 | import { Route, Router } from '@angular/router'; 9 | import { NgWizardStepData } from './ng-wizard-step/ng-wizard-step-data.interface'; 10 | import { NgWizardStep } from './ng-wizard-step/ng-wizard-step'; 11 | import { NoChildRoutes, NoWizardRoute, NoWsInterface } from './ng-wizard-error/ng-wizard.error'; 12 | import { Location } from '@angular/common'; 13 | 14 | @Component({ 15 | template: 'Step 1 works!', 16 | }) 17 | class Step1Component extends NgWizardStep {} 18 | 19 | @Component({ 20 | template: 'Step 2 works!', 21 | }) 22 | class Step2Component extends NgWizardStep {} 23 | 24 | @Component({ 25 | template: 'Step 3 does not work!', 26 | }) 27 | class Step3Component {} 28 | 29 | @Component({ 30 | template: 'Some other page', 31 | }) 32 | class OtherComponent {} 33 | 34 | @Component({ 35 | template: 'Parent page', 36 | }) 37 | class ParentComponent {} 38 | 39 | describe('NgWizardService', () => { 40 | let service: NgWizardService; 41 | 42 | const ngWizardComponentName = 'NgWizardComponent'; 43 | 44 | const wizardRoute: Route = { 45 | path: '', 46 | component: NgWizardComponent, 47 | children: [ 48 | { path: 'step-1', component: Step1Component }, 49 | { path: 'step-2', component: Step2Component }, 50 | { path: 'step-3', component: Step3Component }, 51 | { path: '**', redirectTo: 'step-1' }, 52 | ], 53 | data: { name: ngWizardComponentName } 54 | }; 55 | 56 | const stepData: NgWizardStepData[] = [ 57 | { 58 | order: 1, 59 | componentName: 'Step1Component', 60 | path: 'step-1', 61 | previousStep: undefined, 62 | nextStep: 'step-2', 63 | isCurrent: false, 64 | options: { title: 'Step 1' }, 65 | }, 66 | { 67 | order: 2, 68 | componentName: 'Step2Component', 69 | path: 'step-2', 70 | previousStep: 'step-1', 71 | nextStep: 'step-3', 72 | isCurrent: false, 73 | options: { title: 'Step 2' }, 74 | }, 75 | { 76 | order: 3, 77 | componentName: 'Step3Component', 78 | path: 'step-3', 79 | previousStep: 'step-2', 80 | nextStep: undefined, 81 | isCurrent: false, 82 | options: { title: 'Step 3' }, 83 | }, 84 | ]; 85 | 86 | beforeEach(async(() => { 87 | TestBed.configureTestingModule({ 88 | declarations: [Step1Component, Step2Component, Step3Component, OtherComponent], 89 | providers: [ 90 | NgWizardService, 91 | { 92 | provide: Router, 93 | useValue: { 94 | config: [wizardRoute, { path: 'other', component: OtherComponent }], 95 | url: 'step-2', 96 | navigate: () => true, 97 | }, 98 | }, 99 | ], 100 | imports: [NgWizardModule], 101 | }).compileComponents(); 102 | })); 103 | 104 | beforeEach(() => { 105 | service = TestBed.inject(NgWizardService); 106 | }); 107 | 108 | describe('loadWizardRoutes', () => { 109 | it('should get the route config for the NgWizardComponent and store it', () => { 110 | service.loadWizardRoutes(ngWizardComponentName); 111 | expect((service as any).wizardRoute).toEqual(wizardRoute); 112 | }); 113 | 114 | it('should store the child routes as a list of NgWizardStepData', () => { 115 | service.loadWizardRoutes(ngWizardComponentName); 116 | expect((service as any).stepData).toEqual(stepData); 117 | }); 118 | 119 | it('should emit a stepDataChange event for each added step', () => { 120 | spyOn((service as any).stepDataChanges, 'next'); 121 | service.loadWizardRoutes(ngWizardComponentName); 122 | 123 | expect((service as any).stepDataChanges.next).toHaveBeenCalledTimes(3); 124 | expect((service as any).stepDataChanges.next).toHaveBeenCalledWith(stepData); 125 | }); 126 | }); 127 | 128 | describe('registerActiveComponent', () => { 129 | beforeEach(() => { 130 | service.loadWizardRoutes(ngWizardComponentName); 131 | }); 132 | 133 | it('should store the current stepData and currentComponent', () => { 134 | const componentRef = (new Step2Component() as unknown) as ComponentRef; 135 | service.registerActiveComponent(componentRef); 136 | 137 | expect((service as any).currentComponent).toBe(componentRef); 138 | expect((service as any).currentStepData).toEqual({ 139 | ...stepData[1], 140 | isCurrent: true, 141 | componentRef: componentRef, 142 | }); 143 | }); 144 | 145 | it('should emit a stepDataChange event', () => { 146 | spyOn((service as any).stepDataChanges, 'next'); 147 | const componentRef = (new Step2Component() as unknown) as ComponentRef; 148 | service.registerActiveComponent(componentRef); 149 | 150 | expect((service as any).stepDataChanges.next).toHaveBeenCalled(); 151 | }); 152 | 153 | it('should set only the latest registered active step as current step', () => { 154 | (service as any).stepData[0].isCurrent = true; 155 | (service as any).stepData[1].isCurrent = false; 156 | (service as any).stepData[2].isCurrent = false; 157 | const component1Ref = (new Step2Component() as unknown) as ComponentRef; 158 | 159 | service.registerActiveComponent(component1Ref); 160 | 161 | expect((service as any).stepData[0].isCurrent).toBe(false); 162 | expect((service as any).stepData[1].isCurrent).toBe(true); 163 | expect((service as any).stepData[2].isCurrent).toBe(false); 164 | }); 165 | 166 | it('should throw a NO_WS_INTERFACE error if the activated step does not implement the NgWizardStep interface', () => { 167 | const component3Ref = (new Step3Component() as unknown) as ComponentRef; 168 | expect(() => service.registerActiveComponent(component3Ref)).toThrow(new NoWsInterface('Step3Component')); 169 | }); 170 | }); 171 | 172 | describe('navigateToStep', () => { 173 | let router: Router; 174 | 175 | beforeEach(() => { 176 | router = TestBed.inject(Router); 177 | spyOn(router, 'navigate'); 178 | 179 | service.loadWizardRoutes(ngWizardComponentName); 180 | }); 181 | 182 | it("should call the current component's wsIsValid method", () => { 183 | const component2Ref = (new Step2Component() as unknown) as ComponentRef; 184 | service.registerActiveComponent(component2Ref); 185 | 186 | spyOn((component2Ref as unknown) as NgWizardStep, 'wsIsValid'); 187 | service.navigateToStep(stepData[2]); 188 | expect(((component2Ref as unknown) as NgWizardStep).wsIsValid).toHaveBeenCalled(); 189 | }); 190 | 191 | it("should call the current component's wsOnNext method if the order of the step is greater than the current step", () => { 192 | const component2Ref = (new Step2Component() as unknown) as ComponentRef; 193 | service.registerActiveComponent(component2Ref); 194 | 195 | spyOn((component2Ref as unknown) as NgWizardStep, 'wsOnNext'); 196 | service.navigateToStep(stepData[2]); 197 | expect(((component2Ref as unknown) as NgWizardStep).wsOnNext).toHaveBeenCalled(); 198 | }); 199 | 200 | it("should call the current component's wsOnPrevious method if the order of the step is less than the current step", () => { 201 | const component2Ref = (new Step2Component() as unknown) as ComponentRef; 202 | service.registerActiveComponent(component2Ref); 203 | 204 | spyOn((component2Ref as unknown) as NgWizardStep, 'wsOnPrevious'); 205 | service.navigateToStep(stepData[0]); 206 | expect(((component2Ref as unknown) as NgWizardStep).wsOnPrevious).toHaveBeenCalled(); 207 | }); 208 | 209 | it("should call the route's navigate method with the new path", () => { 210 | const component2Ref = (new Step2Component() as unknown) as ComponentRef; 211 | service.registerActiveComponent(component2Ref); 212 | 213 | service.navigateToStep(stepData[0]); 214 | 215 | expect(router.navigate).toHaveBeenCalledWith([stepData[0].path], { queryParamsHandling: 'merge' }); 216 | }); 217 | 218 | it("should not call the route's navigate method if the wsOnNext method returns false", () => { 219 | const component2Ref = (new Step2Component() as unknown) as ComponentRef; 220 | service.registerActiveComponent(component2Ref); 221 | 222 | spyOn((component2Ref as unknown) as NgWizardStep, 'wsOnNext').and.returnValue(false); 223 | service.navigateToStep(stepData[2]); 224 | 225 | expect(router.navigate).not.toHaveBeenCalled(); 226 | }); 227 | 228 | it("should not call the route's navigate method if the wsOnPrevious method returns false", () => { 229 | const component2Ref = (new Step2Component() as unknown) as ComponentRef; 230 | service.registerActiveComponent(component2Ref); 231 | 232 | spyOn((component2Ref as unknown) as NgWizardStep, 'wsOnPrevious').and.returnValue(false); 233 | service.navigateToStep(stepData[0]); 234 | 235 | expect(router.navigate).not.toHaveBeenCalled(); 236 | }); 237 | }); 238 | 239 | describe('navigateToNextStep', () => { 240 | beforeEach(() => { 241 | service.loadWizardRoutes(ngWizardComponentName); 242 | }); 243 | 244 | it("should call the service's navigateToStep method with the next step's stepData", () => { 245 | const component2Ref = (new Step2Component() as unknown) as ComponentRef; 246 | service.registerActiveComponent(component2Ref); 247 | 248 | spyOn(service, 'navigateToStep'); 249 | service.navigateToNextStep(); 250 | 251 | expect(service.navigateToStep).toHaveBeenCalledWith(stepData[2]); 252 | }); 253 | }); 254 | 255 | describe('navigateToNextStep', () => { 256 | beforeEach(() => { 257 | service.loadWizardRoutes(ngWizardComponentName); 258 | }); 259 | 260 | it("should call the service's navigateToStep method with the previous step's stepData", () => { 261 | const component2Ref = (new Step2Component() as unknown) as ComponentRef; 262 | service.registerActiveComponent(component2Ref); 263 | 264 | spyOn(service, 'navigateToStep'); 265 | service.navigateToPreviousStep(); 266 | 267 | expect(service.navigateToStep).toHaveBeenCalledWith(stepData[0]); 268 | }); 269 | }); 270 | }); 271 | 272 | describe('NgWizardService without wizard route', () => { 273 | let service: NgWizardService; 274 | 275 | const ngWizardComponentName = 'NgWizardComponent'; 276 | 277 | beforeEach(async(() => { 278 | TestBed.configureTestingModule({ 279 | declarations: [OtherComponent], 280 | providers: [NgWizardService], 281 | imports: [NgWizardModule, RouterTestingModule.withRoutes([{ path: 'other', component: OtherComponent }])], 282 | }).compileComponents(); 283 | })); 284 | 285 | beforeEach(() => { 286 | service = TestBed.inject(NgWizardService); 287 | }); 288 | 289 | describe('loadWizardRoutes', () => { 290 | it('should throw a NO_WIZARD_ROUTE error', () => { 291 | expect(() => service.loadWizardRoutes(ngWizardComponentName)).toThrow(new NoWizardRoute(ngWizardComponentName)); 292 | }); 293 | }); 294 | }); 295 | 296 | describe('NgWizardService without child routes', () => { 297 | let service: NgWizardService; 298 | 299 | const ngWizardComponentName = 'NgWizardComponent'; 300 | 301 | beforeEach(async(() => { 302 | TestBed.configureTestingModule({ 303 | declarations: [OtherComponent], 304 | providers: [NgWizardService], 305 | imports: [NgWizardModule, RouterTestingModule.withRoutes( 306 | [{ path: '', component: NgWizardComponent, data: { name: ngWizardComponentName } }] 307 | )], 308 | }).compileComponents(); 309 | })); 310 | 311 | beforeEach(() => { 312 | service = TestBed.inject(NgWizardService); 313 | }); 314 | 315 | describe('loadWizardRoutes', () => { 316 | it('should throw a NO_WIZARD_ROUTE error', () => { 317 | expect(() => service.loadWizardRoutes(ngWizardComponentName)).toThrow( 318 | new NoChildRoutes(ngWizardComponentName, ''), 319 | ); 320 | }); 321 | }); 322 | }); 323 | 324 | describe('NgWizardService with the wizard component on a path', () => { 325 | let service: NgWizardService; 326 | 327 | const ngWizardComponentName = 'NgWizardComponent'; 328 | 329 | const wizardRoute: Route = { 330 | path: 'wizard', 331 | component: NgWizardComponent, 332 | children: [ 333 | { path: 'step-1', component: Step1Component }, 334 | { path: 'step-2', component: Step2Component }, 335 | { path: 'step-3', component: Step3Component }, 336 | { path: '**', redirectTo: 'step-1' }, 337 | ], 338 | data: { name: ngWizardComponentName } 339 | }; 340 | 341 | const stepData: NgWizardStepData[] = [ 342 | { 343 | order: 1, 344 | componentName: 'Step1Component', 345 | path: 'step-1', 346 | previousStep: undefined, 347 | nextStep: 'step-2', 348 | isCurrent: false, 349 | options: { title: 'Step 1' }, 350 | }, 351 | { 352 | order: 2, 353 | componentName: 'Step2Component', 354 | path: 'step-2', 355 | previousStep: 'step-1', 356 | nextStep: 'step-3', 357 | isCurrent: false, 358 | options: { title: 'Step 2' }, 359 | }, 360 | { 361 | order: 3, 362 | componentName: 'Step3Component', 363 | path: 'step-3', 364 | previousStep: 'step-2', 365 | nextStep: undefined, 366 | isCurrent: false, 367 | options: { title: 'Step 3' }, 368 | }, 369 | ]; 370 | 371 | beforeEach(async(() => { 372 | TestBed.configureTestingModule({ 373 | declarations: [Step1Component, Step2Component, Step3Component], 374 | providers: [ 375 | NgWizardService, 376 | { provide: Router, useValue: { config: [wizardRoute], url: 'wizard/step-1', navigate: () => true } }, 377 | ], 378 | imports: [NgWizardModule], 379 | }).compileComponents(); 380 | })); 381 | 382 | beforeEach(() => { 383 | service = TestBed.inject(NgWizardService); 384 | }); 385 | 386 | describe('navigateToStep', () => { 387 | let router: Router; 388 | 389 | beforeEach(() => { 390 | router = TestBed.inject(Router); 391 | spyOn(router, 'navigate'); 392 | 393 | service.loadWizardRoutes(ngWizardComponentName); 394 | }); 395 | 396 | it("should call the route's navigate method with the new path", () => { 397 | const component1Ref = (new Step1Component() as unknown) as ComponentRef; 398 | service.registerActiveComponent(component1Ref); 399 | 400 | service.navigateToStep(stepData[1]); 401 | 402 | expect(router.navigate).toHaveBeenCalledWith([wizardRoute.path + '/' + stepData[1].path], { queryParamsHandling: 'merge' }); 403 | }); 404 | }); 405 | }); 406 | 407 | describe('NgWizardService with more than one wizard', () => { 408 | let service: NgWizardService; 409 | 410 | const wizardARoute: Route = { 411 | path: 'wizardA', 412 | component: NgWizardComponent, 413 | children: [ 414 | { path: 'step-1a', component: Step1Component }, 415 | { path: 'step-2a', component: Step2Component }, 416 | { path: '**', redirectTo: 'step-1a' }, 417 | ], 418 | data: { name: 'WizardA' }, 419 | }; 420 | 421 | const wizardBRoute: Route = { 422 | path: 'wizardB', 423 | component: NgWizardComponent, 424 | children: [ 425 | { path: 'step-1b', component: Step1Component }, 426 | { path: 'step-2b', component: Step2Component }, 427 | { path: '**', redirectTo: 'step-1b' }, 428 | ], 429 | data: { name: 'WizardB' } 430 | }; 431 | 432 | 433 | const wizardAstepData: NgWizardStepData[] = [ 434 | { 435 | order: 1, 436 | componentName: 'Step1Component', 437 | path: 'step-1a', 438 | previousStep: undefined, 439 | nextStep: 'step-2a', 440 | isCurrent: false, 441 | options: { title: 'Step 1a' }, 442 | }, 443 | { 444 | order: 2, 445 | componentName: 'Step2Component', 446 | path: 'step-2a', 447 | previousStep: 'step-1a', 448 | nextStep: undefined, 449 | isCurrent: false, 450 | options: { title: 'Step 2a' }, 451 | } 452 | ]; 453 | 454 | const wizardBstepData: NgWizardStepData[] = [ 455 | { 456 | order: 1, 457 | componentName: 'Step1Component', 458 | path: 'step-1b', 459 | previousStep: undefined, 460 | nextStep: 'step-2b', 461 | isCurrent: false, 462 | options: { title: 'Step 1b' }, 463 | }, 464 | { 465 | order: 2, 466 | componentName: 'Step2Component', 467 | path: 'step-2b', 468 | previousStep: 'step-1b', 469 | nextStep: undefined, 470 | isCurrent: false, 471 | options: { title: 'Step 2b' }, 472 | }, 473 | ]; 474 | 475 | beforeEach(() => { 476 | TestBed.configureTestingModule({ 477 | declarations: [Step1Component, Step2Component], 478 | providers: [ 479 | NgWizardService, 480 | { 481 | provide: Router, 482 | useValue: { 483 | config: [wizardARoute, wizardBRoute], 484 | url: 'wizardA', 485 | navigate: () => true, 486 | }, 487 | }, 488 | ], 489 | imports: [ 490 | NgWizardModule, 491 | ], 492 | }); 493 | 494 | service = TestBed.inject(NgWizardService); 495 | }); 496 | 497 | describe('loadWizardRoutes', () => { 498 | describe('for wizard A', () => { 499 | it('should store the child routes as a list of NgWizardStepData', () => { 500 | service.loadWizardRoutes(wizardARoute.data.name); 501 | expect((service as any).stepData).toEqual(wizardAstepData); 502 | }); 503 | 504 | it('should get the route config for the NgWizardComponent and store it', () => { 505 | service.loadWizardRoutes(wizardARoute.data.name); 506 | expect((service as any).wizardRoute).toEqual(wizardARoute); 507 | }); 508 | 509 | it('should emit a stepDataChange event for each added step', () => { 510 | spyOn((service as any).stepDataChanges, 'next'); 511 | service.loadWizardRoutes(wizardARoute.data.name); 512 | expect((service as any).stepDataChanges.next).toHaveBeenCalledWith(wizardAstepData); 513 | expect((service as any).stepDataChanges.next).toHaveBeenCalledTimes(2); 514 | }); 515 | }); 516 | 517 | describe('for wizard B', () => { 518 | it('should get the route config for the NgWizardComponent and store it', () => { 519 | service.loadWizardRoutes(wizardBRoute.data.name); 520 | expect((service as any).wizardRoute).toEqual(wizardBRoute); 521 | }); 522 | 523 | it('should store the child routes as a list of NgWizardStepData', () => { 524 | service.loadWizardRoutes(wizardBRoute.data.name); 525 | expect((service as any).stepData).toEqual(wizardBstepData); 526 | }); 527 | 528 | it('should emit a stepDataChange event for each added step', () => { 529 | spyOn((service as any).stepDataChanges, 'next'); 530 | service.loadWizardRoutes(wizardBRoute.data.name); 531 | 532 | expect((service as any).stepDataChanges.next).toHaveBeenCalledTimes(2); 533 | expect((service as any).stepDataChanges.next).toHaveBeenCalledWith(wizardBstepData); 534 | }); 535 | }); 536 | 537 | describe('for unknown wizard', () => { 538 | it('should throw a NO_WIZARD_ROUTE error', () => { 539 | expect(() => service.loadWizardRoutes('unknown')) 540 | .toThrow(new NoWizardRoute('unknown')); 541 | }); 542 | }); 543 | }); 544 | 545 | 546 | 547 | }); 548 | 549 | describe('NgWizardService with the wizard component on a nested path', () => { 550 | let service: NgWizardService; 551 | 552 | const ngWizardComponentName = 'NgWizardComponent'; 553 | 554 | const wizardRoute: Route = { 555 | path: 'wizard', 556 | component: NgWizardComponent, 557 | children: [ 558 | { path: 'step-1', component: Step1Component }, 559 | { path: 'step-2', component: Step2Component }, 560 | { path: '**', redirectTo: 'step-1' }, 561 | ], 562 | data: { name: ngWizardComponentName } 563 | }; 564 | 565 | const stepData: NgWizardStepData[] = [ 566 | { 567 | order: 1, 568 | componentName: 'Step1Component', 569 | path: 'step-1', 570 | previousStep: undefined, 571 | nextStep: 'step-2', 572 | isCurrent: false, 573 | options: { title: 'Step 1' }, 574 | }, 575 | { 576 | order: 2, 577 | componentName: 'Step2Component', 578 | path: 'step-2', 579 | previousStep: 'step-1', 580 | nextStep: undefined, 581 | isCurrent: false, 582 | options: { title: 'Step 2' }, 583 | } 584 | ]; 585 | 586 | beforeEach(() => { 587 | TestBed.configureTestingModule({ 588 | declarations: [Step1Component, Step2Component, ParentComponent], 589 | providers: [ 590 | NgWizardService, 591 | { 592 | provide: Router, 593 | useValue: { 594 | config: [{ path: 'parent', component: ParentComponent, children: [wizardRoute]}], 595 | url: 'parent/wizard', 596 | navigate: () => true, 597 | }, 598 | }, 599 | ], 600 | imports: [ 601 | NgWizardModule, 602 | ], 603 | }); 604 | 605 | service = TestBed.inject(NgWizardService); 606 | }); 607 | 608 | describe('loadWizardRoutes', () => { 609 | it('should store the child routes as a list of NgWizardStepData', () => { 610 | service.loadWizardRoutes(ngWizardComponentName); 611 | expect((service as any).stepData).toEqual(stepData); 612 | }); 613 | 614 | it('should get the route config for the NgWizardComponent and store it', () => { 615 | service.loadWizardRoutes(ngWizardComponentName); 616 | expect((service as any).wizardRoute).toEqual(wizardRoute); 617 | }); 618 | 619 | it('should emit a stepDataChange event for each added step', () => { 620 | spyOn((service as any).stepDataChanges, 'next'); 621 | service.loadWizardRoutes(ngWizardComponentName); 622 | expect((service as any).stepDataChanges.next).toHaveBeenCalledWith(stepData); 623 | expect((service as any).stepDataChanges.next).toHaveBeenCalledTimes(2); 624 | }); 625 | }); 626 | }); 627 | 628 | describe('NgWizardService with the wizard component on a dynamic path', () => { 629 | let service: NgWizardService; 630 | let router: Router; 631 | let location: Location; 632 | 633 | const ngWizardComponentName = 'NgWizardComponent'; 634 | 635 | const wizardRoute: Route = { 636 | path: 'resource/:id/wizard', 637 | component: NgWizardComponent, 638 | children: [ 639 | { path: 'step-1', component: Step1Component }, 640 | { path: 'step-2', component: Step2Component }, 641 | { path: '**', redirectTo: 'step-1' }, 642 | ], 643 | data: { name: ngWizardComponentName } 644 | }; 645 | 646 | const stepData: NgWizardStepData[] = [ 647 | { 648 | order: 1, 649 | componentName: 'Step1Component', 650 | path: 'step-1', 651 | previousStep: undefined, 652 | nextStep: 'step-2', 653 | isCurrent: false, 654 | options: { title: 'Step 1' }, 655 | }, 656 | { 657 | order: 2, 658 | componentName: 'Step2Component', 659 | path: 'step-2', 660 | previousStep: 'step-1', 661 | nextStep: undefined, 662 | isCurrent: false, 663 | options: { title: 'Step 2' }, 664 | } 665 | ]; 666 | 667 | beforeEach(async(() => { 668 | TestBed.configureTestingModule({ 669 | declarations: [Step1Component, Step2Component], 670 | providers: [NgWizardService], 671 | imports: [ 672 | NgWizardModule, 673 | RouterTestingModule.withRoutes([wizardRoute]) 674 | ], 675 | }).compileComponents(); 676 | })); 677 | 678 | beforeEach(() => { 679 | service = TestBed.inject(NgWizardService); 680 | router = TestBed.inject(Router); 681 | location = TestBed.inject(Location); 682 | router.initialNavigation(); 683 | }); 684 | 685 | describe('navigateToStep', () => { 686 | it("should call the route's navigate method with the new path", fakeAsync(() => { 687 | router.navigate(['resource/123/wizard']); 688 | tick(); 689 | service.loadWizardRoutes(ngWizardComponentName); 690 | const component1Ref = (new Step1Component() as unknown) as ComponentRef; 691 | service.registerActiveComponent(component1Ref); 692 | service.navigateToStep(stepData[1]); 693 | tick(); 694 | expect(location.path()).toBe('/resource/123/wizard/step-2'); 695 | })); 696 | }); 697 | }); 698 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard.service.ts: -------------------------------------------------------------------------------- 1 | import { ComponentRef, Injectable } from '@angular/core'; 2 | import { Route, Router } from '@angular/router'; 3 | import * as utils from './ng-wizard.utils'; 4 | import { NoChildRoutes, NoWizardRoute, NoWsInterface } from './ng-wizard-error/ng-wizard.error'; 5 | import { NgWizardStepData } from './ng-wizard-step/ng-wizard-step-data.interface'; 6 | import { NgWizardStep } from './ng-wizard-step/ng-wizard-step'; 7 | import { BehaviorSubject, Observable } from 'rxjs'; 8 | import { map } from 'rxjs/operators'; 9 | import { NgWizardOptions } from './ng-wizard-options/ng-wizard-options.interface'; 10 | 11 | @Injectable() 12 | export class NgWizardService { 13 | public wizardOptions: NgWizardOptions; 14 | 15 | private wizardRoute: Route; 16 | private stepData: NgWizardStepData[] = []; 17 | private currentStepData: NgWizardStepData; 18 | private currentComponent: NgWizardStep; 19 | 20 | private stepDataChanges = new BehaviorSubject([]); 21 | 22 | constructor(private router: Router) {} 23 | 24 | /** 25 | * Initializes the wizard by parsing the wizard's child routes found in Angular's router config 26 | * into a list of NgWizardStepData. 27 | * @param wizardName The unique name of the wizard 28 | */ 29 | public loadWizardRoutes(wizardName: string) { 30 | this.wizardRoute = this.getWizardRoute(wizardName); 31 | if (!this.wizardRoute) { 32 | throw new NoWizardRoute(wizardName); 33 | } 34 | this.wizardOptions = utils.mergeWizardOptions(this.wizardRoute.data); 35 | this.loadChildRoutes(this.wizardRoute); 36 | } 37 | 38 | /** 39 | * Checks and stores the currently displayed component. 40 | * @param componentRef A reference to the currently displayed component 41 | */ 42 | public registerActiveComponent(componentRef: ComponentRef) { 43 | if (!utils.componentImplementsNgWizardStepInterface(componentRef)) { 44 | throw new NoWsInterface(componentRef.constructor.name); 45 | } 46 | 47 | // Cast to unknown before casting to NgWizardStep to let typescript know we checked and approve 48 | // this conversion. 49 | this.currentComponent = (componentRef as unknown) as NgWizardStep; 50 | this.currentStepData = utils.getStepDataForUrl(this.stepData, this.router.url); 51 | this.currentStepData.componentRef = componentRef; 52 | this.resetCurrentStep(); 53 | this.currentStepData.isCurrent = true; 54 | this.onStepDataChange(); 55 | } 56 | 57 | /** 58 | * Calls the current component's wsOnPrevious() or wsOnNext()) methods and navigates to the given 59 | * step if the component does not abort or its state is invalid (for next navigation). 60 | * 61 | * @param stepData The NgWizardStepData of the the step to navigate to 62 | */ 63 | public navigateToStep(stepData: NgWizardStepData) { 64 | let goAhead; 65 | if (this.currentStepData.order > stepData.order) { 66 | goAhead = this.currentComponent.wsOnPrevious(); 67 | } else { 68 | goAhead = this.currentComponent.wsIsValid() && this.currentComponent.wsOnNext(); 69 | } 70 | if (goAhead === false) { 71 | return; 72 | } 73 | 74 | // If the wizard is added to a specific path in the application we have to join that path and 75 | // the step's path as the path to navigate to. 76 | // The Angular Router's relativeTo option does not seem to work when using the hash location 77 | // strategy. 78 | // The path is based on the current route to allow route parameter 79 | const routeFragment = this.router.url.split('/'); 80 | routeFragment.pop(); 81 | routeFragment.push(stepData.path); 82 | const stepPath = routeFragment.join('/'); 83 | 84 | if (stepData.options.cleanQueryParameters) { 85 | return this.router.navigate([stepPath], { queryParams: {} }); 86 | } 87 | return this.router.navigate([stepPath], { queryParamsHandling: 'merge' }); 88 | } 89 | 90 | /** 91 | * Utility method to navigate to the next step. 92 | */ 93 | public navigateToNextStep() { 94 | const nextStepData = utils.getStepDataForPath(this.stepData, this.currentStepData.nextStep); 95 | return this.navigateToStep(nextStepData); 96 | } 97 | 98 | /** 99 | * Utility method to navigate to the previous step. 100 | */ 101 | public navigateToPreviousStep() { 102 | const previousStepData = utils.getStepDataForPath(this.stepData, this.currentStepData.previousStep); 103 | 104 | return this.navigateToStep(previousStepData); 105 | } 106 | 107 | /** 108 | * Utility method to navigate to a specific route path (external to the wizard) 109 | */ 110 | public navigateToPath(path: string) { 111 | return this.router.navigate([path], { queryParamsHandling: 'merge' }); 112 | } 113 | 114 | /** 115 | * Returns the step data changes as an observable. 116 | */ 117 | public getStepDataChangesAsObservable(): Observable { 118 | return this.stepDataChanges.asObservable(); 119 | } 120 | 121 | /** 122 | * Returns the current step data as an observable. 123 | */ 124 | // TODO: Write a unit test for this method 125 | public getCurrentStepDataAsObservable(): Observable { 126 | return this.getStepDataChangesAsObservable().pipe( 127 | map((stepDatas) => stepDatas.find((stepData) => stepData.isCurrent)), 128 | ); 129 | } 130 | 131 | /** 132 | * Returns the Angular Route for the wizard component found in Angular's router config. 133 | * @param wizardName The unique name of the wizard 134 | */ 135 | private getWizardRoute(wizardName: string): Route { 136 | const wizardRoutes = this.getAllWizardRoutes(this.router.config, wizardName); 137 | return wizardRoutes.find((route) => route.data && route.data.name === wizardName); 138 | } 139 | 140 | /** 141 | * From a given array of routes config, returns an array of routes config whose component is wizardComponentName. 142 | * Recursively look down every children route config 143 | * @param routes An array of route config 144 | * @param wizardName The name of the wizard to look for 145 | */ 146 | private getAllWizardRoutes (routes: Route[], wizardName: string): Route[] { 147 | let wizardRoutes = routes.filter((route) => route.data && route.data.name === wizardName); 148 | // Recursive search in child routes 149 | routes.filter((route) => route.children && route.children.length > 0).forEach((routeWithChildren) => { 150 | const childWizardRoutes = this.getAllWizardRoutes(routeWithChildren.children, wizardName); 151 | wizardRoutes = wizardRoutes.concat(childWizardRoutes); 152 | }); 153 | // Recursive search in lazily loaded child routes 154 | routes.filter((route) => (route as any)._loadedConfig?.routes?.length > 0).forEach((routeWithChildren) => { 155 | const childWizardRoutes = this.getAllWizardRoutes((routeWithChildren as any)._loadedConfig.routes, wizardName); 156 | wizardRoutes = wizardRoutes.concat(childWizardRoutes); 157 | }); 158 | return wizardRoutes; 159 | } 160 | 161 | /** 162 | * Parses the child routes of the wizard component route and stores them as a list of 163 | * NgWizardStepData. 164 | * @param wizardRoute The Angular Route for the wizard component 165 | */ 166 | private loadChildRoutes(wizardRoute: Route) { 167 | if (!wizardRoute.children) { 168 | throw new NoChildRoutes(wizardRoute.component.name, wizardRoute.path); 169 | } 170 | 171 | const childRoutes = wizardRoute.children.filter((route) => route.component); 172 | 173 | this.stepData = []; 174 | for (let i = 0; i < childRoutes.length; i++) { 175 | this.registerStep(i, childRoutes[i], childRoutes[i - 1], childRoutes[i + 1]); 176 | } 177 | } 178 | 179 | /** 180 | * Stores a child route as an NgWizardStepData. 181 | * @param index The index in the list of child routes 182 | * @param stepRoute The child route 183 | * @param previousStep The previous child route (undefined if first child route) 184 | * @param nextStep The next child route (undefined if last child route) 185 | */ 186 | private registerStep(index: number, stepRoute: Route, previousStep: Route, nextStep: Route) { 187 | this.stepData.push({ 188 | order: index + 1, 189 | componentName: stepRoute.component.name, 190 | path: stepRoute.path, 191 | previousStep: previousStep ? previousStep.path : undefined, 192 | nextStep: nextStep ? nextStep.path : undefined, 193 | isCurrent: false, 194 | options: utils.getWizardStepOptions(stepRoute), 195 | }); 196 | this.onStepDataChange(); 197 | } 198 | 199 | /** 200 | * Emits a step data change event to the subscribers when the step data changes. 201 | */ 202 | private onStepDataChange() { 203 | this.stepDataChanges.next(this.stepData); 204 | } 205 | 206 | /** 207 | * Sets the isCurrent attribute of all step data to false. 208 | */ 209 | private resetCurrentStep() { 210 | this.stepData.map((stepData) => { 211 | stepData.isCurrent = false; 212 | return stepData; 213 | }); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard.utils.spec.ts: -------------------------------------------------------------------------------- 1 | import * as utils from './ng-wizard.utils'; 2 | import { NgWizardStep } from './ng-wizard-step/ng-wizard-step'; 3 | import { ComponentRef } from '@angular/core'; 4 | import { NgWizardStepData } from './ng-wizard-step/ng-wizard-step-data.interface'; 5 | import { NgWizardOptions } from './ng-wizard-options/ng-wizard-options.interface'; 6 | 7 | describe('Utils', () => { 8 | describe('getStepTitleFromRoute', () => { 9 | it("should return the route data's title property if present", () => { 10 | expect(utils.getStepTitleFromRoute({ data: { title: 'Custom title' } })).toBe('Custom title'); 11 | }); 12 | 13 | it("should transform and return the route's path", () => { 14 | expect(utils.getStepTitleFromRoute({ path: 'some-route-path' })).toBe('Some route path'); 15 | expect(utils.getStepTitleFromRoute({ path: '-some-other_path-' })).toBe('Some other path'); 16 | expect(utils.getStepTitleFromRoute({ path: '' })).toBe(''); 17 | expect(utils.getStepTitleFromRoute({ path: 'AWESOME' })).toBe('Awesome'); 18 | expect(utils.getStepTitleFromRoute({ path: '_EvErYtHiNg-@-oNcE_' })).toBe( 19 | 'Everything @ once', 20 | ); 21 | }); 22 | }); 23 | 24 | describe('componentExtendsNgWizardStep', () => { 25 | it('should return true if the component has defined the wsIsValid, wsOnNext and wsOnPrevious methods', () => { 26 | class NgWizardStepExtender extends NgWizardStep {} 27 | 28 | expect( 29 | utils.componentImplementsNgWizardStepInterface( 30 | (new NgWizardStepExtender() as unknown) as ComponentRef, 31 | ), 32 | ).toBe(true); 33 | }); 34 | 35 | it('should return false if the component has not defined the wsIsValid, wsOnNext and wsOnPrevious methods', () => { 36 | class BasicClass {} 37 | 38 | expect( 39 | utils.componentImplementsNgWizardStepInterface( 40 | (new BasicClass() as unknown) as ComponentRef, 41 | ), 42 | ).toBe(false); 43 | }); 44 | }); 45 | 46 | const stepData = [ 47 | { path: 'some-path', componentName: 'SomeComponent' } as NgWizardStepData, 48 | { path: 'some-other-path', componentName: 'SomeOtherComponent' } as NgWizardStepData, 49 | ]; 50 | 51 | describe('getStepDataForPath', () => { 52 | it('should return the stepData for the given path', () => { 53 | expect(utils.getStepDataForPath(stepData, 'some-other-path')).toEqual(stepData[1]); 54 | }); 55 | 56 | it('should return undefined if there is no stepData for the given path', () => { 57 | expect(utils.getStepDataForPath(stepData, 'Woooops')).toBeUndefined(); 58 | }); 59 | }); 60 | 61 | describe('getStepDataForUrl', () => { 62 | it('should return the stepData for the given url', () => { 63 | expect(utils.getStepDataForUrl(stepData, '/some-path')).toEqual(stepData[0]); 64 | expect(utils.getStepDataForUrl(stepData, '/wizard/some-other-path')).toEqual(stepData[1]); 65 | expect(utils.getStepDataForUrl(stepData, '/wizard/child/some-other-path?url=no')).toEqual(stepData[1]); 66 | }); 67 | 68 | it('should return undefined if there is no stepData for the given url', () => { 69 | expect(utils.getStepDataForPath(stepData, '/does-not-exist')).toBeUndefined(); 70 | expect(utils.getStepDataForPath(stepData, '/wizard/does-not-exist?something=yes')).toBeUndefined(); 71 | }); 72 | }); 73 | 74 | describe('getDefaultWizardOptions', () => { 75 | it('should return an object of type NgWizardOptions', () => { 76 | expect(utils.getDefaultWizardOptions()).toBeDefined(); 77 | expect(utils.getDefaultWizardOptions().navBar).toBeDefined(); 78 | expect(utils.getDefaultWizardOptions().navBar.icons).toBeDefined(); 79 | expect(utils.getDefaultWizardOptions().buttons).toBeDefined(); 80 | expect(utils.getDefaultWizardOptions().buttons.previous).toBeDefined(); 81 | expect(utils.getDefaultWizardOptions().buttons.next).toBeDefined(); 82 | }); 83 | }); 84 | 85 | describe('mergeWizardOptions', () => { 86 | it('should return the default wizard options if no wizard options are provided', () => { 87 | expect(utils.mergeWizardOptions(undefined)).toEqual(utils.getDefaultWizardOptions()); 88 | }); 89 | 90 | it('should merge the default options with the provided options', () => { 91 | const options = { 92 | navBar: { 93 | icons: { 94 | previous: 'P', 95 | }, 96 | }, 97 | buttons: { 98 | previous: { 99 | label: 'P', 100 | }, 101 | }, 102 | }; 103 | expect(utils.mergeWizardOptions(options)).toEqual({ 104 | ...utils.getDefaultWizardOptions(), 105 | ...options, 106 | } as NgWizardOptions); 107 | }); 108 | }); 109 | 110 | describe('getWizardStepOptions', () => { 111 | it('should return only the title attribute when the route does not have a data attribute', () => { 112 | const route = { path: 'route-1' }; 113 | expect(utils.getWizardStepOptions(route)).toEqual({ title: 'Route 1' }); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/lib/ng-wizard.utils.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | import { ComponentRef } from '@angular/core'; 3 | import { NgWizardStepData } from './ng-wizard-step/ng-wizard-step-data.interface'; 4 | import { NgWizardOptions } from './ng-wizard-options/ng-wizard-options.interface'; 5 | import { NgWizardStepOptions } from './ng-wizard-step/ng-wizard-step-options'; 6 | 7 | /** 8 | * Returns true if the component extends the NgWizardStep class or implements the NgWizardStepInterface. 9 | * 10 | * @param componentRef The reference to the component to verify 11 | */ 12 | export function componentImplementsNgWizardStepInterface(componentRef: ComponentRef): boolean { 13 | return 'wsIsValid' in componentRef && 'wsOnNext' in componentRef && 'wsOnPrevious' in componentRef; 14 | } 15 | 16 | /** 17 | * Returns the NgWizardStepData with the given path in the stepData list or undefined if none is 18 | * found. 19 | * 20 | * @param stepData The list with NgWizardStepDatas 21 | * @param path The path you want to get the NgWizardStepData for 22 | */ 23 | export function getStepDataForPath(stepData: NgWizardStepData[], path: string) { 24 | return stepData.find(data => data.path === path); 25 | } 26 | 27 | /** 28 | * Returns the NgWizardStepData for the given url in the stepData list or undefined if none is 29 | * found. 30 | * 31 | * @param stepData The list with NgWizardStepDatas 32 | * @param url The url which you want to get the NgWizardStepData for 33 | */ 34 | export function getStepDataForUrl(stepData: NgWizardStepData[], url: string) { 35 | // gets 'path' in the url '/wizard/path?key=value' 36 | const path = url.split('/').pop().split('?')[0]; 37 | return getStepDataForPath(stepData, path); 38 | } 39 | 40 | /** 41 | * Returns the default wizard options. 42 | */ 43 | export function getDefaultWizardOptions(): NgWizardOptions { 44 | return { 45 | name: '', 46 | navBar: { 47 | icons: { 48 | previous: 'done', 49 | current: 'create', 50 | next: 'lock', 51 | }, 52 | }, 53 | buttons: { 54 | previous: { 55 | label: 'chevron_left Previous', 56 | }, 57 | next: { 58 | label: 'Next chevron_right', 59 | }, 60 | } 61 | }; 62 | } 63 | 64 | /** 65 | * Merges the wizard options in the wizard route's config with the default wizard options. 66 | * 67 | * @param wizardOptions The wizard options in the wizard route's config 68 | */ 69 | export function mergeWizardOptions(wizardOptions: {}): NgWizardOptions { 70 | if (!wizardOptions) { 71 | return getDefaultWizardOptions(); 72 | } 73 | 74 | return { ...getDefaultWizardOptions(), ...wizardOptions }; 75 | } 76 | 77 | /** 78 | * Returns the options passed to the wizard step route with an added title attribute. 79 | * 80 | * @param route The wizard step route configuration 81 | */ 82 | export function getWizardStepOptions(route: Route): NgWizardStepOptions { 83 | if (!route.data) { 84 | return { title: getStepTitleFromRoute(route) }; 85 | } 86 | 87 | return { 88 | ...route.data, 89 | title: getStepTitleFromRoute(route), 90 | }; 91 | } 92 | 93 | /** 94 | * Returns the step title based on the Route configuration. 95 | * If the route has a data.title attribute it will be returned. 96 | * Else the path will be capitalized and '-' or '_' characters will be replaces by spaces. 97 | * 98 | * @param route The Angular Route object 99 | */ 100 | export function getStepTitleFromRoute(route: Route): string { 101 | if (route.data && route.data.title) { 102 | return route.data.title; 103 | } 104 | return capitalize(insertSpaces(route.path)); 105 | } 106 | 107 | /** 108 | * Capitalizes the first character of the passed value. 109 | */ 110 | function capitalize(value: string): string { 111 | if (!value) { 112 | return value; 113 | } 114 | return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase(); 115 | } 116 | 117 | /** 118 | * Replaces '-' and '_' characters by spaces. 119 | */ 120 | function insertSpaces(value: string): string { 121 | if (!value) { 122 | return value; 123 | } 124 | return value.replace(/[-_]/g, ' ').trim(); 125 | } 126 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ng-wizard 3 | */ 4 | 5 | export * from './lib/ng-wizard.component'; 6 | export * from './lib/ng-wizard.module'; 7 | export * from './lib/ng-wizard-step/ng-wizard-step'; 8 | export * from './lib/ng-wizard.service'; 9 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/stable/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting() 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/themes/arrows.scss: -------------------------------------------------------------------------------- 1 | @import './common'; 2 | 3 | /* Adapted from Dipu Raj's SmartWizard 4 arrows theme */ 4 | 5 | nav.ng-wizard-navigation { 6 | background-color: #ddd; 7 | border: none; 8 | 9 | ul.ng-wizard-navigation-list { 10 | background: #f5f5f5; 11 | overflow: hidden; 12 | 13 | li.ng-wizard-navigation-list-item { 14 | div { 15 | padding: 5px 0 15px 45px; 16 | position: relative; 17 | display: block; 18 | border: 0; 19 | border-radius: 0; 20 | outline-style: none; 21 | color: #fff; 22 | background: #5cb85c; 23 | 24 | &:after { 25 | content: ""; 26 | display: block; 27 | width: 0; 28 | height: 0; 29 | border-top: 50px solid transparent; 30 | border-bottom: 50px solid transparent; 31 | border-left: 30px solid #5cb85c; 32 | position: absolute; 33 | top: 50%; 34 | margin-top: -50px; 35 | left: 100%; 36 | z-index: 2; 37 | } 38 | 39 | &:before { 40 | content: " "; 41 | display: block; 42 | width: 0; 43 | height: 0; 44 | border-top: 50px solid transparent; 45 | border-bottom: 50px solid transparent; 46 | border-left: 30px solid #fff; 47 | position: absolute; 48 | top: 50%; 49 | margin-top: -50px; 50 | margin-left: 1px; 51 | left: 100%; 52 | z-index: 1; 53 | } 54 | } 55 | 56 | &:first-child div { 57 | padding-left: 15px; 58 | } 59 | 60 | div.ng-wizard-navigation-link { 61 | 62 | } 63 | 64 | div.ng-wizard-navigation-active { 65 | background: #5bc0de !important; 66 | 67 | &:after { 68 | border-left: 30px solid #5bc0de !important; 69 | } 70 | } 71 | 72 | div.ng-wizard-navigation-disabled { 73 | background: #ddd !important; 74 | cursor: not-allowed; 75 | 76 | &:after { 77 | border-left: 30px solid #ddd !important; 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/themes/common.scss: -------------------------------------------------------------------------------- 1 | /* Adapted from Dipu Raj's SmartWizard 4 themes */ 2 | 3 | .ng-wizard-container { 4 | position: relative; 5 | display: block; 6 | margin: 0; 7 | padding: 0; 8 | border-radius: 5px; 9 | border: solid 1px #e9e7dc; 10 | 11 | .ng-wizard-content-container { 12 | position: relative; 13 | margin: 0; 14 | padding: 10px; 15 | border: 0 solid #D4D4D4; 16 | text-align: left; 17 | } 18 | } 19 | 20 | nav.ng-wizard-navigation { 21 | display: -webkit-box; 22 | display: -ms-flexbox; 23 | display: flex; 24 | -ms-flex-wrap: wrap; 25 | flex-wrap: wrap; 26 | padding-left: 0; 27 | margin-bottom: 0; 28 | border-bottom: 1px solid #dee2e6; 29 | 30 | 31 | ul.ng-wizard-navigation-list { 32 | list-style: none; 33 | padding: 0; 34 | margin: 0; 35 | width: 100%; 36 | 37 | li.ng-wizard-navigation-list-item { 38 | display: list-item; 39 | position: relative; 40 | float: left; 41 | cursor: pointer; 42 | 43 | div { 44 | border: none; 45 | //color: #bbb; 46 | text-decoration: none; 47 | outline-style: none; 48 | background: transparent; 49 | 50 | i.ng-wizard-icon { 51 | position: relative; 52 | top: 7px; 53 | border: solid 1px #fff; 54 | border-radius: 50%; 55 | padding: 5px; 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | div.ng-wizard-buttons-container { 63 | padding: 10px; 64 | 65 | div.ng-wizard-buttons { 66 | display: grid; 67 | grid-template-columns: 1fr 1fr; 68 | grid-template-areas: "btn-previous btn-next"; 69 | 70 | button { 71 | width: 150px; 72 | height: 35px; 73 | background-color: #6c757d; 74 | border: solid 1px #6c757d; 75 | color: #fff; 76 | border-radius: 5px; 77 | cursor: pointer; 78 | outline: none; 79 | 80 | &:hover { 81 | background-color: #5a6268; 82 | border-color: #545b62; 83 | } 84 | 85 | &.ng-wizard-button-previous { 86 | grid-area: btn-previous; 87 | justify-self: start; 88 | } 89 | 90 | &.ng-wizard-button-next { 91 | grid-area: btn-next; 92 | justify-self: end; 93 | } 94 | 95 | .ng-wizard-button-label { 96 | position: relative; 97 | top: -5px; 98 | 99 | i.ng-wizard-icon { 100 | position: relative; 101 | top: 7px; 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | /* Responsive CSS */ 109 | @media screen and (max-width: 768px) { 110 | .ng-wizard-container li.ng-wizard-navigation-list-item { 111 | float: none !important; 112 | border-right: none !important; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/themes/default.scss: -------------------------------------------------------------------------------- 1 | @import './common'; 2 | 3 | /* Adapted from Dipu Raj's SmartWizard 4 default theme */ 4 | 5 | nav.ng-wizard-navigation { 6 | ul.ng-wizard-navigation-list { 7 | li.ng-wizard-navigation-list-item { 8 | padding: 5px 20px 20px 15px; 9 | border-right: solid 1px rgba(218,215,197,.6); 10 | 11 | div { 12 | i.ng-wizard-icon { 13 | border-color: rgb(178,175,157); 14 | } 15 | 16 | &::after { 17 | content: ""; 18 | position: absolute; 19 | background: #4285F4; 20 | height: 2px; 21 | width: 100%; 22 | left: 0px; 23 | bottom: 0px; 24 | -webkit-transition: all 250ms ease 0s; 25 | transition: all 250ms ease 0s; 26 | -webkit-transform: scale(0); 27 | -ms-transform: scale(0); 28 | transform: scale(0); 29 | } 30 | } 31 | 32 | div.ng-wizard-navigation-link { 33 | border: none; 34 | color: #5cb85c; 35 | background: transparent; 36 | cursor: pointer; 37 | 38 | &::after { 39 | background: #5cb85c; 40 | -webkit-transform: scale(1); 41 | -ms-transform: scale(1); 42 | transform: scale(1); 43 | } 44 | 45 | i.ng-wizard-icon { 46 | border-color: #5cb85c; 47 | } 48 | } 49 | 50 | div.ng-wizard-navigation-active { 51 | border: none; 52 | color: #4285F4; 53 | background: transparent; 54 | cursor: pointer; 55 | 56 | &::after { 57 | -webkit-transform: scale(1); 58 | -ms-transform: scale(1); 59 | transform: scale(1); 60 | } 61 | 62 | i.ng-wizard-icon { 63 | border-color: #4285F4; 64 | } 65 | } 66 | 67 | div.ng-wizard-navigation-invalid { 68 | border: none; 69 | color: #d9534f; 70 | cursor: not-allowed; 71 | 72 | &::after { 73 | background: #d9534f; 74 | border-left-color: #f8d7da; 75 | -webkit-transform: scale(1); 76 | -ms-transform: scale(1); 77 | transform: scale(1); 78 | } 79 | } 80 | 81 | div.ng-wizard-navigation-disabled { 82 | color: #c6c3b1; 83 | cursor: not-allowed; 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/themes/dots.scss: -------------------------------------------------------------------------------- 1 | @import './common'; 2 | 3 | /* Adapted from Dipu Raj's SmartWizard 4 dots theme */ 4 | 5 | nav.ng-wizard-navigation { 6 | border: none; 7 | 8 | ul.ng-wizard-navigation-list { 9 | position: relative; 10 | background: #fff; 11 | border: 0px solid #ccc !important; 12 | list-style: none; 13 | 14 | &:before { 15 | content: " "; 16 | position: absolute; 17 | top: 47px; 18 | bottom: 0; 19 | width: 100%; 20 | height: 5px; 21 | background-color: #f5f5f5; 22 | border-radius: 3px; 23 | z-order: 0; 24 | z-index: 95; 25 | } 26 | 27 | li.ng-wizard-navigation-list-item { 28 | border: none; 29 | padding: 5px 15px; 30 | 31 | div { 32 | position: relative; 33 | text-align: center; 34 | font-weight: bold; 35 | background: transparent; 36 | border: none; 37 | color: #ccc; 38 | text-decoration: none; 39 | outline-style: none; 40 | z-index: 96; 41 | display: block; 42 | 43 | &:before { 44 | content: ' '; 45 | position: absolute; 46 | bottom: -15px; 47 | left: calc(50% - 15px); 48 | margin-top: 10px; 49 | display: block; 50 | border-radius: 50%; 51 | color: #428bca; 52 | background: #f5f5f5; 53 | border: none; 54 | width: 30px; 55 | height: 30px; 56 | text-decoration: none; 57 | z-index: 98; 58 | } 59 | 60 | &:after { 61 | content: ' '; 62 | position: relative; 63 | left: calc(50% - 8px); 64 | bottom: -7.5px; 65 | margin-top: 10px; 66 | display: block; 67 | width: 16px; 68 | height: 16px; 69 | background: #f5f5f5; 70 | border-radius: 50%; 71 | z-index: 99; 72 | } 73 | 74 | i.ng-wizard-icon { 75 | display: none; 76 | } 77 | } 78 | 79 | div.ng-wizard-navigation-active { 80 | color: #5bc0de; 81 | 82 | &:after { 83 | background: #5bc0de; 84 | } 85 | } 86 | 87 | div.ng-wizard-navigation-link { 88 | color: #5cb85c; 89 | 90 | &:after { 91 | background: #5cb85c; 92 | } 93 | } 94 | 95 | div.ng-wizard-navigation-invalid { 96 | color: #d9534f; 97 | 98 | &:after { 99 | background: #d9534f; 100 | } 101 | } 102 | 103 | div.ng-wizard-navigation-disabled { 104 | color: #eee; 105 | 106 | &:after { 107 | background: #eee; 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | @media screen and (max-width: 768px) { 115 | ul.ng-wizard-navigation-list { 116 | &:before { 117 | top: 0; 118 | bottom: 0; 119 | left: 10px; 120 | width: 5px; 121 | height: 100%; 122 | background-color: #f5f5f5; 123 | display: block; 124 | margin-right: 10px; 125 | } 126 | 127 | li.ng-wizard-navigation-list-item { 128 | margin-left: 20px; 129 | display: block; 130 | clear: both; 131 | 132 | div { 133 | text-align: left; 134 | margin-left: 0; 135 | display: block; 136 | 137 | &:before { 138 | top: 5px; 139 | left: -23px; 140 | margin-right: 10px; 141 | display: block; 142 | } 143 | 144 | &:after { 145 | top: -38px; 146 | left: -31px; 147 | margin-right: 10px; 148 | display: block; 149 | } 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /projects/ng-wizard/src/themes/line.scss: -------------------------------------------------------------------------------- 1 | .ng-wizard-navigation-container{ 2 | .ng-wizard-navigation{ 3 | margin-bottom: 18px; 4 | padding: 6px; 5 | text-align: center; 6 | border-radius: 5px; 7 | background-color: rgba(218,215,197,.4); 8 | 9 | .ng-wizard-navigation-list{ 10 | width: 100%; 11 | display: table; 12 | 13 | .ng-wizard-navigation-list-item{ 14 | position: relative; 15 | float: left; 16 | text-align: center; 17 | overflow: hidden; 18 | 19 | width: 14.28%; 20 | 21 | &:before{ 22 | position: absolute; 23 | top: 25px; 24 | left: 0; 25 | width: 50%; 26 | margin-left: -26px; 27 | content: ''; 28 | border-top: 1px dashed #BFB589; 29 | } 30 | 31 | &:after{ 32 | right: 0; 33 | left: auto; 34 | margin: 0 -25px 0 0; 35 | 36 | position: absolute; 37 | top: 25px; 38 | width: 50%; 39 | margin-left: -26px; 40 | content: ''; 41 | border-top: 1px dashed #BFB589; 42 | } 43 | 44 | .ng-wizard-navigation-icon{ 45 | position: relative!important; 46 | float: none!important; 47 | margin-right: auto!important; 48 | margin-left: auto!important; 49 | 50 | font-size: 21px; 51 | line-height: 37px; 52 | display: block; 53 | width: 42px!important; 54 | height: 42px!important; 55 | margin: 4px 11px 7px 0; 56 | text-align: center!important; 57 | text-decoration: none; 58 | color: #BFB589; 59 | border: 1px solid #BFB589; 60 | border-radius: 50%; 61 | } 62 | 63 | .ng-wizard-navigation-label{ 64 | font-size: 10px; 65 | font-weight: 700; 66 | text-decoration: none; 67 | letter-spacing: 1px; 68 | text-transform: uppercase; 69 | word-wrap: normal; 70 | -webkit-hyphens: auto; 71 | -ms-hyphens: auto; 72 | hyphens: auto; 73 | color: #BFB589; 74 | } 75 | } 76 | } 77 | 78 | .ng-wizard-navigation-active { 79 | .ng-wizard-navigation-icon { 80 | color: #4EA553 !important; 81 | border-color: #4EA553 !important; 82 | } 83 | 84 | .ng-wizard-navigation-label { 85 | color: #4EA553 !important; 86 | } 87 | } 88 | 89 | .ng-wizard-navigation-link { 90 | &:hover { 91 | cursor: pointer; 92 | 93 | .ng-wizard-navigation-icon { 94 | color: #4EA553 !important; 95 | } 96 | } 97 | 98 | .ng-wizard-navigation-label{ 99 | color: #4EA553 !important; 100 | } 101 | } 102 | } 103 | } 104 | 105 | .ng-wizard-buttons { 106 | display: block; 107 | float: none !important; 108 | text-align: right; 109 | 110 | .ng-wizard-button-next{ 111 | font-size: 13px; 112 | font-weight: 400; 113 | line-height: 32px; 114 | display: inline-block; 115 | padding: 0 18px; 116 | vertical-align: middle; 117 | letter-spacing: normal; 118 | word-spacing: normal; 119 | color: #FFF; 120 | border: 0; 121 | border-radius: 5px; 122 | text-shadow: 1px 1px 1px rgba(0,0,0,.2); 123 | border-color: #4EA553; 124 | background-color: #4EA553; 125 | 126 | &:hover{ 127 | transition: color cubic-bezier(.215,.61,.355,1) .3s,background-color cubic-bezier(.215,.61,.355,1) .3s,border cubic-bezier(.215,.61,.355,1) .3s; 128 | text-decoration: none; 129 | color: #FFF; 130 | border-color: #2d6030; 131 | background-color: #2d6030; 132 | text-shadow: 1px 1px 1px rgba(0,0,0,.2); 133 | } 134 | } 135 | .ng-wizard-button-previous, .ng-wizard-button-extra { 136 | font-size: 13px; 137 | font-weight: 400; 138 | line-height: 32px; 139 | display: inline-block; 140 | padding: 0 18px; 141 | vertical-align: middle; 142 | letter-spacing: normal; 143 | word-spacing: normal; 144 | border-radius: 5px; 145 | margin-right: 15px; 146 | color: #BFB589; 147 | border: solid 1px #BFB589; 148 | background-color: #ffffff; 149 | 150 | &:hover{ 151 | transition: color cubic-bezier(.215,.61,.355,1) .3s,background-color cubic-bezier(.215,.61,.355,1) .3s,border cubic-bezier(.215,.61,.355,1) .3s; 152 | text-decoration: none; 153 | text-shadow: 1px 1px 1px rgba(0,0,0,.2); 154 | color: #FFF; 155 | border-color: #BFB589; 156 | background-color: #BFB589; 157 | } 158 | } 159 | } 160 | 161 | -------------------------------------------------------------------------------- /projects/ng-wizard/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2018" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "skipTemplateCodegen": true, 22 | "strictMetadataEmit": true, 23 | "fullTemplateTypeCheck": true, 24 | "strictInjectionParameters": true, 25 | "enableResourceInlining": true 26 | }, 27 | "exclude": [ 28 | "src/test.ts", 29 | "**/*.spec.ts" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /projects/ng-wizard/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "enableIvy": false 5 | } 6 | } -------------------------------------------------------------------------------- /projects/ng-wizard/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/ng-wizard/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "ng", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "ng", 14 | "kebab-case" 15 | ], 16 | "quotemark": [ 17 | true, 18 | "single", 19 | "avoid-escape" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /readme-img/arrows-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/readme-img/arrows-theme.png -------------------------------------------------------------------------------- /readme-img/default-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/readme-img/default-theme.png -------------------------------------------------------------------------------- /readme-img/dots-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/readme-img/dots-theme.png -------------------------------------------------------------------------------- /readme-img/ng-wizard-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/readme-img/ng-wizard-200.png -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { NgWizardComponent } from '../../projects/ng-wizard/src/lib/ng-wizard.component'; 4 | import { Step1Component } from './step1/step1.component'; 5 | import { Step2Component } from './step2/step2.component'; 6 | import { Step3Component } from './step3/step3.component'; 7 | import { Step4Component } from './step4/step4.component'; 8 | import { Step5Component } from './step5/step5.component'; 9 | import { NestedExampleComponent } from './nested-example/nested-example.component'; 10 | 11 | const wizardConfig = { 12 | navBar: { 13 | icons: { 14 | previous: 'cake', 15 | current: 'star', 16 | next: 'pool', 17 | }, 18 | }, 19 | buttons: { 20 | previous: { 21 | label: 'arrow_left Previous', 22 | }, 23 | next: { 24 | label: 'Next arrow_right', 25 | }, 26 | } 27 | }; 28 | 29 | const confirmationStepOptions = { 30 | buttons: { 31 | previous: { 32 | label: 'create Edit', 33 | }, 34 | next: { 35 | label: 'Confirm done_all', 36 | } 37 | } 38 | }; 39 | 40 | const doneStepOptions = { 41 | icon: 'done_all', 42 | buttons: { 43 | previous: { 44 | hidden: true, 45 | }, 46 | }, 47 | disableNavigation: true, 48 | }; 49 | 50 | const routes: Routes = [ 51 | { path: 'nested/:id', component: NestedExampleComponent, children: [ 52 | { path: 'wizard', component: NgWizardComponent, children: [ 53 | { path: 'personal', component: Step1Component }, 54 | { path: 'developer', component: Step2Component }, 55 | { path: 'angular', component: Step3Component }, 56 | { path: 'confirmation', component: Step4Component, data: confirmationStepOptions }, 57 | { path: 'done', component: Step5Component, data: doneStepOptions }, 58 | { path: '**', redirectTo: 'personal' }, 59 | ], data: { name: 'nestedExample', ...wizardConfig}} 60 | ]}, 61 | { path: '', component: NgWizardComponent, children: [ 62 | { path: 'personal', component: Step1Component }, 63 | { path: 'developer', component: Step2Component }, 64 | { path: 'angular', component: Step3Component }, 65 | { path: 'confirmation', component: Step4Component, data: confirmationStepOptions }, 66 | { path: 'done', component: Step5Component, data: doneStepOptions }, 67 | { path: '**', redirectTo: 'personal' }, 68 | ], data: { name: 'ngWizard' }}, 69 | { path: '**', redirectTo: '' }, 70 | ]; 71 | 72 | @NgModule({ 73 | imports: [RouterModule.forRoot(routes, { useHash: true })], 74 | exports: [RouterModule] 75 | }) 76 | export class AppRoutingModule { } 77 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .wizard-container { 2 | max-width: 800px; 3 | margin: auto; 4 | } 5 | 6 | ::ng-deep label { 7 | font-weight: bold; 8 | margin-right: 15px; 9 | } 10 | 11 | ::ng-deep input.ng-invalid.ng-touched { 12 | border: solid 1px red; 13 | } 14 | 15 | ::ng-deep span.error { 16 | color: red; 17 | margin-left: 15px; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { } 9 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { NgWizardModule } from '../../projects/ng-wizard/src/lib/ng-wizard.module'; 7 | import { Step1Component } from './step1/step1.component'; 8 | import { Step2Component } from './step2/step2.component'; 9 | import { Step3Component } from './step3/step3.component'; 10 | import { Step4Component } from './step4/step4.component'; 11 | import { Step5Component } from './step5/step5.component'; 12 | import { ReactiveFormsModule } from '@angular/forms'; 13 | import { NestedExampleComponent } from './nested-example/nested-example.component'; 14 | 15 | @NgModule({ 16 | declarations: [ 17 | AppComponent, 18 | Step1Component, 19 | Step2Component, 20 | Step3Component, 21 | Step4Component, 22 | Step5Component, 23 | NestedExampleComponent 24 | ], 25 | imports: [ 26 | BrowserModule, 27 | AppRoutingModule, 28 | NgWizardModule, 29 | ReactiveFormsModule, 30 | ], 31 | providers: [], 32 | bootstrap: [AppComponent] 33 | }) 34 | export class AppModule { } 35 | -------------------------------------------------------------------------------- /src/app/app.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AppService = TestBed.get(AppService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class AppService { 7 | public formValues = { 8 | lastName: '', 9 | firstName: '', 10 | gitUser: '', 11 | favoriteProject: '', 12 | ngVersion: 0, 13 | ngRouter: false, 14 | }; 15 | 16 | public setFormValues(values: any) { 17 | this.formValues = { ...this.formValues, ...values }; 18 | } 19 | 20 | public step1IsValid() { 21 | return this.formValues.lastName && this.formValues.firstName; 22 | } 23 | 24 | public step2IsValid() { 25 | return this.step1IsValid(); 26 | } 27 | 28 | public step3IsValid() { 29 | return this.step2IsValid() && this.formValues.ngVersion >= 7; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/nested-example/nested-example.component.html: -------------------------------------------------------------------------------- 1 |

Nested example with dynamic route (id: {{id}})

2 | 3 | -------------------------------------------------------------------------------- /src/app/nested-example/nested-example.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-nested-example', 6 | templateUrl: './nested-example.component.html', 7 | }) 8 | export class NestedExampleComponent implements OnInit { 9 | id: string; 10 | 11 | constructor(private route: ActivatedRoute) { } 12 | 13 | ngOnInit(): void { 14 | this.id = this.route.snapshot.paramMap.get('id'); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/step1/step1.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Personal information

3 |

4 | 5 | 6 |

7 |

8 | 9 | 10 |

11 |
12 | -------------------------------------------------------------------------------- /src/app/step1/step1.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgWizardStep } from '../../../projects/ng-wizard/src/lib/ng-wizard-step/ng-wizard-step'; 3 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 4 | import { AppService } from '../app.service'; 5 | 6 | @Component({ 7 | selector: 'app-step1', 8 | templateUrl: './step1.component.html', 9 | }) 10 | export class Step1Component extends NgWizardStep implements OnInit { 11 | public form = new FormGroup({ 12 | lastName: new FormControl('', Validators.required), 13 | firstName: new FormControl('', Validators.required), 14 | }); 15 | 16 | constructor(private service: AppService) { 17 | super(); 18 | } 19 | 20 | ngOnInit() { 21 | this.form.get('lastName').setValue(this.service.formValues.lastName); 22 | this.form.get('firstName').setValue(this.service.formValues.firstName); 23 | } 24 | 25 | wsIsValid() { 26 | this.form.get('lastName').markAsTouched(); 27 | this.form.get('firstName').markAsTouched(); 28 | 29 | return this.form.valid; 30 | } 31 | 32 | wsOnNext() { 33 | this.service.setFormValues(this.form.value); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/step2/step2.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Developer profile

3 |

4 | 5 | 6 |

7 |

8 | 9 | 10 |

11 |
12 | -------------------------------------------------------------------------------- /src/app/step2/step2.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgWizardStep } from '../../../projects/ng-wizard/src/lib/ng-wizard-step/ng-wizard-step'; 3 | import { FormControl, FormGroup } from '@angular/forms'; 4 | import { AppService } from '../app.service'; 5 | import { Router } from '@angular/router'; 6 | 7 | @Component({ 8 | selector: 'app-step2', 9 | templateUrl: './step2.component.html', 10 | }) 11 | export class Step2Component extends NgWizardStep implements OnInit { 12 | public form = new FormGroup({ 13 | gitUser: new FormControl(''), 14 | favoriteProject: new FormControl(''), 15 | }); 16 | 17 | constructor(private service: AppService, private router: Router) { 18 | super(); 19 | } 20 | 21 | ngOnInit() { 22 | if (!this.service.step1IsValid()) { 23 | return this.router.navigate(['personal']); 24 | } 25 | 26 | this.form.get('gitUser').setValue(this.service.formValues.gitUser); 27 | this.form.get('favoriteProject').setValue(this.service.formValues.favoriteProject); 28 | } 29 | 30 | wsOnNext() { 31 | this.service.setFormValues(this.form.value); 32 | } 33 | 34 | wsOnPrevious() { 35 | this.service.setFormValues(this.form.value); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/step3/step3.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Angular usage

3 |

4 | 5 | 6 | 8 | Version must be equal to or greater than 9. 9 |

10 |

11 | 12 | 13 |

14 |
15 | -------------------------------------------------------------------------------- /src/app/step3/step3.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgWizardStep } from '../../../projects/ng-wizard/src/lib/ng-wizard-step/ng-wizard-step'; 3 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 4 | import { AppService } from '../app.service'; 5 | import { Router } from '@angular/router'; 6 | 7 | @Component({ 8 | selector: 'app-step3', 9 | templateUrl: './step3.component.html', 10 | }) 11 | export class Step3Component extends NgWizardStep implements OnInit { 12 | public form = new FormGroup({ 13 | ngVersion: new FormControl('', [Validators.required, this.validateNgVersion]), 14 | ngRouter: new FormControl(''), 15 | }); 16 | 17 | constructor(private service: AppService, private router: Router) { 18 | super(); 19 | } 20 | 21 | ngOnInit() { 22 | if (!this.service.step2IsValid()) { 23 | return this.router.navigate(['developer']); 24 | } 25 | 26 | this.form.get('ngVersion').setValue(this.service.formValues.ngVersion); 27 | this.form.get('ngRouter').setValue(this.service.formValues.ngRouter); 28 | } 29 | 30 | wsIsValid() { 31 | this.form.get('ngVersion').markAsTouched(); 32 | return this.form.valid; 33 | } 34 | 35 | wsOnNext() { 36 | this.service.setFormValues(this.form.value); 37 | } 38 | 39 | wsOnPrevious() { 40 | this.service.setFormValues(this.form.value); 41 | } 42 | 43 | private validateNgVersion(control: FormControl) { 44 | return control.value && control.value >= 9 ? null : { validateNgVersion: { valid: false } }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/step4/step4.component.html: -------------------------------------------------------------------------------- 1 |

Confirmation

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
Last name{{values.lastName}}
First name{{values.firstName}}
GitHub username{{values.gitUser}}
Favorite GitHub project{{values.favoriteProject}}
Angular version{{values.ngVersion}}
Angular router{{values.ngRouter ? 'Yes' : 'No'}}
28 | -------------------------------------------------------------------------------- /src/app/step4/step4.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgWizardStep } from '../../../projects/ng-wizard/src/lib/ng-wizard-step/ng-wizard-step'; 3 | import { AppService } from '../app.service'; 4 | import { Router } from '@angular/router'; 5 | 6 | @Component({ 7 | selector: 'app-step4', 8 | templateUrl: './step4.component.html', 9 | }) 10 | export class Step4Component extends NgWizardStep implements OnInit { 11 | public values; 12 | 13 | constructor(private service: AppService, private router: Router) { 14 | super(); 15 | } 16 | 17 | ngOnInit() { 18 | if (!this.service.step3IsValid()) { 19 | return this.router.navigate(['angular']); 20 | } 21 | 22 | this.values = this.service.formValues; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/step5/step5.component.html: -------------------------------------------------------------------------------- 1 |

All steps completed!

2 |
3 | Navigation is disabled 4 | -------------------------------------------------------------------------------- /src/app/step5/step5.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgWizardStep } from '../../../projects/ng-wizard/src/lib/ng-wizard-step/ng-wizard-step'; 3 | 4 | @Component({ 5 | selector: 'app-step5', 6 | templateUrl: './step5.component.html', 7 | }) 8 | export class Step5Component extends NgWizardStep { 9 | constructor() { 10 | super(); 11 | } 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/ng-wizard-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/src/assets/ng-wizard-transparent.png -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdap/ng-wizard/352c321801c556ca082b920b23c27a5330d41177/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NgWizard 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @import './projects/ng-wizard/src/themes/default'; 2 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "main.ts", 9 | "polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "downlevelIteration": true, 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "ng-wizard": [ 23 | "dist/ng-wizard" 24 | ], 25 | "ng-wizard/*": [ 26 | "dist/ng-wizard/*" 27 | ] 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-redundant-jsdoc": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "unified-signatures": true, 111 | "variable-name": false, 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ], 120 | "no-output-on-prefix": true, 121 | "no-inputs-metadata-property": true, 122 | "no-outputs-metadata-property": true, 123 | "no-host-metadata-property": true, 124 | "no-input-rename": true, 125 | "no-output-rename": true, 126 | "use-lifecycle-interface": true, 127 | "use-pipe-transform-interface": true, 128 | "component-class-suffix": true, 129 | "directive-class-suffix": true 130 | } 131 | } 132 | --------------------------------------------------------------------------------