├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── angular.json
├── e2e
├── protractor.conf.js
├── src
│ ├── app.e2e-spec.ts
│ └── app.po.ts
└── tsconfig.json
├── logo.png
├── package-lock.json
├── package.json
├── projects
├── angular2-promise-buttons-demo
│ ├── e2e
│ │ ├── protractor.conf.js
│ │ └── tsconfig.json
│ ├── karma.conf.js
│ ├── src
│ │ ├── app
│ │ │ ├── app.component.css
│ │ │ ├── app.component.html
│ │ │ ├── app.component.spec.ts
│ │ │ ├── app.component.ts
│ │ │ └── app.module.ts
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── environments
│ │ │ ├── environment.prod.ts
│ │ │ └── environment.ts
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── polyfills.ts
│ │ └── styles.scss
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── tslint.json
└── angular2-promise-buttons
│ ├── karma.conf.js
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ ├── angular2-promise-buttons.module.ts
│ ├── default-promise-btn-config.ts
│ ├── index.ts
│ ├── promise-btn-config.ts
│ ├── promise-btn.directive.spec.ts
│ ├── promise-btn.directive.ts
│ ├── test.ts
│ └── user-cfg.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ ├── tsconfig.spec.json
│ ├── tslint.json
│ └── yarn.lock
├── scripts
└── copy-readme-to-demo.js
├── tsconfig.build-lib.json
├── tsconfig.json
├── wallaby.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://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 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [johannesjo] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events.json
15 | speed-measure-plugin.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | # System Files
45 | .DS_Store
46 | Thumbs.db
47 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "12"
4 |
5 | cache:
6 | yarn: true
7 |
8 |
9 | script:
10 | - yarn
11 | - yarn test-coverage
12 |
13 | addons:
14 | apt:
15 | sources:
16 | - ubuntu-toolchain-r-test
17 | # required by node-gyp to build some packages
18 | packages:
19 | - g++-4.8
20 |
21 | after_success: 'npm run coveralls'
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [4.0.1](https://github.com/johannesjo/angular2-promise-buttons/compare/v4.0.0...v4.0.1) (2019-11-18)
3 |
4 |
5 |
6 |
7 | # [4.0.0](https://github.com/johannesjo/angular2-promise-buttons/compare/v3.0.0...v4.0.0) (2019-06-08)
8 |
9 |
10 | ### Bug Fixes
11 |
12 | * typing error ([a26ebec](https://github.com/johannesjo/angular2-promise-buttons/commit/a26ebec))
13 |
14 |
15 | ### Features
16 |
17 | * support regular booleans ([be350c1](https://github.com/johannesjo/angular2-promise-buttons/commit/be350c1))
18 |
19 |
20 |
21 |
22 | ## [3.0.1](https://github.com/johannesjo/angular2-promise-buttons/compare/v3.0.0...v3.0.1) (2019-01-20)
23 |
24 |
25 | ### Features
26 |
27 | * support regular booleans ([be350c1](https://github.com/johannesjo/angular2-promise-buttons/commit/be350c1))
28 |
29 |
30 |
31 |
32 | # [3.0.0](https://github.com/johannesjo/angular2-promise-buttons/compare/v2.1.1...v3.0.0) (2018-11-11)
33 |
34 |
35 |
36 |
37 | ## [2.1.1](https://github.com/johannesjo/angular2-promise-buttons/compare/v2.1.0...v2.1.1) (2018-05-19)
38 |
39 |
40 |
41 |
42 | # [2.1.0](https://github.com/johannesjo/angular2-promise-buttons/compare/v2.0.1...v2.1.0) (2018-05-12)
43 |
44 |
45 | ### Features
46 |
47 | * update dist files [#22](https://github.com/johannesjo/angular2-promise-buttons/issues/22) ([46d7e1c](https://github.com/johannesjo/angular2-promise-buttons/commit/46d7e1c))
48 | * update to angular 6 [#22](https://github.com/johannesjo/angular2-promise-buttons/issues/22) ([94f25e2](https://github.com/johannesjo/angular2-promise-buttons/commit/94f25e2))
49 |
50 |
51 |
52 |
53 | ## [2.0.1](https://github.com/johannesjo/angular2-promise-buttons/compare/v2.0.0...v2.0.1) (2017-11-15)
54 |
55 |
56 |
57 |
58 | # [2.0.0](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.1.0...v2.0.0) (2017-11-03)
59 |
60 |
61 | ### Features
62 |
63 | * move to angular5 ([562406b](https://github.com/johannesjo/angular2-promise-buttons/commit/562406b))
64 |
65 |
66 |
67 |
68 | # [1.1.0](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.13...v1.1.0) (2017-10-27)
69 |
70 |
71 |
72 |
73 | ## [1.0.13](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.12...v1.0.13) (2017-07-19)
74 |
75 |
76 |
77 |
78 | ## [1.0.12](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.11...v1.0.12) (2017-07-19)
79 |
80 |
81 | ### Features
82 |
83 | * remove bluebird dependency ([9798bd0](https://github.com/johannesjo/angular2-promise-buttons/commit/9798bd0))
84 |
85 |
86 |
87 |
88 | ## [1.0.11](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.10...v1.0.11) (2017-07-18)
89 |
90 |
91 |
92 |
93 | ## [1.0.10](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.9...v1.0.10) (2017-07-17)
94 |
95 |
96 |
97 |
98 | ## [1.0.9](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.7...v1.0.9) (2017-07-17)
99 |
100 |
101 | ### Bug Fixes
102 |
103 | * add missing changelog ([0408a91](https://github.com/johannesjo/angular2-promise-buttons/commit/0408a91))
104 | * weird issue making promiseBtn break inside form if handleCurrentBtnOnly is true by adding a timeout [#7](https://github.com/johannesjo/angular2-promise-buttons/issues/7) ([67b9870](https://github.com/johannesjo/angular2-promise-buttons/commit/67b9870))
105 |
106 |
107 | ### Features
108 |
109 | * add example for an observable ([54965a0](https://github.com/johannesjo/angular2-promise-buttons/commit/54965a0))
110 | * beautify demo page ([1205fee](https://github.com/johannesjo/angular2-promise-buttons/commit/1205fee))
111 | * beautify demo page some more ([27e87f0](https://github.com/johannesjo/angular2-promise-buttons/commit/27e87f0))
112 |
113 |
114 |
115 |
116 | ## [1.0.8](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.7...v1.0.8) (2017-07-17)
117 |
118 |
119 | ### Bug Fixes
120 |
121 | * add missing changelog ([0408a91](https://github.com/johannesjo/angular2-promise-buttons/commit/0408a91))
122 | * weird issue making promiseBtn break inside form if handleCurrentBtnOnly is true by adding a timeout [#7](https://github.com/johannesjo/angular2-promise-buttons/issues/7) ([67b9870](https://github.com/johannesjo/angular2-promise-buttons/commit/67b9870))
123 |
124 |
125 | ### Features
126 |
127 | * add example for an observable ([54965a0](https://github.com/johannesjo/angular2-promise-buttons/commit/54965a0))
128 | * beautify demo page ([1205fee](https://github.com/johannesjo/angular2-promise-buttons/commit/1205fee))
129 | * beautify demo page some more ([27e87f0](https://github.com/johannesjo/angular2-promise-buttons/commit/27e87f0))
130 |
131 |
132 |
133 |
134 | ## [1.0.8](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.7...v1.0.8) (2017-06-02)
135 |
136 |
137 | ### Bug Fixes
138 |
139 | * weird issue making promiseBtn break inside form if handleCurrentBtnOnly is true by adding a timeout [#7](https://github.com/johannesjo/angular2-promise-buttons/issues/7) ([67b9870](https://github.com/johannesjo/angular2-promise-buttons/commit/67b9870))
140 |
141 |
142 | ### Features
143 |
144 | * beautify demo page ([1205fee](https://github.com/johannesjo/angular2-promise-buttons/commit/1205fee))
145 | * beautify demo page some more ([27e87f0](https://github.com/johannesjo/angular2-promise-buttons/commit/27e87f0))
146 |
147 |
148 |
149 |
150 | ## [1.0.7](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.6...v1.0.7) (2017-05-11)
151 |
152 |
153 | ### Features
154 |
155 | * add angular2 compatibility [#3](https://github.com/johannesjo/angular2-promise-buttons/issues/3) ([52a98e9](https://github.com/johannesjo/angular2-promise-buttons/commit/52a98e9))
156 |
157 |
158 |
159 |
160 | ## [1.0.6](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.5...v1.0.6) (2017-05-10)
161 |
162 |
163 | ### Features
164 |
165 | * simplify code ([009bca9](https://github.com/johannesjo/angular2-promise-buttons/commit/009bca9))
166 |
167 |
168 |
169 |
170 | ## [1.0.5](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.4...v1.0.5) (2017-05-10)
171 |
172 |
173 | ### Features
174 |
175 | * add proper aot support ([7e0186a](https://github.com/johannesjo/angular2-promise-buttons/commit/7e0186a))
176 |
177 |
178 |
179 |
180 | ## [1.0.4](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.3...v1.0.4) (2017-05-10)
181 |
182 |
183 |
184 |
185 | ## [1.0.3](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.2...v1.0.3) (2017-05-10)
186 |
187 |
188 |
189 |
190 | ## [1.0.2](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.1...v1.0.2) (2017-05-09)
191 |
192 |
193 |
194 |
195 | ## [1.0.1](https://github.com/johannesjo/angular2-promise-buttons/compare/v1.0.0...v1.0.1) (2017-05-09)
196 |
197 |
198 |
199 |
200 | # [1.0.0](https://github.com/johannesjo/angular2-promise-buttons/compare/v0.1.5...v1.0.0) (2017-05-09)
201 |
202 |
203 | ### Bug Fixes
204 |
205 | * add jasmine types again to tsconfig ([e59f8bb](https://github.com/johannesjo/angular2-promise-buttons/commit/e59f8bb))
206 | * aot and prod build not working ([5e3fd11](https://github.com/johannesjo/angular2-promise-buttons/commit/5e3fd11))
207 | * build tsconfig for building the module ([885b185](https://github.com/johannesjo/angular2-promise-buttons/commit/885b185))
208 | * forRoot containing conditional logic ([a6b52f6](https://github.com/johannesjo/angular2-promise-buttons/commit/a6b52f6))
209 |
210 |
211 | ### Features
212 |
213 | * add readme ([52f84a7](https://github.com/johannesjo/angular2-promise-buttons/commit/52f84a7))
214 | * use Renderer2 for adding and removing classes ([e2c9555](https://github.com/johannesjo/angular2-promise-buttons/commit/e2c9555))
215 |
216 |
217 |
218 |
219 | ## [0.1.5](https://github.com/johannesjo/angular2-promise-buttons/compare/v0.1.4...v0.1.5) (2017-05-05)
220 |
221 |
222 |
223 |
224 | ## [0.1.4](https://github.com/johannesjo/angular2-promise-buttons/compare/v0.1.3...v0.1.4) (2017-05-05)
225 |
226 |
227 |
228 |
229 | ## [0.1.3](https://github.com/johannesjo/angular2-promise-buttons/compare/v0.1.2...v0.1.3) (2017-05-05)
230 |
231 |
232 |
233 |
234 | ## [0.1.2](https://github.com/johannesjo/angular2-promise-buttons/compare/v0.1.1...v0.1.2) (2017-05-05)
235 |
236 |
237 |
238 |
239 | ## 0.1.1 (2017-05-05)
240 |
241 |
242 | ### Features
243 |
244 | * make building the module work ([8761ef9](https://github.com/johannesjo/angular2-promise-buttons/commit/8761ef9))
245 |
246 |
247 |
248 |
249 | ## 0.1.1 (2017-05-05)
250 |
251 |
252 | ### Features
253 |
254 | * make building the module work ([bc36932](https://github.com/johannesjo/angular2-promise-buttons/commit/bc36932))
255 |
256 |
257 |
258 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute to this repository
2 | Create a fork of this repository and check it out.
3 |
4 | To make everything work you need to symlink the src directory to the app folder once. You can do this manually or by running `npm run link-mod`.
5 |
6 | Once this is done you can run `npm start` to start the development server.
7 |
8 | If you implement a new feature it is always a good idea to also add an example of it to the demo application.
9 |
10 | ## Commit guidelines
11 | In general this repo tries to adhere to the [angular commit guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit).
12 |
13 | ## Dev tasks
14 | There are several scripts defined in the package.json
15 |
16 | `npm start`: Starts the development server.
17 |
18 | `npm run` **test-watch**: Runs the unit tests via karma.
19 |
20 | `npm run` **test**: Runs the unit tests once.
21 |
22 | `npm run` **test-coverage**: Runs the unit tests with a coverage report.
23 |
24 | `npm run` **build**: Creates a compiled version of your library inside the dist folder.
25 |
26 | `npm run` **demo.deploy**:
27 | Builds the demo app to demo/dist, copies the readme to it and publishes everything to github pages.
28 |
29 | `npm run` **release.changelog**:
30 | Creates a changelog based on the [angular commit conventions](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).
31 |
32 | `npm run` **link-mod**: Creates a symlink to the module inside the demo/src folder. This is required for compiling the app with aot.
33 |
34 | `npm run` **lint**: Lints all demo and library files
35 |
36 | `npm run` **e2e**: Runs the end2end tests.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Johannes Millan
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 |

2 |
3 |
4 |
5 |
7 |
8 |
10 |
11 |
13 |
14 |
16 |
17 |
18 |
19 |
20 | *angular2-promise-buttons* is a simple module that let's you add a loading indicator to a button of your choice. Check out the [demo](http://johannesjo.github.io/angular2-promise-buttons/#demo)!
21 |
22 | [Bug-reports or feature request](https://github.com/johannesjo/angular2-promise-buttons/issues) as well as any other kind of **feedback is highly welcome!**
23 |
24 | ## Getting started
25 | Install it via npm:
26 | ```
27 | npm install angular2-promise-buttons -S
28 | ```
29 |
30 | And add it as a dependency to your main module
31 | ```typescript
32 | import {Angular2PromiseButtonModule} from 'angular2-promise-buttons';
33 |
34 | @NgModule({
35 | imports: [
36 | Angular2PromiseButtonModule.forRoot(),
37 | ],
38 | })
39 | export class MainAppModule {
40 | }
41 | ```
42 | Using the buttons is easy. Just pass a promise to the directive:
43 | ```html
44 |
46 | ```
47 | ```typescript
48 | export class SomeComponent {
49 | // some example async action, but this works with any promise
50 | someAction(){
51 | this.promiseSetBySomeAction = new Promise((resolve, reject) => {
52 | setTimeout(resolve, 2000);
53 | });
54 | }
55 | }
56 |
57 | ```
58 |
59 | ## Styling the button
60 | To give you maximum flexibility there are no base styles coming with the directive, but it is easy to fix that! There are lots of free css-spinners out there. Just find one of your liking and add the css to your global stylesheet.
61 |
62 | **Ressources:**
63 | * http://cssload.net/
64 | * http://projects.lukehaas.me/css-loaders/
65 | * http://tobiasahlin.com/spinkit/
66 |
67 | There are selectors you can use to style. There is the `.is-loading` class on the button, which is set, when the promise is pending and there is the `` element inside the button.
68 |
69 |
70 | ## Configuration
71 | Configuration is done via the forRoot method of the promise button module:
72 | ```typescript
73 | import {Angular2PromiseButtonModule} from 'angular2-promise-buttons';
74 |
75 | @NgModule({
76 | imports: [
77 | Angular2PromiseButtonModule
78 | .forRoot({
79 | // your custom config goes here
80 | spinnerTpl: '',
81 | // disable buttons when promise is pending
82 | disableBtn: true,
83 | // the class used to indicate a pending promise
84 | btnLoadingClass: 'is-loading',
85 | // only disable and show is-loading class for clicked button,
86 | // even when they share the same promise
87 | handleCurrentBtnOnly: false,
88 | }),
89 | ],
90 | })
91 | export class MainAppModule {
92 | }
93 | ```
94 |
95 | ## Using observables
96 | When you're using the module with observables make sure to pass a subscription to the directive rather than an observable directly.
97 | ```typescript
98 | const FAKE_FACTORY = {
99 | initObservable: (): Observable => {
100 | return new Observable(observer => {
101 | setTimeout(() => {
102 | observer.complete();
103 | }, 4000);
104 | });
105 | }
106 | };
107 |
108 | // DO:
109 | const observable = FAKE_FACTORY.initObservable();
110 | this.passedToDirective = observable.subscribe(
111 | // ...
112 | );
113 |
114 | // DON'T DO:
115 | const observable = FAKE_FACTORY.initObservable();
116 | this.passedToDirective = observable;
117 |
118 | ```
119 |
120 | ## Using booleans
121 | Is now also possible.
122 | ```html
123 |
125 | ```
126 | ## Contributing
127 | Contribution guidelines: [CONTRIBUTING.md](https://github.com/johannesjo/angular2-promise-buttons/blob/master/CONTRIBUTING.md)
128 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "angular2-promise-buttons-demo": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss"
11 | }
12 | },
13 | "root": "projects/angular2-promise-buttons-demo",
14 | "sourceRoot": "projects/angular2-promise-buttons-demo/src",
15 | "prefix": "app",
16 | "architect": {
17 | "build": {
18 | "builder": "@angular-devkit/build-angular:browser",
19 | "options": {
20 | "outputPath": "../../dist/demo",
21 | "index": "projects/angular2-promise-buttons-demo/src/index.html",
22 | "main": "projects/angular2-promise-buttons-demo/src/main.ts",
23 | "polyfills": "projects/angular2-promise-buttons-demo/src/polyfills.ts",
24 | "tsConfig": "projects/angular2-promise-buttons-demo/tsconfig.app.json",
25 | "assets": [
26 | "projects/angular2-promise-buttons-demo/src/favicon.ico",
27 | "projects/angular2-promise-buttons-demo/src/assets"
28 | ],
29 | "styles": [
30 | "projects/angular2-promise-buttons-demo/src/styles.scss"
31 | ],
32 | "scripts": [],
33 | "vendorChunk": true,
34 | "extractLicenses": false,
35 | "buildOptimizer": false,
36 | "sourceMap": true,
37 | "optimization": false,
38 | "namedChunks": true
39 | },
40 | "configurations": {
41 | "production": {
42 | "fileReplacements": [
43 | {
44 | "replace": "projects/angular2-promise-buttons-demo/src/environments/environment.ts",
45 | "with": "projects/angular2-promise-buttons-demo/src/environments/environment.prod.ts"
46 | }
47 | ],
48 | "optimization": true,
49 | "outputHashing": "all",
50 | "sourceMap": false,
51 | "namedChunks": false,
52 | "extractLicenses": true,
53 | "vendorChunk": false,
54 | "buildOptimizer": true,
55 | "budgets": [
56 | {
57 | "type": "initial",
58 | "maximumWarning": "2mb",
59 | "maximumError": "5mb"
60 | },
61 | {
62 | "type": "anyComponentStyle",
63 | "maximumWarning": "6kb"
64 | }
65 | ]
66 | }
67 | },
68 | "defaultConfiguration": ""
69 | },
70 | "serve": {
71 | "builder": "@angular-devkit/build-angular:dev-server",
72 | "options": {
73 | "browserTarget": "angular2-promise-buttons-demo:build"
74 | },
75 | "configurations": {
76 | "production": {
77 | "browserTarget": "angular2-promise-buttons-demo:build:production"
78 | }
79 | }
80 | },
81 | "extract-i18n": {
82 | "builder": "@angular-devkit/build-angular:extract-i18n",
83 | "options": {
84 | "browserTarget": "angular2-promise-buttons-demo:build"
85 | }
86 | },
87 | "lint": {
88 | "builder": "@angular-devkit/build-angular:tslint",
89 | "options": {
90 | "tsConfig": [
91 | "projects/angular2-promise-buttons-demo/tsconfig.app.json",
92 | "projects/angular2-promise-buttons-demo/e2e/tsconfig.json"
93 | ],
94 | "exclude": [
95 | "**/node_modules/**"
96 | ]
97 | }
98 | },
99 | "e2e": {
100 | "builder": "@angular-devkit/build-angular:protractor",
101 | "options": {
102 | "protractorConfig": "projects/angular2-promise-buttons-demo/e2e/protractor.conf.js",
103 | "devServerTarget": "angular2-promise-buttons-demo:serve"
104 | },
105 | "configurations": {
106 | "production": {
107 | "devServerTarget": "angular2-promise-buttons-demo:serve:production"
108 | }
109 | }
110 | }
111 | }
112 | },
113 | "angular2-promise-buttons": {
114 | "projectType": "library",
115 | "root": "projects/angular2-promise-buttons",
116 | "sourceRoot": "projects/angular2-promise-buttons/src",
117 | "prefix": "lib",
118 | "architect": {
119 | "build": {
120 | "builder": "@angular-devkit/build-angular:ng-packagr",
121 | "options": {
122 | "tsConfig": "projects/angular2-promise-buttons/tsconfig.lib.json",
123 | "project": "projects/angular2-promise-buttons/ng-package.json"
124 | },
125 | "configurations": {
126 | "production": {
127 | "tsConfig": "projects/angular2-promise-buttons/tsconfig.lib.prod.json"
128 | }
129 | }
130 | },
131 | "test": {
132 | "builder": "@angular-devkit/build-angular:karma",
133 | "options": {
134 | "main": "projects/angular2-promise-buttons/src/test.ts",
135 | "tsConfig": "projects/angular2-promise-buttons/tsconfig.spec.json",
136 | "karmaConfig": "projects/angular2-promise-buttons/karma.conf.js"
137 | }
138 | },
139 | "lint": {
140 | "builder": "@angular-devkit/build-angular:tslint",
141 | "options": {
142 | "tsConfig": [
143 | "projects/angular2-promise-buttons/tsconfig.lib.json",
144 | "projects/angular2-promise-buttons/tsconfig.spec.json"
145 | ],
146 | "exclude": [
147 | "**/node_modules/**"
148 | ]
149 | }
150 | }
151 | }
152 | }
153 | },
154 | "defaultProject": "angular2-promise-buttons-demo"
155 | }
156 |
--------------------------------------------------------------------------------
/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Protractor configuration file, see link for more information
3 | // https://github.com/angular/protractor/blob/master/lib/config.ts
4 |
5 | const { SpecReporter } = require('jasmine-spec-reporter');
6 |
7 | /**
8 | * @type { import("protractor").Config }
9 | */
10 | exports.config = {
11 | allScriptsTimeout: 11000,
12 | specs: [
13 | './src/**/*.e2e-spec.ts'
14 | ],
15 | capabilities: {
16 | 'browserName': 'chrome'
17 | },
18 | directConnect: true,
19 | baseUrl: 'http://localhost:4200/',
20 | framework: 'jasmine',
21 | jasmineNodeOpts: {
22 | showColors: true,
23 | defaultTimeoutInterval: 30000,
24 | print: function() {}
25 | },
26 | onPrepare() {
27 | require('ts-node').register({
28 | project: require('path').join(__dirname, './tsconfig.json')
29 | });
30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
31 | }
32 | };
--------------------------------------------------------------------------------
/e2e/src/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 | import { browser, logging } from 'protractor';
3 |
4 | describe('workspace-project App', () => {
5 | let page: AppPage;
6 |
7 | beforeEach(() => {
8 | page = new AppPage();
9 | });
10 |
11 | it('should display welcome message', () => {
12 | page.navigateTo();
13 | expect(page.getTitleText()).toEqual('Welcome to angular-material-css-vars!');
14 | });
15 |
16 | afterEach(async () => {
17 | // Assert that there are no errors emitted from the browser
18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER);
19 | expect(logs).not.toContain(jasmine.objectContaining({
20 | level: logging.Level.SEVERE,
21 | } as logging.Entry));
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/e2e/src/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get(browser.baseUrl) as Promise;
6 | }
7 |
8 | getTitleText() {
9 | return element(by.css('app-root h1')).getText() as Promise;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "module": "commonjs",
6 | "target": "es2018",
7 | "types": [
8 | "jasmine",
9 | "jasminewd2",
10 | "node"
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johannesjo/angular2-promise-buttons/eeea9cf2c42c2a17d6bb35f4bd4bb1028046a261/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-promise-buttons",
3 | "version": "6.0.1",
4 | "license": "MIT",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+ssh://git@github.com/johannesjo/angular2-promise-buttons.git"
8 | },
9 | "scripts": {
10 | "ng": "ng",
11 | "start": "ng serve",
12 | "build": "ng build",
13 | "test": "ng test --browsers ChromeHeadless --watch=false",
14 | "lint": "ng lint",
15 | "e2e": "ng e2e",
16 | "demo": "run-s demo.build demo.copy-readme demo.gh-pages",
17 | "demo.build": "ng build --aot --configuration production --base-href='./'",
18 | "demo.copy-readme": "node scripts/copy-readme-to-demo.js",
19 | "demo.gh-pages": "gh-pages -d dist/demo",
20 | "lib": "run-s lib.build copy",
21 | "lib.build": "ng build --configuration production angular2-promise-buttons",
22 | "copy": "run-s copy.licence copy.readme",
23 | "copy.licence": "copyfiles ./LICENSE ./dist/angular2-promise-buttons",
24 | "copy.readme": "copyfiles ./README.md ./dist/angular2-promise-buttons",
25 | "pub": "run-s lib && cd ./dist/angular2-promise-buttons/ && npm publish && cd .. && cd ..",
26 | "patch": "npm version patch && cd ./projects/angular2-promise-buttons && npm version patch && cd .. && cd .. && git add . && git commit -am\"chore: update lib version\"",
27 | "patch-release_": "run-s lib demo patch pub",
28 | "patch-release": "npm run patch-release_",
29 | "major": "npm version major && cd ./projects/angular2-promise-buttons && npm version major && cd .. && cd .. && git add . && git commit -am\"chore: update lib version\"",
30 | "major-release_": "run-s lib demo major pub",
31 | "major-release": "npm run major-release_",
32 | "test-coverage": "ng test --browsers ChromeHeadless --code-coverage --watch=false",
33 | "coveralls": "YOURPACKAGE_COVERAGE=1 cat ./coverage/lcov.info | ./node_modules/.bin/coveralls"
34 | },
35 | "peerDependencies": {},
36 | "devDependencies": {
37 | "@angular-devkit/build-angular": "^12.2.18",
38 | "@angular/cli": "^12.2.18",
39 | "@angular/common": "^12.2.17",
40 | "@angular/compiler": "^12.2.17",
41 | "@angular/compiler-cli": "^12.2.17",
42 | "@angular/core": "^12.2.17",
43 | "@angular/forms": "^12.2.17",
44 | "@angular/platform-browser": "^12.2.17",
45 | "@angular/platform-browser-dynamic": "^12.2.17",
46 | "@angular/router": "^12.2.17",
47 | "@linnenschmidt/build-ng-packagr": "^9.0.0",
48 | "@types/bluebird": "^3.5.29",
49 | "@types/core-js": "^2.5.2",
50 | "@types/jasmine": "~3.6.0",
51 | "@types/jquery": "^3.3.32",
52 | "@types/node": "^13.7.0",
53 | "angular2-template-loader": "^0.6.2",
54 | "bluebird": "^3.7.2",
55 | "bootstrap": "^4.4.1",
56 | "bootstrap-material-design": "^4.1.2",
57 | "codelyzer": "^6.0.0",
58 | "conventional-changelog-cli": "^2.0.31",
59 | "conventional-github-releaser": "^3.1.3",
60 | "copyfiles": "^2.2.0",
61 | "core-js": "^3.6.4",
62 | "coveralls": "^3.0.9",
63 | "gh-pages": "^2.2.0",
64 | "intl": "^1.2.5",
65 | "jasmine-core": "~3.6.0",
66 | "jasmine-spec-reporter": "~5.0.0",
67 | "jquery": "^3.5.0",
68 | "karma": "~6.3.16",
69 | "karma-chrome-launcher": "~3.1.0",
70 | "karma-cli": "~2.0.0",
71 | "karma-coverage-istanbul-reporter": "~3.0.2",
72 | "karma-jasmine": "~4.0.0",
73 | "karma-jasmine-html-reporter": "^1.5.0",
74 | "karma-phantomjs-launcher": "^1.0.4",
75 | "marked": "^2.0.0",
76 | "ng-packagr": "^12.2.7",
77 | "npm-run-all": "^4.1.5",
78 | "protractor": "~7.0.0",
79 | "reflect-metadata": "^0.1.13",
80 | "rxjs": "^6.5.4",
81 | "ts-node": "~8.6.2",
82 | "tslib": "^2.0.0",
83 | "tslint": "~6.1.0",
84 | "typescript": "~4.3.5",
85 | "wallaby-webpack": "3.9.15",
86 | "web-animations-js": "^2.3.2",
87 | "zone.js": "~0.11.4"
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | beforeLaunch: function() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | },
27 | onPrepare() {
28 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../../out-tsc/e2e",
6 | "module": "commonjs",
7 | "target": "es2018",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/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: false,
28 | browsers: ['ChromeHeadless'],
29 | singleRun: true,
30 | customLaunchers: {
31 | 'ChromeHeadless': {
32 | base: 'Chrome',
33 | flags: [
34 | // We must disable the Chrome sandbox when running Chrome inside Docker
35 | // (Chrome's sandbox needs more permissions than Docker allows by default)
36 | '--headless',
37 | '--no-sandbox',
38 | '--disable-gpu',
39 | '--no-default-browser-check',
40 | '--no-first-run',
41 | '--disable-default-apps',
42 | '--disable-popup-blocking',
43 | '--disable-translate',
44 | '--disable-background-timer-throttling',
45 | '--disable-renderer-backgrounding',
46 | '--disable-device-discovery-notifications',
47 | // Without a remote debugging port, Google Chrome exits immediately.
48 | '--remote-debugging-port=9222',
49 | '--disable-web-security',
50 | ],
51 | debug: true
52 | }
53 | },
54 | browserNoActivityTimeout: 120000,
55 | });
56 | };
57 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | h2 {
2 | margin-top: 16px;
3 | }
4 | h3 {
5 | margin-top: 16px;
6 | }
7 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
5 |
9 |
10 |
11 |
15 |
18 |
19 | Same promise buttons
20 |
24 |
28 |
29 | Chained promise buttons
30 |
34 |
35 |
36 | Inside a form
37 |
44 |
45 |
46 |
Observable
47 |
48 |
52 |
56 |
57 |
58 |
62 |
65 |
66 | Same observable buttons
67 |
71 |
75 |
76 | Chained observable button
77 |
81 |
82 |
83 |
84 |
Simple Boolean
85 |
88 |
89 |
92 |
93 |
94 |
95 |
96 |
Dynamic [disabled]
97 |
98 |
101 |
102 |
105 |
106 |
109 |
110 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {async, TestBed} from '@angular/core/testing';
2 | import {AppComponent} from './app.component';
3 | import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
4 |
5 | // @Directive({
6 | // selector: '[promiseBtn]'
7 | // })
8 | // class MockPromiseBtnDirective {
9 | // @Input('promiseBtn') promise: Promise;
10 | // }
11 |
12 | describe('AppComponent', () => {
13 | beforeEach(async(() => {
14 | TestBed.configureTestingModule({
15 | declarations: [
16 | AppComponent,
17 | // MockPromiseBtnDirective
18 | ],
19 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
20 | }).compileComponents();
21 | }));
22 |
23 | it('should create the app', async(() => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | const app = fixture.debugElement.componentInstance;
26 | expect(app).toBeTruthy();
27 | }));
28 | });
29 |
30 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 | import {Observable, Subscription} from 'rxjs';
3 |
4 | const STANDARD_DELAY = 1000;
5 | const FAKE_FACT = {
6 | success() {
7 | return new Promise((fulfill) => {
8 | setTimeout(() => {
9 | fulfill({
10 | msg: 'SUCCESS'
11 | });
12 | }, STANDARD_DELAY);
13 | });
14 | },
15 | error: () => {
16 | return new Promise((fulfill, reject) => {
17 | setTimeout(() => {
18 | reject({
19 | msg: 'ERROR'
20 | });
21 | }, STANDARD_DELAY);
22 | });
23 | },
24 | endless: () => {
25 | return new Promise((fulfill) => {
26 | setTimeout(fulfill, 99999999);
27 | });
28 | },
29 | endlessObservable: (): Observable => {
30 | return new Observable(() => {
31 | });
32 | },
33 | initSuccessObservable: (): Observable => {
34 | return new Observable(observer => {
35 | setTimeout(() => {
36 | observer.complete();
37 | }, STANDARD_DELAY);
38 | });
39 | },
40 | initErrorObservable: (): Observable => {
41 | return new Observable(observer => {
42 | setTimeout(() => {
43 | observer.error('ERROR');
44 | }, STANDARD_DELAY);
45 | });
46 | },
47 | initChainedObservable: (): Observable => {
48 | return new Observable(observer => {
49 | setTimeout(() => {
50 | observer.next(1);
51 | }, 1000);
52 |
53 | setTimeout(() => {
54 | observer.next(2);
55 | }, 2000);
56 |
57 | setTimeout(() => {
58 | observer.next(3);
59 | }, 3000);
60 |
61 | setTimeout(() => {
62 | observer.complete();
63 | }, 4000);
64 | });
65 | },
66 | };
67 |
68 | @Component({
69 | selector: 'app-root',
70 | templateUrl: './app.component.html',
71 | styleUrls: ['./app.component.css']
72 | })
73 | export class AppComponent {
74 | successPromise: Promise;
75 | errorPromise: Promise;
76 | endlessInitialPromise: Promise;
77 | endlessPromise: Promise;
78 | submitPromise: Promise;
79 | chainedPromises: any;
80 | promiseIndex: number;
81 | myBool = true;
82 |
83 | successObservable: Subscription;
84 | errorObservable: Subscription;
85 | endlessInitialObservable: Subscription;
86 | endlessObservable: Subscription;
87 | chainedObservableValue: any;
88 | chainedObservable: Subscription;
89 | customDisabled = true;
90 | myBoolWithCustomDisabled = false;
91 | isOutsideDisabled = true;
92 |
93 | constructor() {
94 | this.endlessInitial();
95 | this.initEndlessInitialObservable();
96 | }
97 |
98 | success($event: any): Promise {
99 | console.log($event);
100 | this.successPromise = FAKE_FACT.success();
101 | return this.successPromise;
102 | }
103 |
104 | error() {
105 | this.errorPromise = FAKE_FACT.error()
106 | .catch(() => {
107 | console.log('YEAH ERROR');
108 | });
109 | }
110 |
111 | endless() {
112 | this.endlessPromise = FAKE_FACT.endless();
113 | }
114 |
115 | endlessInitial() {
116 | this.endlessInitialPromise = FAKE_FACT.endless();
117 | }
118 |
119 | initSuccessObservable() {
120 | const observable = FAKE_FACT.initSuccessObservable();
121 | this.successObservable = observable.subscribe(
122 | () => {
123 | },
124 | () => {
125 | },
126 | () => {
127 | }
128 | );
129 | }
130 |
131 | initErrorObservable() {
132 | const observable = FAKE_FACT.initErrorObservable();
133 | this.errorObservable = observable.subscribe(
134 | () => {
135 | },
136 | (msg) => {
137 | console.log(msg);
138 | },
139 | () => {
140 | },
141 | );
142 | }
143 |
144 | initChainedObservable() {
145 | const observable = FAKE_FACT.initChainedObservable();
146 | this.chainedObservableValue = 'INITIALIZED';
147 | this.chainedObservable = observable.subscribe(
148 | (value: number) => {
149 | this.chainedObservableValue = value;
150 | },
151 | () => {
152 | },
153 | () => {
154 | this.chainedObservableValue = 'COMPLETED';
155 | }
156 | );
157 | }
158 |
159 | initEndlessObservable() {
160 | const observable = FAKE_FACT.endlessObservable();
161 | this.endlessObservable = observable.subscribe(
162 | () => {
163 | },
164 | () => {
165 | },
166 | () => {
167 | },
168 | );
169 | }
170 |
171 | initEndlessInitialObservable() {
172 | const observable = FAKE_FACT.endlessObservable();
173 | this.endlessInitialObservable = observable.subscribe(
174 | () => {
175 | },
176 | () => {
177 | },
178 | () => {
179 | },
180 | );
181 | }
182 |
183 | submit() {
184 | this.submitPromise = FAKE_FACT.success();
185 | }
186 |
187 | chain() {
188 | this.promiseIndex = 0;
189 | this.chainedPromises = this.countChain()
190 | .then(this.countChain.bind(this))
191 | .then(this.countChain.bind(this))
192 | .then(this.countChain.bind(this))
193 | .then(this.countChain.bind(this));
194 |
195 | return this.chainedPromises;
196 | }
197 |
198 | countChain() {
199 | return FAKE_FACT.success()
200 | .then(() => {
201 | this.promiseIndex++;
202 | });
203 | }
204 |
205 | }
206 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import {Angular2PromiseButtonModule} from '../../../angular2-promise-buttons/src';
2 | import {BrowserModule} from '@angular/platform-browser';
3 | import {FormsModule} from '@angular/forms';
4 | import {NgModule} from '@angular/core';
5 |
6 | import {AppComponent} from './app.component';
7 |
8 | @NgModule({
9 | declarations: [
10 | AppComponent,
11 | ],
12 | imports: [
13 | BrowserModule,
14 | FormsModule,
15 | Angular2PromiseButtonModule
16 | .forRoot({
17 | // handleCurrentBtnOnly: true,
18 | }),
19 | ],
20 | providers: [],
21 | bootstrap: [AppComponent]
22 | })
23 | export class AppModule {
24 | }
25 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johannesjo/angular2-promise-buttons/eeea9cf2c42c2a17d6bb35f4bd4bb1028046a261/projects/angular2-promise-buttons-demo/src/assets/.gitkeep
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johannesjo/angular2-promise-buttons/eeea9cf2c42c2a17d6bb35f4bd4bb1028046a261/projects/angular2-promise-buttons-demo/src/favicon.ico
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular2PromiseButtons
6 |
7 |
9 |
12 |
14 |
15 |
16 |
17 |
18 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Star
37 |
38 |
39 |
Fork
45 |
46 |
47 |
Issue
53 |
54 |
55 |
56 |
57 |
58 | ___README_MD_NEEDLE___
59 |
60 |
61 | Demo
63 | Loading...
64 |
65 |
66 |
67 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/main.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import 'reflect-metadata';
3 | import { enableProdMode } from '@angular/core';
4 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
5 |
6 | import { AppModule } from './app/app.module';
7 | import { environment } from './environments/environment';
8 |
9 | if (environment.production) {
10 | enableProdMode();
11 | }
12 |
13 | platformBrowserDynamic().bootstrapModule(AppModule);
14 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/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'; // Included with Angular CLI.
59 |
60 |
61 | /***************************************************************************************************
62 | * APPLICATION IMPORTS
63 | */
64 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/src/styles.scss:
--------------------------------------------------------------------------------
1 | @import '../../../node_modules/bootstrap-material-design/dist/css/bootstrap-material-design.css';
2 | //@import 'node_modules/bootstrap-material-design/scss/bootstrap-material-design';
3 |
4 | /* You can add global styles to this file, and also import other style files */
5 | @-webkit-keyframes three-quarters {
6 | 0% {
7 | -webkit-transform: rotate(0deg);
8 | -moz-transform: rotate(0deg);
9 | -ms-transform: rotate(0deg);
10 | -o-transform: rotate(0deg);
11 | transform: rotate(0deg);
12 | }
13 |
14 | 100% {
15 | -webkit-transform: rotate(360deg);
16 | -moz-transform: rotate(360deg);
17 | -ms-transform: rotate(360deg);
18 | -o-transform: rotate(360deg);
19 | transform: rotate(360deg);
20 | }
21 | }
22 |
23 | @-moz-keyframes three-quarters {
24 | 0% {
25 | -webkit-transform: rotate(0deg);
26 | -moz-transform: rotate(0deg);
27 | -ms-transform: rotate(0deg);
28 | -o-transform: rotate(0deg);
29 | transform: rotate(0deg);
30 | }
31 |
32 | 100% {
33 | -webkit-transform: rotate(360deg);
34 | -moz-transform: rotate(360deg);
35 | -ms-transform: rotate(360deg);
36 | -o-transform: rotate(360deg);
37 | transform: rotate(360deg);
38 | }
39 | }
40 |
41 | @-o-keyframes three-quarters {
42 | 0% {
43 | -webkit-transform: rotate(0deg);
44 | -moz-transform: rotate(0deg);
45 | -ms-transform: rotate(0deg);
46 | -o-transform: rotate(0deg);
47 | transform: rotate(0deg);
48 | }
49 |
50 | 100% {
51 | -webkit-transform: rotate(360deg);
52 | -moz-transform: rotate(360deg);
53 | -ms-transform: rotate(360deg);
54 | -o-transform: rotate(360deg);
55 | transform: rotate(360deg);
56 | }
57 | }
58 |
59 | @keyframes three-quarters {
60 | 0% {
61 | -webkit-transform: rotate(0deg);
62 | -moz-transform: rotate(0deg);
63 | -ms-transform: rotate(0deg);
64 | -o-transform: rotate(0deg);
65 | transform: rotate(0deg);
66 | }
67 |
68 | 100% {
69 | -webkit-transform: rotate(360deg);
70 | -moz-transform: rotate(360deg);
71 | -ms-transform: rotate(360deg);
72 | -o-transform: rotate(360deg);
73 | transform: rotate(360deg);
74 | }
75 | }
76 |
77 | /* Styles for old versions of IE */
78 | button .btn-spinner {
79 | font-family: sans-serif;
80 | font-weight: 100;
81 | -webkit-animation: three-quarters 1250ms infinite linear;
82 | -moz-animation: three-quarters 1250ms infinite linear;
83 | -ms-animation: three-quarters 1250ms infinite linear;
84 | -o-animation: three-quarters 1250ms infinite linear;
85 | animation: three-quarters 1250ms infinite linear;
86 | border: 3px solid #8c024c;
87 | border-right-color: transparent;
88 | border-radius: 100%;
89 | box-sizing: border-box;
90 | display: inline-block;
91 | position: relative;
92 | vertical-align: middle;
93 | overflow: hidden;
94 | text-indent: -9999px;
95 | width: 18px;
96 | height: 18px;
97 | }
98 |
99 | button .btn-spinner:not(:required) {
100 | margin-left: -22px;
101 | opacity: 0;
102 | transition: 0.4s margin ease-out,
103 | 0.2s opacity ease-out;
104 | }
105 |
106 | button.is-loading .btn-spinner {
107 | transition: 0.2s margin ease-in,
108 | 0.4s opacity ease-in;
109 | margin-left: 5px;
110 | opacity: 1;
111 | }
112 |
113 | .btn {
114 | text-align: left;
115 | }
116 |
117 | body {
118 | padding-bottom: 50px;
119 | }
120 |
121 | .main-header {
122 | background: #3f51b5;
123 | color: #eeeeee;
124 | height: 133px;
125 | }
126 |
127 | .main-header h1 {
128 | margin-top: 10px;
129 | }
130 |
131 | .fork-me-badge {
132 | position: absolute;
133 | right: 0;
134 | top: 0;
135 | }
136 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/app",
5 | "types": []
6 | },
7 | "files": [
8 | "src/main.ts",
9 | "src/polyfills.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons-demo/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rules": {
4 | "align": {
5 | "options": [
6 | "parameters",
7 | "statements"
8 | ]
9 | },
10 | "array-type": false,
11 | "arrow-parens": false,
12 | "arrow-return-shorthand": true,
13 | "deprecation": {
14 | "severity": "warning"
15 | },
16 | "component-class-suffix": true,
17 | "contextual-lifecycle": true,
18 | "curly": true,
19 | "directive-class-suffix": true,
20 | "directive-selector": [
21 | false,
22 | "attribute",
23 | "app",
24 | "camelCase"
25 | ],
26 | "component-selector": [
27 | false,
28 | "element",
29 | "app",
30 | "kebab-case"
31 | ],
32 | "eofline": true,
33 | "import-blacklist": [
34 | true,
35 | "rxjs/Rx"
36 | ],
37 | "import-spacing": true,
38 | "indent": {
39 | "options": [
40 | "spaces"
41 | ]
42 | },
43 | "interface-name": false,
44 | "max-classes-per-file": false,
45 | "max-line-length": [
46 | true,
47 | 150
48 | ],
49 | "member-access": false,
50 | "member-ordering": [
51 | true,
52 | {
53 | "order": [
54 | "static-field",
55 | "instance-field",
56 | "static-method",
57 | "instance-method"
58 | ]
59 | }
60 | ],
61 | "no-consecutive-blank-lines": false,
62 | "no-console": [
63 | true,
64 | "debug",
65 | "info",
66 | "time",
67 | "timeEnd",
68 | "trace"
69 | ],
70 | "no-empty": false,
71 | "no-inferrable-types": [
72 | true,
73 | "ignore-params"
74 | ],
75 | "no-non-null-assertion": true,
76 | "no-redundant-jsdoc": true,
77 | "no-switch-case-fall-through": true,
78 | "no-var-requires": false,
79 | "object-literal-key-quotes": [
80 | true,
81 | "as-needed"
82 | ],
83 | "object-literal-sort-keys": false,
84 | "ordered-imports": false,
85 | "quotemark": [
86 | true,
87 | "single"
88 | ],
89 | "trailing-comma": false,
90 | "no-conflicting-lifecycle": true,
91 | "no-host-metadata-property": true,
92 | "no-input-rename": false,
93 | "no-inputs-metadata-property": true,
94 | "no-output-native": true,
95 | "no-output-on-prefix": true,
96 | "no-output-rename": true,
97 | "semicolon": {
98 | "options": [
99 | "always"
100 | ]
101 | },
102 | "space-before-function-paren": {
103 | "options": {
104 | "anonymous": "never",
105 | "asyncArrow": "always",
106 | "constructor": "never",
107 | "method": "never",
108 | "named": "never"
109 | }
110 | },
111 | "no-outputs-metadata-property": true,
112 | "template-banana-in-box": true,
113 | "template-no-negated-async": false,
114 | "typedef-whitespace": {
115 | "options": [
116 | {
117 | "call-signature": "nospace",
118 | "index-signature": "nospace",
119 | "parameter": "nospace",
120 | "property-declaration": "nospace",
121 | "variable-declaration": "nospace"
122 | },
123 | {
124 | "call-signature": "onespace",
125 | "index-signature": "onespace",
126 | "parameter": "onespace",
127 | "property-declaration": "onespace",
128 | "variable-declaration": "onespace"
129 | }
130 | ]
131 | },
132 | "use-lifecycle-interface": true,
133 | "use-pipe-transform-interface": true,
134 | "variable-name": [
135 | true,
136 | "allow-pascal-case",
137 | "allow-snake-case",
138 | "ban-keywords",
139 | "check-format",
140 | "allow-leading-underscore"
141 | ],
142 | "whitespace": {
143 | "options": [
144 | "check-branch",
145 | "check-decl",
146 | "check-operator",
147 | "check-separator",
148 | "check-type",
149 | "check-typecast"
150 | ]
151 | }
152 | },
153 | "rulesDirectory": [
154 | "codelyzer"
155 | ]
156 | }
157 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/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/angular2-promise-buttons'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
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 | restartOnFileChange: true
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/angular2-promise-buttons",
4 | "lib": {
5 | "entryFile": "src/index.ts"
6 | },
7 | "assets": [
8 | "./**/*.scss"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-promise-buttons",
3 | "version": "6.0.0",
4 | "description": "Chilled loading buttons for angular",
5 | "author": "johannesjo (http://super-productivity.com)",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+ssh://git@github.com/johannesjo/angular2-promise-buttons.git"
10 | },
11 | "keywords": [
12 | "angular",
13 | "javascript",
14 | "typescript",
15 | "button",
16 | "promise",
17 | "spinner"
18 | ],
19 | "dependencies": {
20 | "tslib": "^2.0.0"
21 | },
22 | "peerDependencies": {
23 | "@angular/common": ">=9.0.4",
24 | "@angular/core": ">=9.0.4"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/src/angular2-promise-buttons.module.ts:
--------------------------------------------------------------------------------
1 | import {ModuleWithProviders, NgModule} from '@angular/core';
2 | import {PromiseBtnDirective} from './promise-btn.directive';
3 | import {PromiseBtnConfig} from './promise-btn-config';
4 | import {userCfg} from './user-cfg';
5 |
6 | @NgModule({
7 | declarations: [
8 | PromiseBtnDirective,
9 | ],
10 | imports: [],
11 | exports: [
12 | PromiseBtnDirective,
13 | ],
14 | providers: []
15 | })
16 | export class Angular2PromiseButtonModule {
17 | // add forRoot to make it configurable
18 | static forRoot(config?: PromiseBtnConfig): ModuleWithProviders {
19 | // NOTE: this is never allowed to contain any conditional logic
20 | return {
21 | ngModule: Angular2PromiseButtonModule,
22 | providers: [{provide: userCfg, useValue: config}]
23 | };
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/src/default-promise-btn-config.ts:
--------------------------------------------------------------------------------
1 | import {PromiseBtnConfig} from './promise-btn-config';
2 |
3 | export const DEFAULT_CFG: PromiseBtnConfig = {
4 | spinnerTpl: '',
5 | disableBtn: true,
6 | btnLoadingClass: 'is-loading',
7 | handleCurrentBtnOnly: false,
8 | minDuration: null,
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/src/index.ts:
--------------------------------------------------------------------------------
1 | export {PromiseBtnDirective} from './promise-btn.directive';
2 | export {Angular2PromiseButtonModule} from './angular2-promise-buttons.module';
3 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/src/promise-btn-config.ts:
--------------------------------------------------------------------------------
1 | export interface PromiseBtnConfig {
2 | spinnerTpl?: string;
3 | disableBtn?: boolean;
4 | btnLoadingClass?: boolean | string;
5 | handleCurrentBtnOnly?: boolean;
6 | minDuration?: number | null;
7 | }
8 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/src/promise-btn.directive.spec.ts:
--------------------------------------------------------------------------------
1 | // import 'core-js/fn/object/entries';
2 | import {Component, DebugElement, ElementRef} from '@angular/core';
3 | import {async, ComponentFixture, TestBed} from '@angular/core/testing';
4 | import {PromiseBtnDirective} from './promise-btn.directive';
5 | import {userCfg} from './user-cfg';
6 | import {By} from '@angular/platform-browser';
7 | import {Observable} from 'rxjs';
8 | import {delay} from 'rxjs/operators';
9 | import * as BlueBird from 'bluebird';
10 | import * as jQuery from 'jquery';
11 |
12 | class MockElementRef extends ElementRef {
13 | constructor() {
14 | super(null);
15 | this.nativeElement = {};
16 | }
17 | }
18 |
19 | @Component({
20 | selector: 'test-component',
21 | template: ''
22 | })
23 | class TestComponent {
24 | testPromise: any;
25 | setPromise: any;
26 | isDisabled: any;
27 | }
28 |
29 |
30 | let testUserCfg: any;
31 |
32 | describe('PromiseBtnDirective', () => {
33 | beforeEach(async(() => {
34 | testUserCfg = {};
35 | TestBed.configureTestingModule({
36 | declarations: [
37 | TestComponent,
38 | PromiseBtnDirective
39 | ],
40 | providers: [
41 | // more providers
42 | {
43 | provide: ElementRef,
44 | useClass: MockElementRef
45 | },
46 | {
47 | provide: userCfg, useValue: testUserCfg
48 | },
49 | ]
50 | });
51 | }));
52 |
53 | describe('runtimeCfg', () => {
54 | let fixture: ComponentFixture;
55 | let buttonDebugElement: DebugElement;
56 | let buttonElement: HTMLButtonElement;
57 | let promiseBtnDirective: PromiseBtnDirective;
58 |
59 | beforeEach(() => {
60 | fixture = TestBed.overrideComponent(TestComponent, {
61 | set: {
62 | template: ''
63 | }
64 | }).createComponent(TestComponent);
65 | fixture.detectChanges();
66 |
67 | buttonDebugElement = fixture.debugElement.query(By.css('button'));
68 | buttonElement = (buttonDebugElement.nativeElement as HTMLButtonElement);
69 | promiseBtnDirective = buttonDebugElement.injector.get(PromiseBtnDirective);
70 | });
71 |
72 | describe('default cfg', () => {
73 | describe('basic init', () => {
74 | it('should create an instance', () => {
75 | expect(promiseBtnDirective).toBeDefined();
76 | expect(promiseBtnDirective.cfg).toBeDefined();
77 | // const directive = new PromiseBtnDirective({}, {});
78 | // expect(directive).toBeTruthy();
79 | });
80 | it('should append the spinner el to the button', () => {
81 | const spinnerEl = buttonElement.querySelector('span');
82 | expect(spinnerEl && spinnerEl.outerHTML).toBe('');
83 | });
84 | describe('should accept all promise-alike values', () => {
85 | const possibleValues = {
86 | 'native Promise': () => new Promise((resolve) => {
87 | resolve();
88 | }),
89 | 'jQuery Deferred': () => jQuery.Deferred((defer: any) => {
90 | defer.resolve();
91 | }),
92 | 'jQuery Deferred Promise': () => jQuery.Deferred((defer: any) => {
93 | defer.resolve();
94 | }).promise(),
95 | 'bluebird Promise': () => new BlueBird((resolve) => {
96 | resolve();
97 | }),
98 | 'RxJs Observable': () => {
99 | const observable = new Observable((subscriber) => {
100 | subscriber.complete();
101 | });
102 |
103 | return observable.pipe(delay(0)).subscribe(
104 | () => {
105 | },
106 | () => {
107 | },
108 | () => {
109 | },
110 | );
111 | },
112 | };
113 |
114 | // Iterate over possible values
115 | for (const [description, getPromise] of (Object as any).entries(possibleValues)) {
116 | describe(`testing ${description}`, () => {
117 | beforeEach(() => {
118 | fixture.componentInstance.testPromise = getPromise();
119 | // test init before to be sure
120 | spyOn(promiseBtnDirective, 'initLoadingState').and.callThrough();
121 | fixture.detectChanges();
122 | });
123 |
124 | it('should init the loading state', () => {
125 | expect(promiseBtnDirective.initLoadingState).toHaveBeenCalled();
126 | });
127 | });
128 | }
129 | });
130 | it('should convert RxJs Observable Subscription to Promise', () => {
131 | const observable = new Observable((subscriber) => {
132 | subscriber.complete();
133 | });
134 | fixture.componentInstance.testPromise = observable.pipe(delay(0)).subscribe(
135 | () => {
136 | },
137 | () => {
138 | },
139 | () => {
140 | },
141 | );
142 | fixture.detectChanges();
143 | expect(promiseBtnDirective.promise instanceof Promise).toBe(true);
144 | });
145 | it('should throw an error if an observable is passed directly', () => {
146 | const observable = new Observable((subscriber) => {
147 | subscriber.complete();
148 | });
149 | fixture.componentInstance.testPromise = observable;
150 |
151 | expect(() => {
152 | fixture.detectChanges();
153 | }).toThrowError('promiseBtn must be an instance of Subscription, instance of Observable given');
154 | });
155 | it('should do nothing with a closed subscription', () => {
156 | spyOn(promiseBtnDirective, 'initLoadingState');
157 |
158 | const observable = new Observable((subscriber) => {
159 | subscriber.complete();
160 | });
161 | // subscription will immediately complete and close
162 | fixture.componentInstance.testPromise = observable.subscribe(
163 | () => {
164 | },
165 | () => {
166 | },
167 | () => {
168 | },
169 | );
170 | fixture.detectChanges();
171 |
172 | expect(promiseBtnDirective.promise).toBe(undefined);
173 | expect(promiseBtnDirective.initLoadingState).not.toHaveBeenCalled();
174 | });
175 | });
176 |
177 | describe('when promise is passed after click', () => {
178 | beforeEach(() => {
179 | fixture.componentInstance.setPromise = () => {
180 | fixture.componentInstance.testPromise = new Promise(() => {
181 | });
182 | };
183 | fixture.detectChanges();
184 |
185 | // remove initial promise
186 | fixture.componentInstance.testPromise = null;
187 | fixture.detectChanges();
188 |
189 | // test init before to be sure
190 | spyOn(promiseBtnDirective, 'initLoadingState').and.callThrough();
191 | fixture.detectChanges();
192 |
193 | buttonDebugElement.triggerEventHandler('click', null);
194 | fixture.detectChanges();
195 | });
196 |
197 | it('should init the loading state', () => {
198 | expect(promiseBtnDirective.initLoadingState).toHaveBeenCalled();
199 | });
200 | it('should add .is-loading class', async(() => {
201 | fixture.whenStable().then(() => {
202 | expect(buttonElement.className).toBe('is-loading');
203 | });
204 | }));
205 | it('should disable the button', async(() => {
206 | fixture.whenStable().then(() => {
207 | expect(buttonElement.getAttribute('disabled')).toBe('disabled');
208 | });
209 | }));
210 | });
211 |
212 | describe('once a promise is passed', () => {
213 | beforeEach(() => {
214 | fixture.componentInstance.testPromise = new Promise(() => {
215 | });
216 | spyOn(promiseBtnDirective, 'initLoadingState').and.callThrough();
217 | fixture.detectChanges();
218 | });
219 |
220 | it('should init the loading state', () => {
221 | expect(promiseBtnDirective.initLoadingState).toHaveBeenCalled();
222 | });
223 | it('should add .is-loading class', async(() => {
224 | fixture.whenStable().then(() => {
225 | expect(buttonElement.className).toBe('is-loading');
226 | });
227 | }));
228 | it('should disable the button', async(() => {
229 | fixture.whenStable().then(() => {
230 | expect(buttonElement.getAttribute('disabled')).toBe('disabled');
231 | });
232 | }));
233 | });
234 |
235 | describe('once a passed promise is resolved', () => {
236 | let promise;
237 | let resolve: any;
238 | beforeEach(async(() => {
239 | promise = new Promise((res) => {
240 | resolve = res;
241 | });
242 | fixture.componentInstance.testPromise = promise;
243 |
244 | // test init before to be sure
245 | spyOn(promiseBtnDirective, 'initLoadingState').and.callThrough();
246 | fixture.detectChanges();
247 | expect(promiseBtnDirective.initLoadingState).toHaveBeenCalled();
248 |
249 | fixture.whenStable().then(() => {
250 | spyOn(promiseBtnDirective, 'cancelLoadingStateIfPromiseAndMinDurationDone').and.callThrough();
251 | resolve();
252 | });
253 | fixture.detectChanges();
254 | }));
255 |
256 | it('should cancel the loading state', () => {
257 | expect(promiseBtnDirective.cancelLoadingStateIfPromiseAndMinDurationDone).toHaveBeenCalled();
258 | });
259 | it('should remove the .is-loading class', () => {
260 | expect(buttonElement.className).toBe('');
261 | });
262 | it('should enable the button', () => {
263 | expect(buttonElement.hasAttribute('disabled')).toBe(false);
264 | });
265 | });
266 |
267 | describe('once a passed promise is rejected', () => {
268 | let promise;
269 | let reject: any;
270 | beforeEach(async(() => {
271 | promise = new Promise((res, rej) => {
272 | reject = rej;
273 | });
274 | fixture.componentInstance.testPromise = promise;
275 |
276 | // test init before to be sure
277 | spyOn(promiseBtnDirective, 'initLoadingState').and.callThrough();
278 | fixture.detectChanges();
279 | expect(promiseBtnDirective.initLoadingState).toHaveBeenCalled();
280 |
281 | fixture.whenStable().then(() => {
282 | spyOn(promiseBtnDirective, 'cancelLoadingStateIfPromiseAndMinDurationDone').and.callThrough();
283 | reject();
284 | });
285 | fixture.detectChanges();
286 | }));
287 |
288 | it('should cancel the loading state', () => {
289 | expect(promiseBtnDirective.cancelLoadingStateIfPromiseAndMinDurationDone).toHaveBeenCalled();
290 | });
291 | it('should remove the .is-loading class', () => {
292 | expect(buttonElement.className).toBe('');
293 | });
294 | it('should enable the button', () => {
295 | expect(buttonElement.hasAttribute('disabled')).toBe(false);
296 | });
297 | });
298 |
299 | describe('should do nothing when anything else than a promise is passed', () => {
300 | const possibleValues = {
301 | undefined,
302 | null: null,
303 | boolean: false,
304 | number: 1,
305 | NaN,
306 | array: [],
307 | object: {},
308 | 'object, "then" is not a function': {then: true},
309 | 'object, "then" is invalid function': {
310 | then: () => {
311 | }
312 | },
313 | };
314 |
315 | // Iterate over possible values
316 | for (const [description, promise] of (Object as any).entries(possibleValues)) {
317 | describe(`testing ${description}`, () => {
318 | beforeEach(() => {
319 | fixture.componentInstance.testPromise = promise;
320 | // test init before to be sure
321 | spyOn(promiseBtnDirective, 'initLoadingState').and.callThrough();
322 | fixture.detectChanges();
323 | });
324 |
325 | it('should cancel the loading state', () => {
326 | expect(promiseBtnDirective.initLoadingState).not.toHaveBeenCalled();
327 | });
328 | it('should remove the .is-loading class', () => {
329 | expect(buttonElement.className).toBe('');
330 | });
331 | it('should enable the button', () => {
332 | expect(buttonElement.hasAttribute('disabled')).toBe(false);
333 | });
334 | });
335 | }
336 | });
337 | });
338 |
339 | describe('cfg:minDuration', () => {
340 | describe('once a passed promise is resolved but minDuration has not been exceeded', () => {
341 | let promise;
342 | let resolve: any;
343 | beforeEach((done) => {
344 | promiseBtnDirective.cfg.minDuration = 300;
345 | promise = new Promise((res) => {
346 | resolve = res;
347 | });
348 | fixture.componentInstance.testPromise = promise;
349 |
350 | // test init before to be sure
351 | spyOn(promiseBtnDirective, 'initLoadingState').and.callThrough();
352 | fixture.detectChanges();
353 | expect(promiseBtnDirective.initLoadingState).toHaveBeenCalled();
354 |
355 | spyOn(promiseBtnDirective, 'cancelLoadingStateIfPromiseAndMinDurationDone').and.callThrough();
356 | setTimeout(() => {
357 | resolve();
358 | setTimeout(() => {
359 | done();
360 | }, 10);
361 | }, 10);
362 | });
363 |
364 | it('should try to cancel the loading state', () => {
365 | expect(promiseBtnDirective.cancelLoadingStateIfPromiseAndMinDurationDone).toHaveBeenCalled();
366 | });
367 | it('should not yet remove the .is-loading class', () => {
368 | expect(buttonElement.className).toBe('is-loading');
369 | });
370 | it('should not yet enable the button', () => {
371 | expect(buttonElement.hasAttribute('disabled')).toBe(true);
372 | });
373 | });
374 |
375 | describe('once a passed promise is resolved and the minDuration has been exceeded', () => {
376 | let promise;
377 | let resolve: any;
378 | beforeEach((done) => {
379 | promiseBtnDirective.cfg.minDuration = 30;
380 | promise = new Promise((res) => {
381 | resolve = res;
382 | });
383 | fixture.componentInstance.testPromise = promise;
384 |
385 | // test init before to be sure
386 | spyOn(promiseBtnDirective, 'initLoadingState').and.callThrough();
387 | fixture.detectChanges();
388 | expect(promiseBtnDirective.initLoadingState).toHaveBeenCalled();
389 |
390 | spyOn(promiseBtnDirective, 'cancelLoadingStateIfPromiseAndMinDurationDone').and.callThrough();
391 | setTimeout(() => {
392 | resolve();
393 | setTimeout(() => {
394 | done();
395 | }, ((promiseBtnDirective.cfg.minDuration as number) + 5));
396 | }, 10);
397 | });
398 |
399 | it('should try to cancel the loading state', () => {
400 | expect(promiseBtnDirective.cancelLoadingStateIfPromiseAndMinDurationDone).toHaveBeenCalled();
401 | });
402 | it('should remove the .is-loading class', () => {
403 | expect(buttonElement.className).toBe('');
404 | });
405 | it('should enable the button', () => {
406 | expect(buttonElement.hasAttribute('disabled')).toBe(false);
407 | });
408 | });
409 | });
410 |
411 | describe('cfg:disableBtn:false once a promise is passed', () => {
412 | beforeEach(() => {
413 | promiseBtnDirective.cfg.disableBtn = false;
414 | fixture.componentInstance.testPromise = new Promise(() => {
415 | });
416 | spyOn(promiseBtnDirective, 'initLoadingState').and.callThrough();
417 | fixture.detectChanges();
418 | });
419 |
420 | it('should init the loading state', () => {
421 | expect(promiseBtnDirective.initLoadingState).toHaveBeenCalled();
422 | });
423 | it('should NOT disable the button', async(() => {
424 | expect(buttonElement.hasAttribute('disabled')).toBe(false);
425 | }));
426 | });
427 |
428 | describe('cfg:btnLoadingClass once a promise is passed', () => {
429 | it('should add a custom loading class', async(() => {
430 | spyOn(promiseBtnDirective, 'addLoadingClass').and.callThrough();
431 | promiseBtnDirective.cfg.btnLoadingClass = 'TEST';
432 |
433 | fixture.componentInstance.testPromise = new Promise(() => {
434 | });
435 | fixture.detectChanges();
436 |
437 | fixture.whenStable().then(() => {
438 | expect(promiseBtnDirective.addLoadingClass).toHaveBeenCalled();
439 | expect(buttonElement.className).toBe('TEST');
440 | });
441 | }));
442 | it('should not add a loading class if set to false', async(() => {
443 | spyOn(promiseBtnDirective, 'addLoadingClass').and.callThrough();
444 |
445 | promiseBtnDirective.cfg.btnLoadingClass = false;
446 | fixture.componentInstance.testPromise = new Promise(() => {
447 | });
448 | fixture.detectChanges();
449 |
450 | fixture.whenStable().then(() => {
451 | expect(buttonElement.className).toBe('');
452 | });
453 | }));
454 | });
455 | });
456 |
457 | describe('cfg:handleCurrentBtnOnly', () => {
458 | let fixture: ComponentFixture;
459 | let buttonDebugElement: DebugElement;
460 | let divDebugElement: DebugElement;
461 | let buttonElement: HTMLButtonElement;
462 | let divElement: HTMLDivElement;
463 | let promiseBtnDirective1: PromiseBtnDirective;
464 | let promiseBtnDirective2: PromiseBtnDirective;
465 |
466 | beforeEach(() => {
467 | testUserCfg.handleCurrentBtnOnly = true;
468 |
469 | fixture = TestBed.overrideComponent(TestComponent, {
470 | set: {
471 | template: '2
'
472 | }
473 | }).createComponent(TestComponent);
474 | fixture.detectChanges();
475 | buttonDebugElement = fixture.debugElement.query(By.css('button'));
476 | divDebugElement = fixture.debugElement.query(By.css('div'));
477 | buttonElement = (buttonDebugElement.nativeElement as HTMLButtonElement);
478 | divElement = (divDebugElement.nativeElement as HTMLDivElement);
479 | promiseBtnDirective1 = buttonDebugElement.injector.get(PromiseBtnDirective);
480 | promiseBtnDirective2 = divDebugElement.injector.get(PromiseBtnDirective);
481 | fixture.detectChanges();
482 |
483 | promiseBtnDirective1.cfg.handleCurrentBtnOnly = true;
484 | promiseBtnDirective2.cfg.handleCurrentBtnOnly = true;
485 |
486 | fixture.componentInstance.testPromise = new Promise(() => {
487 | });
488 |
489 | spyOn(promiseBtnDirective1, 'initLoadingState').and.callThrough();
490 | spyOn(promiseBtnDirective2, 'initLoadingState').and.callThrough();
491 | spyOn(promiseBtnDirective1, 'handleCurrentBtnOnly').and.callThrough();
492 | fixture.detectChanges();
493 | });
494 |
495 | it('should cancel the click handler when handleCurrentBtnOnly is false', async(() => {
496 | promiseBtnDirective1.cfg.handleCurrentBtnOnly = false;
497 | buttonElement.click();
498 | fixture.detectChanges();
499 |
500 | fixture.whenStable().then(() => {
501 | expect(promiseBtnDirective1.handleCurrentBtnOnly()).toBe(true);
502 | expect(promiseBtnDirective1.initLoadingState).not.toHaveBeenCalled();
503 | });
504 | }));
505 |
506 | it('should set loading state for first button when clicked, but not for second', async(() => {
507 | buttonElement.click();
508 | fixture.detectChanges();
509 | fixture.whenStable().then(() => {
510 | expect(promiseBtnDirective1.initLoadingState).toHaveBeenCalled();
511 | expect(promiseBtnDirective2.initLoadingState).not.toHaveBeenCalled();
512 | });
513 | }));
514 |
515 | it('should set loading state for second button when clicked, but not for first', async(() => {
516 | divElement.click();
517 | fixture.detectChanges();
518 | fixture.whenStable().then(() => {
519 | expect(promiseBtnDirective1.initLoadingState).not.toHaveBeenCalled();
520 | expect(promiseBtnDirective2.initLoadingState).toHaveBeenCalled();
521 | });
522 | }));
523 |
524 | it('should set loading state when promise is set after click', async(() => {
525 | const setPromise = () => {
526 | fixture.componentInstance.testPromise = new Promise(() => {
527 | });
528 | };
529 |
530 | // remove initial promise
531 | fixture.componentInstance.testPromise = null;
532 | fixture.detectChanges();
533 |
534 | // add promise on click
535 | buttonElement.addEventListener('click', setPromise);
536 | fixture.detectChanges();
537 |
538 | buttonElement.click();
539 | fixture.detectChanges();
540 |
541 | fixture.whenStable().then(() => {
542 | expect(promiseBtnDirective1.initLoadingState).toHaveBeenCalled();
543 |
544 | // cleanup
545 | buttonElement.removeEventListener('click', setPromise);
546 | });
547 | }));
548 | });
549 |
550 | describe('cfg before runtime', () => {
551 | let fixture: ComponentFixture;
552 | let buttonDebugElement: DebugElement;
553 | let buttonElement: HTMLButtonElement;
554 | let promiseBtnDirective: PromiseBtnDirective;
555 |
556 | describe('cfg:spinnerTpl', () => {
557 | it('should add a custom template from config', () => {
558 | testUserCfg.spinnerTpl = 'loading
';
559 |
560 | fixture = TestBed.overrideComponent(TestComponent, {
561 | set: {
562 | template: ''
563 | }
564 | }).createComponent(TestComponent);
565 | fixture.detectChanges();
566 | buttonDebugElement = fixture.debugElement.query(By.css('button'));
567 | buttonElement = (buttonDebugElement.nativeElement as HTMLButtonElement);
568 | promiseBtnDirective = buttonDebugElement.injector.get(PromiseBtnDirective);
569 | fixture.detectChanges();
570 |
571 | const spinnerEl = buttonElement.querySelector('div');
572 | expect(spinnerEl && spinnerEl.outerHTML).toBe('loading
');
573 | });
574 | });
575 | });
576 |
577 |
578 | describe('simple boolean', () => {
579 | let fixture: ComponentFixture;
580 |
581 | it('should not remove', () => {
582 | fixture = TestBed.overrideComponent(TestComponent, {
583 | set: {
584 | template: ''
585 | }
586 | }).createComponent(TestComponent);
587 | const buttonDebugElement = fixture.debugElement.query(By.css('button'));
588 | const buttonElement = (buttonDebugElement.nativeElement as HTMLButtonElement);
589 | fixture.componentInstance.isDisabled = true;
590 |
591 | fixture.detectChanges();
592 | expect(buttonElement.hasAttribute('disabled')).toBe(true);
593 | });
594 | });
595 | });
596 |
597 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/src/promise-btn.directive.ts:
--------------------------------------------------------------------------------
1 | import {AfterContentInit, Directive, ElementRef, HostListener, Inject, Input, OnDestroy} from '@angular/core';
2 | import {Observable, Subscription} from 'rxjs';
3 | import {DEFAULT_CFG} from './default-promise-btn-config';
4 | import {PromiseBtnConfig} from './promise-btn-config';
5 | import {userCfg} from './user-cfg';
6 |
7 | @Directive({
8 | selector: '[promiseBtn]'
9 | })
10 |
11 | export class PromiseBtnDirective implements OnDestroy, AfterContentInit {
12 | cfg: PromiseBtnConfig;
13 | // the timeout used for min duration display
14 | minDurationTimeout: number;
15 | // boolean to determine minDurationTimeout state
16 | isMinDurationTimeoutDone: boolean;
17 | // boolean to determine if promise was resolved
18 | isPromiseDone: boolean;
19 | // the promise button button element
20 | btnEl: HTMLElement;
21 | // the promise itself or a function expression
22 | // NOTE: we need the type any here as we might deal with custom promises like bluebird
23 | promise: any;
24 |
25 | // this is added to fix the overriding of the disabled state by the loading indicator button.
26 | // https://github.com/johannesjo/angular2-promise-buttons/issues/34
27 | @Input('disabled')
28 | set isDisabledFromTheOutsideSetter(v: boolean) {
29 | this.isDisabledFromTheOutside = v;
30 | if (v) {
31 | // disabled means always disabled
32 | this.btnEl.setAttribute('disabled', 'disabled');
33 | } else if (this.isPromiseDone || this.isPromiseDone === undefined) {
34 | this.btnEl.removeAttribute('disabled');
35 | }
36 | // else the button is loading, so do not change the disabled loading state.
37 | }
38 |
39 | isDisabledFromTheOutside: boolean;
40 |
41 | private _fakePromiseResolve: (value: void) => void;
42 |
43 | constructor(el: ElementRef,
44 | @Inject(userCfg) cfg: PromiseBtnConfig) {
45 | // provide configuration
46 | this.cfg = Object.assign({}, DEFAULT_CFG, cfg);
47 |
48 | // save element
49 | this.btnEl = el.nativeElement;
50 | }
51 |
52 | @Input()
53 | set promiseBtn(passedValue: any) {
54 | const isObservable: boolean = passedValue instanceof Observable;
55 | const isSubscription: boolean = passedValue instanceof Subscription;
56 | const isBoolean: boolean = typeof passedValue === 'boolean';
57 | const isPromise: boolean = passedValue instanceof Promise || (
58 | passedValue !== null &&
59 | typeof passedValue === 'object' &&
60 | typeof passedValue.then === 'function' &&
61 | typeof passedValue.catch === 'function'
62 | );
63 |
64 | if (isObservable) {
65 | throw new TypeError('promiseBtn must be an instance of Subscription, instance of Observable given');
66 | } else if (isSubscription) {
67 | const sub: Subscription = passedValue;
68 | if (!sub.closed) {
69 | this.promise = new Promise((resolve) => {
70 | sub.add(resolve);
71 | });
72 | }
73 | } else if (isPromise) {
74 | this.promise = passedValue;
75 | } else if (isBoolean) {
76 | this.promise = this.createPromiseFromBoolean(passedValue);
77 | }
78 |
79 | this.checkAndInitPromiseHandler(this.btnEl);
80 | }
81 |
82 | ngAfterContentInit() {
83 | this.prepareBtnEl(this.btnEl);
84 | // trigger changes once to handle initial promises
85 | this.checkAndInitPromiseHandler(this.btnEl);
86 | }
87 |
88 | ngOnDestroy() {
89 | // cleanup
90 | if (this.minDurationTimeout) {
91 | clearTimeout(this.minDurationTimeout);
92 | }
93 | }
94 |
95 | createPromiseFromBoolean(val: boolean): Promise {
96 | if (val) {
97 | return new Promise((resolve) => {
98 | this._fakePromiseResolve = resolve;
99 | });
100 | } else {
101 | if (this._fakePromiseResolve) {
102 | this._fakePromiseResolve();
103 | }
104 | return this.promise;
105 | }
106 | }
107 |
108 | /**
109 | * Initializes all html and event handlers
110 | */
111 | prepareBtnEl(btnEl: HTMLElement) {
112 | // handle promises passed via promiseBtn attribute
113 | this.appendSpinnerTpl(btnEl);
114 | }
115 |
116 | /**
117 | * Checks if all required parameters are there and inits the promise handler
118 | */
119 | checkAndInitPromiseHandler(btnEl: HTMLElement) {
120 | // check if element and promise is set
121 | if (btnEl && this.promise) {
122 | this.initPromiseHandler(btnEl);
123 | }
124 | }
125 |
126 | /**
127 | * Helper FN to add class
128 | */
129 | addLoadingClass(el: any) {
130 | if (typeof this.cfg.btnLoadingClass === 'string') {
131 | el.classList.add(this.cfg.btnLoadingClass);
132 | }
133 | }
134 |
135 | /**
136 | * Helper FN to remove classes
137 | */
138 | removeLoadingClass(el: any) {
139 | if (typeof this.cfg.btnLoadingClass === 'string') {
140 | el.classList.remove(this.cfg.btnLoadingClass);
141 | }
142 | }
143 |
144 | /**
145 | * Handles everything to be triggered when the button is set
146 | * to loading state.
147 | */
148 | initLoadingState(btnEl: HTMLElement) {
149 | this.addLoadingClass(btnEl);
150 | this.disableBtn(btnEl);
151 | }
152 |
153 | /**
154 | * Handles everything to be triggered when loading is finished
155 | */
156 | cancelLoadingStateIfPromiseAndMinDurationDone(btnEl: HTMLElement) {
157 | if ((!this.cfg.minDuration || this.isMinDurationTimeoutDone) && this.isPromiseDone) {
158 | this.removeLoadingClass(btnEl);
159 | this.enableBtn(btnEl);
160 | }
161 | }
162 |
163 | disableBtn(btnEl: HTMLElement) {
164 | if (this.cfg.disableBtn) {
165 | btnEl.setAttribute('disabled', 'disabled');
166 | }
167 | }
168 |
169 | enableBtn(btnEl: HTMLElement) {
170 | if (this.cfg.disableBtn) {
171 | if (this.isDisabledFromTheOutside) {
172 | btnEl.setAttribute('disabled', 'disabled');
173 | } else {
174 | btnEl.removeAttribute('disabled');
175 | }
176 | }
177 | }
178 |
179 | /**
180 | * Initializes a watcher for the promise. Also takes
181 | * this.cfg.minDuration into account if given.
182 | */
183 |
184 | initPromiseHandler(btnEl: HTMLElement) {
185 | const promise = this.promise;
186 |
187 | // watch promise to resolve or fail
188 | this.isMinDurationTimeoutDone = false;
189 | this.isPromiseDone = false;
190 |
191 | // create timeout if option is set
192 | if (this.cfg.minDuration) {
193 | this.minDurationTimeout = window.setTimeout(() => {
194 | this.isMinDurationTimeoutDone = true;
195 | this.cancelLoadingStateIfPromiseAndMinDurationDone(btnEl);
196 | }, this.cfg.minDuration);
197 | }
198 |
199 | const resolveLoadingState = () => {
200 | this.isPromiseDone = true;
201 | this.cancelLoadingStateIfPromiseAndMinDurationDone(btnEl);
202 | };
203 |
204 | if (!this.cfg.handleCurrentBtnOnly) {
205 | this.initLoadingState(btnEl);
206 | }
207 | // native Promise doesn't have finally
208 | if (promise.finally) {
209 | promise.finally(resolveLoadingState);
210 | } else {
211 | promise
212 | .then(resolveLoadingState)
213 | .catch(resolveLoadingState);
214 | }
215 |
216 | }
217 |
218 |
219 | /**
220 | * $compile and append the spinner template to the button.
221 | */
222 | appendSpinnerTpl(btnEl: HTMLElement) {
223 | // TODO add some kind of compilation later on
224 | btnEl.insertAdjacentHTML('beforeend', this.cfg.spinnerTpl as string);
225 | }
226 |
227 | /**
228 | * Limit loading state to show only for the currently clicked button.
229 | * Executed only if this.cfg.handleCurrentBtnOnly is set
230 | */
231 | @HostListener('click')
232 | handleCurrentBtnOnly() {
233 | if (!this.cfg.handleCurrentBtnOnly) {
234 | return true; // return true for testing
235 | }
236 |
237 | // Click triggers @Input update
238 | // We need to use timeout to wait for @Input to update
239 | window.setTimeout(() => {
240 | // return if something else than a promise is passed
241 | if (!this.promise) {
242 | return;
243 | }
244 |
245 | this.initLoadingState(this.btnEl);
246 | }, 0);
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/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';
4 | import 'zone.js/dist/async-test.js';
5 | import 'zone.js/dist/proxy.js';
6 | import 'zone.js/dist/sync-test';
7 | import 'zone.js/dist/jasmine-patch';
8 |
9 | import {getTestBed} from '@angular/core/testing';
10 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} 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 |
22 | // And load the modules.
23 | context.keys().map(context);
24 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/src/user-cfg.ts:
--------------------------------------------------------------------------------
1 | import {InjectionToken} from '@angular/core';
2 |
3 | export const userCfg = new InjectionToken('cfg');
4 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "target": "es2015",
6 | "declaration": true,
7 | "inlineSources": true,
8 | "types": [],
9 | "lib": [
10 | "dom",
11 | "es2018"
12 | ]
13 | },
14 | "angularCompilerOptions": {
15 | "annotateForClosureCompiler": false,
16 | "skipTemplateCodegen": true,
17 | "strictMetadataEmit": true,
18 | "fullTemplateTypeCheck": true,
19 | "strictInjectionParameters": true,
20 | "enableResourceInlining": true,
21 | "enableIvy": true,
22 | "allowEmptyCodegenFiles": true,
23 | },
24 | "exclude": [
25 | "src/test.ts",
26 | "**/*.spec.ts"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.lib.json",
3 | "compilerOptions": {
4 | "declarationMap": false
5 | },
6 | "angularCompilerOptions": {
7 | "allowEmptyCodegenFiles": true,
8 | "compilationMode": "partial"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/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/angular2-promise-buttons/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../angular2-promise-buttons-demo/tslint.json",
3 | "rules": {
4 | "directive-selector": [
5 | false,
6 | "attribute",
7 | "lib",
8 | "camelCase"
9 | ],
10 | "component-selector": [
11 | false,
12 | "element",
13 | "lib",
14 | "kebab-case"
15 | ],
16 | "variable-name": [
17 | true,
18 | "allow-pascal-case",
19 | "allow-snake-case",
20 | "ban-keywords",
21 | "check-format",
22 | "allow-leading-underscore"
23 | ]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/projects/angular2-promise-buttons/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@ctrl/tinycolor@^2.6.0":
6 | version "2.6.0"
7 | resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-2.6.0.tgz#c269def5ed1f871913f299a475c01bdb39119eee"
8 | integrity sha512-bvkszNAcbmR2zrjjkaHbTVbEj07Id44HsBWf57mugPcvJNIPaWLqxWV/GUJVJuXXayqFP2X09cZRqKrCy/v10Q==
9 |
10 |
--------------------------------------------------------------------------------
/scripts/copy-readme-to-demo.js:
--------------------------------------------------------------------------------
1 | const marked = require('marked');
2 | const fs = require('fs');
3 |
4 | const demoIndexPagePath = __dirname + '/../dist/demo/index.html';
5 | const readmePath = __dirname + '/../README.md';
6 | const readmeNeedle = '___README_MD_NEEDLE___';
7 | const readme = fs.readFileSync(readmePath);
8 | const demoIndex = fs.readFileSync(demoIndexPagePath);
9 |
10 | const demoIndexHtml = demoIndex.toString();
11 | const readmeHtml = marked(readme.toString());
12 |
13 | const newDemoIndexHtml = demoIndexHtml.replace(readmeNeedle, readmeHtml);
14 |
15 | fs.writeFileSync(demoIndexPagePath, newDemoIndexHtml);
16 |
--------------------------------------------------------------------------------
/tsconfig.build-lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "experimentalDecorators": true,
8 | "removeComments": false,
9 | "noImplicitAny": true,
10 | "declaration": true,
11 | "outDir": "./dist",
12 | "stripInternal": true,
13 | "lib": [
14 | "dom",
15 | "es6"
16 | ]
17 | },
18 | "include": [
19 | "projects/angular2-promise-buttons-demo/src/**/*"
20 | ],
21 | "exclude": [
22 | "node_modules",
23 | "**/*.spec.ts"
24 | ],
25 | "files": [
26 | "./scripts/typings.d.ts",
27 | "./projects/angular2-promise-buttons-demo/src/index.ts"
28 | ],
29 | "angularCompilerOptions": {
30 | "skipTemplateCodegen": true
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "downlevelIteration": true,
9 | "experimentalDecorators": true,
10 | "module": "es2020",
11 | "moduleResolution": "node",
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2018",
19 | "dom"
20 | ],
21 | "paths": {
22 | "angular-material-css-variables": [
23 | "dist/angular-material-css-variables"
24 | ],
25 | "angular-material-css-variables/*": [
26 | "dist/angular-material-css-variables/*"
27 | ],
28 | "material-css-vars": [
29 | "dist/material-css-vars"
30 | ],
31 | "material-css-vars/*": [
32 | "dist/material-css-vars/*"
33 | ]
34 | }
35 | },
36 | "angularCompilerOptions": {
37 | "fullTemplateTypeCheck": true,
38 | "strictInjectionParameters": true
39 | }
40 | }
--------------------------------------------------------------------------------
/wallaby.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const wallabyWebpack = require('wallaby-webpack');
4 | const path = require('path');
5 |
6 | const compilerOptions = Object.assign(
7 | require('./tsconfig.json').compilerOptions);
8 |
9 | // don't generate the declaration files (.d.ts)
10 | compilerOptions.declaration = false;
11 |
12 | module.exports = function (wallaby) {
13 |
14 | const webpackPostprocessor = wallabyWebpack({
15 | entryPatterns: [
16 | 'scripts/wallabyTest.js',
17 | 'src/**/*spec.js'
18 | ],
19 |
20 | module: {
21 | loaders: [
22 | { test: /\.css$/, loader: 'raw-loader' },
23 | { test: /\.html$/, loader: 'raw-loader' },
24 | { test: /\.js$/, loader: 'angular2-template-loader', exclude: /node_modules/ },
25 | { test: /\.json$/, loader: 'json-loader' },
26 | { test: /\.styl$/, loaders: ['raw-loader', 'stylus-loader'] },
27 | { test: /\.less$/, loaders: ['raw-loader', 'less-loader'] },
28 | { test: /\.scss$|\.sass$/, loaders: ['raw-loader', 'sass-loader'] },
29 | { test: /\.(jpg|png)$/, loader: 'url-loader?limit=128000' }
30 | ]
31 | },
32 |
33 | resolve: {
34 | modules: [
35 | path.join(wallaby.projectCacheDir, 'demo/src'),
36 | path.join(wallaby.projectCacheDir, 'src')
37 | ]
38 | }
39 | });
40 |
41 | return {
42 | files: [
43 | { pattern: 'src/**/*.ts', load: false },
44 | { pattern: 'src/**/*.d.ts', ignore: true },
45 | { pattern: 'src/**/*.css', load: false },
46 | { pattern: 'src/**/*.less', load: false },
47 | { pattern: 'src/**/*.scss', load: false },
48 | { pattern: 'src/**/*.sass', load: false },
49 | { pattern: 'src/**/*.styl', load: false },
50 | { pattern: 'src/**/*.html', load: false },
51 | { pattern: 'src/**/*.json', load: false },
52 | { pattern: 'scripts/*.ts', load: false },
53 | { pattern: 'src/**/*spec.ts', ignore: true },
54 | { pattern: 'demo/src/**/*.ts', load: false },
55 | { pattern: 'demo/src/**/*.d.ts', ignore: true },
56 | { pattern: 'demo/src/**/*.css', load: false },
57 | { pattern: 'demo/src/**/*.less', load: false },
58 | { pattern: 'demo/src/**/*.scss', load: false },
59 | { pattern: 'demo/src/**/*.sass', load: false },
60 | { pattern: 'demo/src/**/*.styl', load: false },
61 | { pattern: 'demo/src/**/*.html', load: false },
62 | { pattern: 'demo/src/**/*.json', load: false },
63 | { pattern: 'demo/src/**/*spec.ts', ignore: true }
64 | ],
65 |
66 | tests: [
67 | { pattern: 'src/**/*spec.ts', load: false }
68 | ],
69 |
70 | testFramework: 'jasmine',
71 | preprocessors: {
72 | './scripts/test.ts': ['@angular/cli']
73 | },
74 | compilers: {
75 | '**/*.ts': wallaby.compilers.typeScript(compilerOptions)
76 | },
77 |
78 | env: {
79 | kind: 'electron'
80 | },
81 |
82 | postprocessor: webpackPostprocessor,
83 |
84 | setup: function () {
85 | window.__moduleBundler.loadTests();
86 | },
87 |
88 | debug: true
89 | };
90 | };
91 |
--------------------------------------------------------------------------------