├── .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 | 
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 | 
211 |
212 | **Arrows**
213 | 
214 |
215 | **Dots**
216 | 
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 |
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 |
3 |
4 |
5 |
10 |
11 |
12 | {{ stepData.options.title }}
13 |
14 |
15 |
16 |
19 |
20 |
21 | {{ stepData.options.title }}
22 |
23 |
24 |
25 | currentStepData?.order"
26 | class="ng-wizard-navigation-disabled">
27 |
28 |
29 | {{ stepData.options.title }}
30 |
31 |
32 |
33 |
34 |
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 | Last name*
5 |
6 |
7 |
8 | First name*
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 | GitHub username
5 |
6 |
7 |
8 | Favorite GitHub project
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 | Your project's Angular version*
5 |
6 |
8 | Version must be equal to or greater than 9.
9 |
10 |
11 |
12 | Your projects includes Angular's Router
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 | Last name
5 | {{values.lastName}}
6 |
7 |
8 | First name
9 | {{values.firstName}}
10 |
11 |
12 | GitHub username
13 | {{values.gitUser}}
14 |
15 |
16 | Favorite GitHub project
17 | {{values.favoriteProject}}
18 |
19 |
20 | Angular version
21 | {{values.ngVersion}}
22 |
23 |
24 | Angular router
25 | {{values.ngRouter ? 'Yes' : 'No'}}
26 |
27 |
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 |
--------------------------------------------------------------------------------