├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── config ├── helpers.js ├── karma.conf.js ├── spec-bundle.js ├── testing-utils.ts └── webpack.test.js ├── index.ts ├── karma.conf.js ├── package.json ├── src ├── slim-loading-bar.component.ts ├── slim-loading-bar.service.ts └── slim-loading-bar.utils.ts ├── style.css ├── tests ├── slim-loading-bar.component.spec.ts └── slim-loading-bar.service.spec.ts ├── tsconfig.json ├── tslint.json ├── typings.json └── webpack.config.js /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting Pull Requests 2 | If you're changing the structure of the repository please create an issue first 3 | 4 | ## Submitting bug reports 5 | 6 | Make sure you are on latest changes and that you ran this command `npm install` after updating your local repository. If you can, please provide more infomation about your environment such as browser, operating system, node version, and npm version -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **I'm submitting a ...** (check one with "x") 2 | ``` 3 | [ ] bug report => check the FAQ and search github for a similar issue or PR before submitting 4 | [ ] support request => check the FAQ and search github for a similar issue before submitting 5 | [ ] feature request 6 | ``` 7 | 8 | **Current behavior** 9 | 10 | 11 | **Expected/desired behavior** 12 | 13 | 14 | **Reproduction of the problem** 15 | If the current behavior is a bug or you can illustrate your feature request better with an example, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar. 16 | 17 | 18 | **What is the expected behavior?** 19 | 20 | 21 | **What is the motivation / use case for changing the behavior?** 22 | 23 | 24 | **Please tell us about your environment:** 25 | 26 | * **ng2-slim-loading-bar version:** x.x.x 27 | 28 | * **Angular version:** 2.X.X 29 | 30 | * **Browser:** [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] 31 | 32 | * **Language:** [all | TypeScript X.X | ES6/7 | ES5] 33 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 2 | 3 | 4 | 5 | * **What is the current behavior?** (You can also link to an open issue here) 6 | 7 | 8 | 9 | * **What is the new behavior (if this is a feature change)?** 10 | 11 | 12 | 13 | * **Other information**: 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Misc 3 | ################# 4 | **/.DS_Store 5 | nbproject 6 | manifest.mf 7 | build.xml 8 | node_modules/* 9 | npm-debug.log 10 | *.js 11 | !config/* 12 | !karma.conf.js 13 | !webpack.config.js 14 | *.map 15 | *.d.ts 16 | !make.js 17 | coverage 18 | *.metadata.json 19 | bundles 20 | .vscode 21 | 22 | ################# 23 | ## JetBrains 24 | ################# 25 | .idea 26 | .project 27 | .settings 28 | 29 | ############ 30 | ## Windows 31 | ############ 32 | 33 | # Windows image file caches 34 | Thumbs.db 35 | 36 | # Folder config file 37 | Desktop.ini 38 | 39 | ############ 40 | ## Mac 41 | ############ 42 | 43 | # Mac crap 44 | .DS_Store 45 | 46 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Misc 3 | ################# 4 | **/.DS_Store 5 | nbproject 6 | manifest.mf 7 | build.xml 8 | node_modules/* 9 | npm-debug.log 10 | *.ts 11 | !*.d.ts 12 | tests 13 | .github 14 | coverage 15 | !*.metadata.json 16 | !bundles/*.js 17 | *.ngFactory.ts 18 | 19 | ################# 20 | ## JetBrains 21 | ################# 22 | .idea 23 | .project 24 | .settings 25 | 26 | ############ 27 | ## Windows 28 | ############ 29 | 30 | # Windows image file caches 31 | Thumbs.db 32 | 33 | # Folder config file 34 | Desktop.ini 35 | 36 | ############ 37 | ## Mac 38 | ############ 39 | 40 | # Mac crap 41 | .DS_Store 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | cache: 4 | directories: 5 | - $HOME/.npm 6 | - $HOME/.yarn-cache 7 | - node_modules 8 | 9 | sudo: false 10 | 11 | notifications: 12 | email: false 13 | 14 | node_js: 15 | - '6' 16 | 17 | branches: 18 | except: 19 | - "/^v\\d+\\.\\d+\\.\\d+$/" 20 | 21 | before_install: 22 | - export CHROME_BIN=chromium-browser 23 | - npm i -g yarn 24 | 25 | before_script: 26 | - npm prune 27 | 28 | install: 29 | - yarn 30 | 31 | after_success: 32 | - npm run semantic-release 33 | 34 | before_script: 35 | - export DISPLAY=:99.0 36 | - sh -e /etc/init.d/xvfb start -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sergey Akopkokhyants 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 | # Angular 2 Slim Loading Bar [![npm version](https://img.shields.io/npm/v/ng2-slim-loading-bar.svg)](https://www.npmjs.com/package/ng2-slim-loading-bar) [![npm monthly downloads](https://img.shields.io/npm/dm/ng2-slim-loading-bar.svg?style=flat-square)](https://www.npmjs.com/package/ng2-slim-loading-bar) 2 | Angular2 component shows slim loading bar at the top of the page of your application. 3 | 4 | [![Build Status](https://travis-ci.org/akserg/ng2-slim-loading-bar.svg?branch=master)](https://travis-ci.org/akserg/ng2-slim-loading-bar) 5 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 6 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 7 | [![Dependency Status](https://david-dm.org/akserg/ng2-slim-loading-bar.svg)](https://david-dm.org/akserg/ng2-slim-loading-bar) 8 | [![devDependency Status](https://david-dm.org/akserg/ng2-slim-loading-bar/dev-status.svg)](https://david-dm.org/akserg/ng2-slim-loading-bar#info=devDependencies) 9 | [![Known Vulnerabilities](https://snyk.io/test/github/akserg/ng2-slim-loading-bar/badge.svg)](https://snyk.io/test/github/akserg/ng2-slim-loading-bar) 10 | 11 | Follow me [![twitter](https://img.shields.io/twitter/follow/akopkokhyants.svg?style=social&label=%20akopkokhyants)](https://twitter.com/akopkokhyants) to be notified about new releases. 12 | 13 | _Some of these APIs and Components are not final and are subject to change!_ 14 | 15 | ## Installation 16 | 17 | ```sh 18 | npm install ng2-slim-loading-bar --save 19 | ``` 20 | 21 | ## Demo 22 | Simple examples using ng2-slim-loading-bar: 23 | - with SystemJS in [ng2-systemjs-demo](https://github.com/akserg/ng2-systemjs-demo) 24 | - with Webpack in [ng2-webpack-demo](https://github.com/akserg/ng2-webpack-demo) 25 | 26 | Online demo available [here](http://akserg.github.io/ng2-webpack-demo) 27 | 28 | ## Usage 29 | 30 | If you use SystemJS to load your files, you might have to update your config: 31 | 32 | ```js 33 | System.config({ 34 | map: { 35 | 'ng2-slim-loading-bar': 'node_modules/ng2-slim-loading-bar/bundles/index.umd.js' 36 | } 37 | }); 38 | ``` 39 | 40 | #### 1. Update the markup 41 | - Import the `style.css` file into your web page 42 | - Add `` tag in template of your application component. 43 | 44 | #### 2. Import the `SlimLoadingBarModule` 45 | Import `SlimLoadingBarModule.forRoot()` in the NgModule of your application. 46 | The `forRoot` method is a convention for modules that provide a singleton service. 47 | 48 | ```ts 49 | import {BrowserModule} from "@angular/platform-browser"; 50 | import {NgModule} from '@angular/core'; 51 | import {SlimLoadingBarModule} from 'ng2-slim-loading-bar'; 52 | 53 | @NgModule({ 54 | imports: [ 55 | BrowserModule, 56 | SlimLoadingBarModule.forRoot() 57 | ], 58 | bootstrap: [AppComponent] 59 | }) 60 | export class AppModule { 61 | } 62 | ``` 63 | 64 | If you have multiple NgModules and you use one as a shared NgModule (that you import in all of your other NgModules), 65 | don't forget that you can use it to export the `SlimLoadingBarModule` that you imported in order to avoid having to import it multiple times. 66 | 67 | ```ts 68 | @NgModule({ 69 | imports: [ 70 | BrowserModule, 71 | SlimLoadingBarModule.forRoot() 72 | ], 73 | exports: [BrowserModule, SlimLoadingBarModule], 74 | }) 75 | export class SharedModule { 76 | } 77 | ``` 78 | 79 | #### 3. Use the `SlimLoadingBarService` for your application 80 | - Import `SlimLoadingBarService` from `ng2-slim-loading-bar` in your application code: 81 | 82 | ```js 83 | import {Component} from '@angular/core'; 84 | import {SlimLoadingBarService} from 'ng2-slim-loading-bar'; 85 | 86 | @Component({ 87 | selector: 'app', 88 | template: ` 89 |
Hello world
90 | 91 | 92 | 93 | 94 | ` 95 | }) 96 | export class AppComponent { 97 | 98 | constructor(private slimLoadingBarService: SlimLoadingBarService) { } 99 | 100 | startLoading() { 101 | this.slimLoadingBarService.start(() => { 102 | console.log('Loading complete'); 103 | }); 104 | } 105 | 106 | stopLoading() { 107 | this.slimLoadingBarService.stop(); 108 | } 109 | 110 | completeLoading() { 111 | this.slimLoadingBarService.complete(); 112 | } 113 | } 114 | ``` 115 | 116 | #### 3. Customize the the `ng2-slim-loading-bar` for your application 117 | You can use the following properties to customize the `ng2-slim-loading-bar` component in your template: 118 | - `color` - The color of loading bar. Default is `firebrick`. It can be any CSS compatible value. 119 | - `height` - The height of loading bar. Default value is `2px`. 120 | - `show` - The flag helps hide and show the loading bar. Default value is `true`. 121 | 122 | Example: 123 | `` 124 | 125 | #### 4. Manage the loading bar 126 | You can use the following properties to customize the SlimLoadingBar via instance of SlimLoadingBarService: 127 | - `color` - The color of loading bar. 128 | - `height` - The height of loading bar. 129 | - `visible` - The flag helps hide and show the loading bar, false for hidden and true for visible. 130 | 131 | You can use the following methods to control the SlimLoadingBar via instance of SlimLoadingBarService: 132 | - `start` - Start the loading progress. Use the callback function as an parameter to listed the complete event. 133 | - `stop` - Stop the loading progress. This method pause the current position of loading progress. 134 | - `reset`- Reset the position of loading progress to 0. 135 | - `complete` - Set the progress to 100% and hide the progress bar. 136 | 137 | #### 5. Events handling 138 | You can hook up with our different types of events thrown. 139 | - `SlimLoadingBarEventType.PROGRESS` 140 | - `SlimLoadingBarEventType.HEIGHT` 141 | - `SlimLoadingBarEventType.COLOR` 142 | - `SlimLoadingBarEventType.VISIBLE` 143 | 144 | you can subscribe to these events types by simplying doing this 145 | ```js 146 | constructor(private _loadingBar: SlimLoadingBarService) { 147 | this._loadingBar.events.subscribe((item:SlimLoadingBarEvent) => console.log(item)); 148 | } 149 | ``` 150 | where item returned is of `SlimLoadingBarEvent {type: SlimLoadingBarEventType, value: any}` 151 | 152 | # Credits 153 | Inspired by [ngProgress.js](https://github.com/VictorBjelkholm/ngProgress) 154 | 155 | # License 156 | [MIT](/LICENSE) 157 | -------------------------------------------------------------------------------- /config/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * taken from angular2-webpack-starter 3 | */ 4 | var path = require('path'); 5 | 6 | // Helper functions 7 | var ROOT = path.resolve(__dirname, '..'); 8 | 9 | function hasProcessFlag(flag) { 10 | return process.argv.join('').indexOf(flag) > -1; 11 | } 12 | 13 | function isWebpackDevServer() { 14 | return process.argv[1] && !! (/webpack-dev-server$/.exec(process.argv[1])); 15 | } 16 | 17 | function root(args) { 18 | args = Array.prototype.slice.call(arguments, 0); 19 | return path.join.apply(path, [ROOT].concat(args)); 20 | } 21 | 22 | function checkNodeImport(context, request, cb) { 23 | if (!path.isAbsolute(request) && request.charAt(0) !== '.') { 24 | cb(null, 'commonjs ' + request); return; 25 | } 26 | cb(); 27 | } 28 | 29 | exports.hasProcessFlag = hasProcessFlag; 30 | exports.isWebpackDevServer = isWebpackDevServer; 31 | exports.root = root; 32 | exports.checkNodeImport = checkNodeImport; -------------------------------------------------------------------------------- /config/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | var testWebpackConfig = require('./webpack.test.js'); 3 | 4 | var configuration = { 5 | basePath: '', 6 | 7 | frameworks: ['jasmine'], 8 | 9 | // list of files to exclude 10 | exclude: [ ], 11 | 12 | /* 13 | * list of files / patterns to load in the browser 14 | * 15 | * we are building the test environment in ./spec-bundle.js 16 | */ 17 | files: [ { pattern: './config/spec-bundle.js', watched: false } ], 18 | 19 | preprocessors: { './config/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'] }, 20 | 21 | // Webpack Config at ./webpack.test.js 22 | webpack: testWebpackConfig, 23 | 24 | coverageReporter: { 25 | type: 'in-memory' 26 | }, 27 | 28 | remapCoverageReporter: { 29 | 'text-summary': null, 30 | json: './coverage/coverage.json', 31 | html: './coverage/html' 32 | }, 33 | 34 | // Webpack please don't spam the console when running in karma! 35 | webpackMiddleware: { stats: 'errors-only'}, 36 | 37 | reporters: [ 'mocha', 'coverage', 'remap-coverage' ], 38 | 39 | // web server port 40 | port: 9876, 41 | 42 | colors: true, 43 | 44 | /* 45 | * level of logging 46 | * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 47 | */ 48 | logLevel: config.LOG_INFO, 49 | 50 | autoWatch: false, 51 | 52 | browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'], 53 | 54 | singleRun: true 55 | }; 56 | 57 | config.set(configuration); 58 | }; -------------------------------------------------------------------------------- /config/spec-bundle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * When testing with webpack and ES6, we have to do some extra 3 | * things to get testing to work right. Because we are gonna write tests 4 | * in ES6 too, we have to compile those as well. That's handled in 5 | * karma.conf.js with the karma-webpack plugin. This is the entry 6 | * file for webpack test. Just like webpack will create a bundle.js 7 | * file for our client, when we run test, it will compile and bundle them 8 | * all here! Crazy huh. So we need to do some setup 9 | */ 10 | Error.stackTraceLimit = Infinity; 11 | 12 | require('core-js/es6'); 13 | require('core-js/es7/reflect'); 14 | 15 | // Typescript emit helpers polyfill 16 | require('ts-helpers'); 17 | 18 | require('zone.js/dist/zone'); 19 | require('zone.js/dist/long-stack-trace-zone'); 20 | require('zone.js/dist/async-test'); 21 | require('zone.js/dist/fake-async-test'); 22 | require('zone.js/dist/sync-test'); 23 | require('zone.js/dist/proxy'); // since zone.js 0.6.15 24 | require('zone.js/dist/jasmine-patch'); // put here since zone.js 0.6.14 25 | 26 | // RxJS 27 | require('rxjs/Rx'); 28 | 29 | var testing = require('@angular/core/testing'); 30 | var browser = require('@angular/platform-browser-dynamic/testing'); 31 | 32 | testing.TestBed.initTestEnvironment( 33 | browser.BrowserDynamicTestingModule, 34 | browser.platformBrowserDynamicTesting() 35 | ); 36 | 37 | /* 38 | * Ok, this is kinda crazy. We can use the context method on 39 | * require that webpack created in order to tell webpack 40 | * what files we actually want to require or import. 41 | * Below, context will be a function/object with file names as keys. 42 | * Using that regex we are saying look in ../src then find 43 | * any file that ends with spec.ts and get its path. By passing in true 44 | * we say do this recursively 45 | */ 46 | var testContext = require.context('../tests', true, /\.spec\.ts/); 47 | 48 | /* 49 | * get all the files, for each file, call the context function 50 | * that will require the file and load it up here. Context will 51 | * loop and require those spec files here 52 | */ 53 | function requireAll(requireContext) { 54 | return requireContext.keys().map(requireContext); 55 | } 56 | 57 | // requires and returns all modules that match 58 | var modules = requireAll(testContext); -------------------------------------------------------------------------------- /config/testing-utils.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /* 4 | Temporary fiile for referencing the TypeScript defs for Jasmine + some potentially 5 | utils for testing. Will change/adjust this once I find a better way of doing 6 | */ 7 | 8 | declare module jasmine { 9 | interface Matchers { 10 | toHaveText(text: string): boolean; 11 | toContainText(text: string): boolean; 12 | } 13 | } 14 | 15 | beforeEach(() => { 16 | jasmine.addMatchers({ 17 | 18 | toHaveText: function() { 19 | return { 20 | compare: function(actual, expectedText) { 21 | var actualText = actual.textContent; 22 | return { 23 | pass: actualText === expectedText, 24 | get message() { 25 | return 'Expected ' + actualText + ' to equal ' + expectedText; 26 | } 27 | }; 28 | } 29 | }; 30 | }, 31 | 32 | toContainText: function() { 33 | return { 34 | compare: function(actual, expectedText) { 35 | var actualText = actual.textContent; 36 | return { 37 | pass: actualText.indexOf(expectedText) > -1, 38 | get message() { 39 | return 'Expected ' + actualText + ' to contain ' + expectedText; 40 | } 41 | }; 42 | } 43 | }; 44 | } 45 | }); 46 | }); -------------------------------------------------------------------------------- /config/webpack.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adapted from angular2-webpack-starter 3 | */ 4 | 5 | const helpers = require('./helpers'), 6 | webpack = require('webpack'), 7 | LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'); 8 | 9 | /** 10 | * Webpack Plugins 11 | */ 12 | 13 | module.exports = { 14 | 15 | /** 16 | * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack 17 | * 18 | * Do not change, leave as is or it wont work. 19 | * See: https://github.com/webpack/karma-webpack#source-maps 20 | */ 21 | devtool: 'inline-source-map', 22 | 23 | resolve: { 24 | extensions: ['.ts', '.js'], 25 | modules: [helpers.root('src'), 'node_modules'] 26 | }, 27 | 28 | module: { 29 | rules: [{ 30 | enforce: 'pre', 31 | test: /\.ts$/, 32 | loader: 'tslint-loader', 33 | exclude: [helpers.root('node_modules')] 34 | }, { 35 | enforce: 'pre', 36 | test: /\.js$/, 37 | loader: 'source-map-loader', 38 | exclude: [ 39 | // these packages have problems with their sourcemaps 40 | helpers.root('node_modules/rxjs'), 41 | helpers.root('node_modules/@angular') 42 | ] 43 | }, { 44 | test: /\.ts$/, 45 | loader: 'awesome-typescript-loader', 46 | query: { 47 | // use inline sourcemaps for "karma-remap-coverage" reporter 48 | sourceMap: false, 49 | inlineSourceMap: true, 50 | module: "commonjs", 51 | removeComments: true 52 | }, 53 | exclude: [/\.e2e\.ts$/] 54 | }, { 55 | enforce: 'post', 56 | test: /\.(js|ts)$/, 57 | loader: 'istanbul-instrumenter-loader', 58 | include: helpers.root('src'), 59 | exclude: [/\.spec\.ts$/, /\.e2e\.ts$/, /node_modules/] 60 | }], 61 | }, 62 | 63 | plugins: [ 64 | // fix the warning in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js 65 | new webpack.ContextReplacementPlugin( 66 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, 67 | helpers.root('./src') 68 | ), 69 | 70 | new LoaderOptionsPlugin({ 71 | debug: true, 72 | options: { 73 | 74 | /** 75 | * Static analysis linter for TypeScript advanced options configuration 76 | * Description: An extensible linter for the TypeScript language. 77 | * 78 | * See: https://github.com/wbuchwalter/tslint-loader 79 | */ 80 | 'tslint-loader': { 81 | emitErrors: false, 82 | failOnHint: false, 83 | resourcePath: 'src' 84 | }, 85 | 86 | } 87 | }) 88 | ] 89 | }; 90 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 Sergey Akopkokhyants 2 | // This project is licensed under the terms of the MIT license. 3 | // https://github.com/akserg/ng2-slim-loading-bar 4 | 5 | import { NgModule, ModuleWithProviders } from "@angular/core"; 6 | 7 | import { SlimLoadingBarComponent } from './src/slim-loading-bar.component'; 8 | import { SlimLoadingBarService } from './src/slim-loading-bar.service'; 9 | 10 | export * from './src/slim-loading-bar.component'; 11 | export * from './src/slim-loading-bar.service'; 12 | 13 | @NgModule({ 14 | declarations: [SlimLoadingBarComponent], 15 | exports: [SlimLoadingBarComponent], 16 | providers: [SlimLoadingBarService] 17 | }) 18 | export class SlimLoadingBarModule { 19 | static forRoot(): ModuleWithProviders { 20 | return { 21 | ngModule: SlimLoadingBarModule, 22 | providers: [SlimLoadingBarService] 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Look in ./config for karma.conf.js 2 | module.exports = require('./config/karma.conf.js'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-slim-loading-bar", 3 | "description": "Angular2 component shows slim loading bar at the top of the page", 4 | "version": "0.0.0-semantically-released", 5 | "scripts": { 6 | "test": "karma start", 7 | "test-watch": "tsc && karma start --no-single-run --auto-watch", 8 | "commit": "npm run prepublish && npm test && git-cz", 9 | "prepublish": "ngc && npm run build", 10 | "build": "webpack && cp style.css bundles/style.css", 11 | "semantic-release": "semantic-release pre && npm publish && semantic-release post", 12 | "analyze": "ANALYZE=true webpack" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/akserg/ng2-slim-loading-bar.git" 17 | }, 18 | "keywords": [ 19 | "angular", 20 | "angular2", 21 | "slim", 22 | "loading", 23 | "bar", 24 | "progress" 25 | ], 26 | "author": "Sergey Akopkokhyants", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/akserg/ng2-slim-loading-bar/issues" 30 | }, 31 | "main": "bundles/index.umd.js", 32 | "module": "index.js", 33 | "typings": "index.d.ts", 34 | "homepage": "https://github.com/akserg/ng2-slim-loading-bar", 35 | "peerDependencies": { 36 | "@angular/core": "^2.4.7 || ^4.3.0" 37 | }, 38 | "devDependencies": { 39 | "@angular/common": "^2.4.7 || ^4.3.0", 40 | "@angular/compiler": "^2.4.7", 41 | "@angular/compiler-cli": "^2.4.7", 42 | "@angular/core": "^2.4.7", 43 | "@angular/platform-browser": "^2.4.7", 44 | "@angular/platform-browser-dynamic": "^2.4.7", 45 | "@angular/platform-server": "^2.4.7", 46 | "@types/hammerjs": "^2.0.33", 47 | "@types/jasmine": "2.5.37", 48 | "@types/node": "6.0.46", 49 | "awesome-typescript-loader": "2.2.4", 50 | "codelyzer": "2.0.0-beta.2", 51 | "commitizen": "2.8.6", 52 | "core-js": "2.4.1", 53 | "cz-conventional-changelog": "1.2.0", 54 | "istanbul-instrumenter-loader": "0.2.0", 55 | "jasmine-core": "2.5.2", 56 | "karma": "1.3.0", 57 | "karma-chrome-launcher": "2.0.0", 58 | "karma-coverage": "1.1.1", 59 | "karma-firefox-launcher": "1.0.0", 60 | "karma-jasmine": "1.0.2", 61 | "karma-mocha-reporter": "^2.1.0", 62 | "karma-remap-coverage": "~0.1.2", 63 | "karma-sourcemap-loader": "^0.3.7", 64 | "karma-webpack": "^2.0.1", 65 | "loader-utils": "~0.2.16", 66 | "reflect-metadata": "^0.1.8", 67 | "rxjs": "^5.0.3", 68 | "semantic-release": "4.3.5", 69 | "source-map-loader": "0.1.5", 70 | "ts-helpers": "1.1.2", 71 | "tslint": "~4.0.0", 72 | "tslint-loader": "3.3.0", 73 | "typescript": "2.0.10", 74 | "webpack": "2.2.0", 75 | "webpack-bundle-analyzer": "^2.9.0", 76 | "zone.js": "^0.7.6" 77 | }, 78 | "config": { 79 | "commitizen": { 80 | "path": "./node_modules/cz-conventional-changelog" 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/slim-loading-bar.component.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 Sergey Akopkokhyants 2 | // This project is licensed under the terms of the MIT license. 3 | // https://github.com/akserg/ng2-slim-loading-bar 4 | 5 | import { Component, Input, OnInit, AfterViewInit, ChangeDetectorRef, ChangeDetectionStrategy, ElementRef } from '@angular/core'; 6 | 7 | import { SlimLoadingBarService, SlimLoadingBarEvent, SlimLoadingBarEventType } from './slim-loading-bar.service'; 8 | import { isPresent } from './slim-loading-bar.utils'; 9 | 10 | /** 11 | * A Slim Loading Bar component shows message loading progress bar on the top of web page or parent component. 12 | */ 13 | @Component({ 14 | selector: 'ng2-slim-loading-bar', 15 | changeDetection: ChangeDetectionStrategy.OnPush, 16 | template: ` 17 |
18 |
20 |
` 21 | }) 22 | export class SlimLoadingBarComponent implements OnInit, AfterViewInit { 23 | 24 | isTransition: string = 'none'; 25 | 26 | private _progress: string = '0'; 27 | @Input() set progress(progress: string) { 28 | this.isTransition = progress >= this._progress ? 'all 0.5s ease-in-out' : 'none'; 29 | this._progress = progress; 30 | } 31 | 32 | get progress() { 33 | return this._progress; 34 | } 35 | 36 | @Input() color: string = 'firebrick'; 37 | @Input() height: string = '2px'; 38 | @Input() show: boolean = true; 39 | 40 | constructor(public service: SlimLoadingBarService, private _elmRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef) { } 41 | 42 | ngOnInit(): void { 43 | this.service.events.subscribe((event: SlimLoadingBarEvent) => { 44 | if (event.type === SlimLoadingBarEventType.PROGRESS && isPresent(event.value)) { 45 | this.progress = event.value; 46 | } else if (event.type === SlimLoadingBarEventType.COLOR) { 47 | this.color = event.value; 48 | } else if (event.type === SlimLoadingBarEventType.HEIGHT) { 49 | this.height = event.value; 50 | } else if (event.type === SlimLoadingBarEventType.VISIBLE) { 51 | this.show = event.value; 52 | } 53 | }); 54 | } 55 | 56 | ngAfterViewInit(): void { 57 | this.service.events.subscribe((event: SlimLoadingBarEvent) => { 58 | this._elmRef.nativeElement.visible = event.type === SlimLoadingBarEventType.VISIBLE ? event.value : true; 59 | this._changeDetectorRef.detectChanges(); 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/slim-loading-bar.service.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016 Sergey Akopkokhyants 2 | // This project is licensed under the terms of the MIT license. 3 | // https://github.com/akserg/ng2-slim-loading-bar 4 | 5 | import {Injectable} from '@angular/core'; 6 | 7 | import {isPresent} from './slim-loading-bar.utils'; 8 | import {Subject} from 'rxjs/Subject'; 9 | import {Observable} from 'rxjs/Observable'; 10 | 11 | export enum SlimLoadingBarEventType { 12 | PROGRESS, 13 | HEIGHT, 14 | COLOR, 15 | VISIBLE 16 | } 17 | 18 | export class SlimLoadingBarEvent { 19 | constructor(public type:SlimLoadingBarEventType, public value:any) {} 20 | } 21 | 22 | /** 23 | * SlimLoadingBar service helps manage Slim Loading bar on the top of screen or parent component 24 | */ 25 | @Injectable() 26 | export class SlimLoadingBarService { 27 | 28 | private _progress:number = 0; 29 | private _height:string = '2px'; 30 | private _color:string = 'firebrick'; 31 | private _visible:boolean = true; 32 | 33 | private _intervalCounterId:any = 0; 34 | public interval:number = 500; // in milliseconds 35 | 36 | private eventSource: Subject = new Subject(); 37 | public events: Observable = this.eventSource.asObservable(); 38 | 39 | constructor() {} 40 | 41 | set progress(value:number) { 42 | if (isPresent(value)) { 43 | if (value > 0) { 44 | this.visible = true; 45 | } 46 | this._progress = value; 47 | this.emitEvent(new SlimLoadingBarEvent(SlimLoadingBarEventType.PROGRESS, this._progress)); 48 | } 49 | } 50 | 51 | get progress():number { 52 | return this._progress; 53 | } 54 | 55 | 56 | set height(value:string) { 57 | if (isPresent(value)) { 58 | this._height = value; 59 | this.emitEvent(new SlimLoadingBarEvent(SlimLoadingBarEventType.HEIGHT, this._height)); 60 | } 61 | } 62 | 63 | get height():string { 64 | return this._height; 65 | } 66 | 67 | set color(value:string) { 68 | if (isPresent(value)) { 69 | this._color = value; 70 | this.emitEvent(new SlimLoadingBarEvent(SlimLoadingBarEventType.COLOR, this._color)); 71 | } 72 | } 73 | 74 | get color():string { 75 | return this._color; 76 | } 77 | 78 | set visible(value: boolean) { 79 | if (isPresent(value)) { 80 | this._visible = value; 81 | this.emitEvent(new SlimLoadingBarEvent(SlimLoadingBarEventType.VISIBLE, this._visible)); 82 | } 83 | } 84 | 85 | get visible():boolean { 86 | return this._visible; 87 | } 88 | 89 | private emitEvent(event: SlimLoadingBarEvent) { 90 | if (this.eventSource) { 91 | // Push up a new event 92 | this.eventSource.next(event); 93 | } 94 | } 95 | 96 | 97 | start(onCompleted:Function = null) { 98 | // Stop current timer 99 | this.stop(); 100 | // Make it visible for sure 101 | this.visible = true; 102 | // Run the timer with milliseconds iterval 103 | this._intervalCounterId = setInterval(() => { 104 | // Increment the progress and update view component 105 | this.progress++; 106 | // If the progress is 100% - call complete 107 | if (this.progress === 100) { 108 | this.complete(onCompleted); 109 | } 110 | }, this.interval); 111 | } 112 | 113 | stop() { 114 | if (this._intervalCounterId) { 115 | clearInterval(this._intervalCounterId); 116 | this._intervalCounterId = null; 117 | } 118 | } 119 | 120 | reset() { 121 | this.stop(); 122 | this.progress = 0; 123 | } 124 | 125 | complete(onCompleted: Function = null) { 126 | this.progress = 100; 127 | this.stop(); 128 | setTimeout(() => { 129 | // Hide it away 130 | this.visible = false; 131 | setTimeout(() => { 132 | // Drop to 0 133 | this.progress = 0; 134 | if (onCompleted) { 135 | onCompleted(); 136 | } 137 | }, 250); 138 | }, 250); 139 | } 140 | 141 | 142 | } 143 | 144 | -------------------------------------------------------------------------------- /src/slim-loading-bar.utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check and return true if an object not undefined or null 3 | */ 4 | export function isPresent(obj: any) { 5 | return obj !== undefined && obj !== null; 6 | } -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2016 Sergey Akopkokhyants 3 | * This project is licensed under the terms of the MIT license. 4 | * https://github.com/akserg/ng2-slim-loading-bar 5 | */ 6 | 7 | 8 | /* Styling for the Slim Loading Progress Bar container */ 9 | .slim-loading-bar { 10 | position: fixed; 11 | margin: 0; 12 | padding: 0; 13 | top: 0; 14 | left: 0; 15 | right: 0; 16 | z-index: 99999; 17 | } 18 | 19 | /* Styling for the Slim Loading Progress Bar itself */ 20 | .slim-loading-bar-progress { 21 | margin: 0; 22 | padding: 0; 23 | z-index: 99998; 24 | background-color: rgba(46, 204, 113,1.0); 25 | color: rgba(46, 204, 113,1.0); 26 | box-shadow: 0 0 10px 0; /* Inherits the font color */ 27 | height: 2px; 28 | opacity: 0; 29 | } 30 | -------------------------------------------------------------------------------- /tests/slim-loading-bar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, ComponentFixture } 2 | from '@angular/core/testing'; 3 | 4 | import {SlimLoadingBarService} 5 | from '../src/slim-loading-bar.service'; 6 | import {SlimLoadingBarComponent} 7 | from '../src/slim-loading-bar.component'; 8 | 9 | describe('SlimLoadingBar', () => { 10 | let componentFixture:ComponentFixture; 11 | let component:SlimLoadingBarComponent; 12 | let containerDiv:HTMLDivElement; 13 | let progressDiv:HTMLDivElement; 14 | 15 | let providers = [SlimLoadingBarService]; 16 | 17 | beforeEach(() => { 18 | TestBed.configureTestingModule({ 19 | declarations: [SlimLoadingBarComponent], 20 | providers: [providers] 21 | }); 22 | TestBed.compileComponents(); 23 | }); 24 | 25 | beforeEach(() => { 26 | componentFixture = TestBed.createComponent(SlimLoadingBarComponent); 27 | let element = componentFixture.elementRef.nativeElement; 28 | containerDiv = element.querySelector('.slim-loading-bar'); 29 | progressDiv = element.querySelector('.slim-loading-bar-progress'); 30 | component = componentFixture.componentInstance; 31 | componentFixture.detectChanges(); 32 | }); 33 | 34 | it('should be defined', () => { 35 | expect(containerDiv).toBeDefined(); 36 | expect(progressDiv).toBeDefined(); 37 | expect(component).toBeDefined(); 38 | }); 39 | 40 | it ('should change width of progress when calling set progress', () => { 41 | expect(progressDiv.style.width).toBe('0%'); 42 | 43 | // Set progress 44 | component.progress = '30'; 45 | componentFixture.detectChanges(); 46 | expect(progressDiv.style.width).toBe('30%'); 47 | }); 48 | 49 | it ('should change colors of progress when calling set color', () => { 50 | expect(progressDiv.style.color).toBe('firebrick'); 51 | 52 | // Set color 53 | component.color = 'green'; 54 | componentFixture.detectChanges(); 55 | expect(progressDiv.style.color).toBe('green'); 56 | expect(progressDiv.style.backgroundColor).toBe('green'); 57 | }); 58 | 59 | it ('should change height of progress when calling set height', () => { 60 | expect(progressDiv.style.height).toBe('2px'); 61 | 62 | // Set height 63 | component.height = '3px'; 64 | componentFixture.detectChanges(); 65 | expect(progressDiv.style.height).toBe('3px'); 66 | }); 67 | 68 | it ('should change visibility of progress when calling show', () => { 69 | expect(progressDiv.style.opacity).toBe('1'); 70 | 71 | // Hide 72 | component.show = false; 73 | componentFixture.detectChanges(); 74 | expect(progressDiv.style.opacity).toBe('0'); 75 | 76 | // Show 77 | component.show = true; 78 | componentFixture.detectChanges(); 79 | expect(progressDiv.style.opacity).toBe('1'); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/slim-loading-bar.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { inject, fakeAsync, tick, TestBed } 2 | from '@angular/core/testing'; 3 | 4 | import {SlimLoadingBarService} 5 | from '../src/slim-loading-bar.service'; 6 | 7 | describe('SlimLoadingBarService', () => { 8 | 9 | let service: SlimLoadingBarService; 10 | let providers = [SlimLoadingBarService]; 11 | 12 | beforeEach(() => { 13 | TestBed.configureTestingModule({ 14 | providers: [providers] 15 | }); 16 | }); 17 | 18 | beforeEach(inject([SlimLoadingBarService], (slbs: SlimLoadingBarService) => { 19 | service = slbs; 20 | })); 21 | 22 | it('is defined', () => { 23 | expect(SlimLoadingBarService).toBeDefined(); 24 | expect(service instanceof SlimLoadingBarService).toBeTruthy(); 25 | }); 26 | 27 | it('starts at zero when just being injected', () => { 28 | expect(service.progress).toBe(0); 29 | }); 30 | 31 | it('can change the progress to 30 if you call set progress', () => { 32 | service.progress = 30; 33 | expect(service.progress).toBe(30); 34 | }); 35 | 36 | it('increaments over time after calling start()', fakeAsync((): void => { 37 | // var value, flag; 38 | expect(service.progress).toBe(0); 39 | service.start(); 40 | 41 | tick(500); 42 | expect(service.progress).toBe(1); 43 | service.stop(); 44 | })); 45 | 46 | it('have 100 returned from progress after complete()', () => { 47 | service.start(); 48 | service.complete(); 49 | expect(service.progress).toBe(100); 50 | }); 51 | 52 | it('resets to zero when calling reset() after start() or set()', () => { 53 | service.progress = 30; 54 | service.reset(); 55 | expect(service.progress).toBe(0); 56 | }); 57 | 58 | it('will return 100 after calling complete', () => { 59 | service.progress = 30; 60 | service.complete(); 61 | expect(service.progress).toBe(100); 62 | }); 63 | 64 | it('return current height when calling height() without parameters', () => { 65 | expect(service.height).toBe('2px'); 66 | }); 67 | 68 | it('set the height when calling height() with parameter', () => { 69 | service.height = '5px'; 70 | expect(service.height).toBe('5px'); 71 | }); 72 | 73 | it('return current color ', () => { 74 | expect(service.color).toBe('firebrick'); 75 | }); 76 | it('set the color', () => { 77 | service.color = 'green'; 78 | expect(service.color).toBe('green'); 79 | }); 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "module": "es2015", 5 | "target": "es5", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "declaration": true, 9 | "moduleResolution": "node", 10 | "types": [ 11 | "hammerjs", 12 | "jasmine", 13 | "node" 14 | ], 15 | "lib": ["es2015", "dom"] 16 | }, 17 | "files": [ 18 | "index.ts", 19 | "./src/slim-loading-bar.component.ts", 20 | "./src/slim-loading-bar.service.ts", 21 | "./src/slim-loading-bar.utils.ts", 22 | "tests/slim-loading-bar.component.spec.ts", 23 | "tests/slim-loading-bar.service.spec.ts" 24 | ], 25 | "exclude": [ 26 | "node_modules", 27 | "bundles" 28 | ], 29 | "angularCompilerOptions": { 30 | "strictMetadataEmit": true, 31 | "skipTemplateCodegen": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "curly": true, 8 | "forin": true, 9 | "indent": [ 10 | true, 11 | "spaces" 12 | ], 13 | "label-position": true, 14 | "member-access": false, 15 | "no-arg": true, 16 | "no-bitwise": true, 17 | "no-console": [ 18 | true, 19 | "debug", 20 | "info", 21 | "time", 22 | "timeEnd", 23 | "trace" 24 | ], 25 | "no-construct": true, 26 | "no-debugger": true, 27 | "no-duplicate-variable": true, 28 | "no-empty": false, 29 | "no-eval": true, 30 | "no-inferrable-types": false, 31 | "no-shadowed-variable": true, 32 | "no-string-literal": false, 33 | "no-unused-expression": true, 34 | "no-unused-variable": true, 35 | "no-use-before-declare": true, 36 | "object-literal-sort-keys": false, 37 | "one-line": [ 38 | true, 39 | "check-open-brace", 40 | "check-catch", 41 | "check-else", 42 | "check-whitespace" 43 | ], 44 | "radix": true, 45 | "semicolon": [ 46 | "always" 47 | ], 48 | "triple-equals": [ 49 | true, 50 | "allow-null-check" 51 | ], 52 | "typedef-whitespace": [ 53 | true, 54 | { 55 | "call-signature": "nospace", 56 | "index-signature": "nospace", 57 | "parameter": "nospace", 58 | "property-declaration": "nospace", 59 | "variable-declaration": "nospace" 60 | } 61 | ], 62 | "variable-name": false, 63 | "use-input-property-decorator": true, 64 | "use-output-property-decorator": true, 65 | "use-host-property-decorator": false, 66 | "use-life-cycle-interface": true, 67 | "use-pipe-transform-interface": true 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160725163759", 4 | "jasmine": "registry:dt/jasmine#2.2.0+20160621224255", 5 | "node": "registry:dt/node#6.0.0+20160909174046" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adapted from angular2-webpack-starter 3 | */ 4 | 5 | const helpers = require('./config/helpers'), 6 | webpack = require('webpack'); 7 | 8 | /** 9 | * Webpack Plugins 10 | */ 11 | const ProvidePlugin = require('webpack/lib/ProvidePlugin'); 12 | const DefinePlugin = require('webpack/lib/DefinePlugin'); 13 | const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'); 14 | 15 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 16 | 17 | const ANALYZE = process.env.ANALYZE || false; 18 | const OPTIONAL_PLUGINS = configureOptionalPlugins(); 19 | 20 | module.exports = { 21 | resolve: { 22 | extensions: ['.ts', '.js'] 23 | }, 24 | 25 | entry: helpers.root('index.ts'), 26 | 27 | output: { 28 | path: helpers.root('bundles'), 29 | publicPath: '/', 30 | filename: 'index.umd.js', 31 | libraryTarget: 'umd', 32 | library: 'ng2-slim-loading-bar' 33 | }, 34 | 35 | // require those dependencies but don't bundle them 36 | externals: [/^\@angular\//, /^rxjs\//], 37 | 38 | module: { 39 | rules: [{ 40 | enforce: 'pre', 41 | test: /\.ts$/, 42 | loader: 'tslint-loader', 43 | exclude: [helpers.root('node_modules')] 44 | }, { 45 | test: /\.ts$/, 46 | loader: 'awesome-typescript-loader?declaration=false', 47 | exclude: [/\.e2e\.ts$/] 48 | }] 49 | }, 50 | 51 | plugins: [ 52 | // fix the warning in ./~/@angular/core/src/linker/system_js_ng_module_factory_loader.js 53 | new webpack.ContextReplacementPlugin( 54 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, 55 | helpers.root('./src') 56 | ), 57 | 58 | new webpack.LoaderOptionsPlugin({ 59 | options: { 60 | tslintLoader: { 61 | emitErrors: false, 62 | failOnHint: false 63 | } 64 | } 65 | }), 66 | 67 | ...OPTIONAL_PLUGINS 68 | ] 69 | }; 70 | 71 | function configureOptionalPlugins() { 72 | let plugins = []; 73 | 74 | if (ANALYZE) { 75 | // Include the BundleAnalyzerPlugin for analisys 76 | plugins.push(new BundleAnalyzerPlugin()); 77 | } 78 | 79 | return plugins; 80 | } 81 | --------------------------------------------------------------------------------