├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── continuous-deployment-workflow.yml │ └── continuous-integration-workflow.yml ├── .gitignore ├── LICENSE ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── projects ├── cache │ ├── .eslintrc.json │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── cache.decorator.spec.ts │ │ │ ├── cache.decorator.ts │ │ │ ├── cache.instance.ts │ │ │ ├── cache.manager.ts │ │ │ ├── cache.model.ts │ │ │ ├── cache.module.spec.ts │ │ │ ├── cache.module.ts │ │ │ ├── impl │ │ │ │ ├── memory-cache.ts │ │ │ │ ├── no-op-cache.ts │ │ │ │ └── storage-cache.ts │ │ │ └── simple-cache.manager.ts │ │ ├── polyfills.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── cookie │ ├── .eslintrc.json │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── browser │ │ │ │ ├── browser-cookie.factory.ts │ │ │ │ └── index.ts │ │ │ ├── cookie.decorator.spec.ts │ │ │ ├── cookie.decorator.ts │ │ │ ├── cookie.model.ts │ │ │ ├── cookie.module.spec.ts │ │ │ ├── cookie.module.ts │ │ │ ├── cookie.service.spec.ts │ │ │ ├── cookie.service.ts │ │ │ ├── cookie.token.ts │ │ │ └── server │ │ │ │ ├── index.ts │ │ │ │ └── server-cookie.factory.ts │ │ ├── polyfills.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── device │ ├── .eslintrc.json │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── device.model.ts │ │ │ ├── device.module.spec.ts │ │ │ ├── device.module.ts │ │ │ ├── device.service.spec.ts │ │ │ ├── device.service.ts │ │ │ └── device.token.ts │ │ ├── polyfills.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── logger │ ├── .eslintrc.json │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── console-logger.service.spec.ts │ │ │ ├── console-logger.service.ts │ │ │ ├── level.model.ts │ │ │ ├── level.token.ts │ │ │ ├── logger.decorator.spec.ts │ │ │ ├── logger.decorator.ts │ │ │ ├── logger.module.spec.ts │ │ │ ├── logger.module.ts │ │ │ ├── logger.rxjs.ts │ │ │ └── logger.service.ts │ │ ├── polyfills.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── spring │ ├── .eslintrc.json │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── lib │ │ │ ├── spring-data.model.ts │ │ │ └── spring-data.spec.ts │ │ ├── polyfills.ts │ │ ├── public-api.ts │ │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json └── utils │ ├── .eslintrc.json │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── functions.ts │ │ ├── once.decorator.spec.ts │ │ ├── once.decorator.ts │ │ ├── queue.decorator.spec.ts │ │ ├── queue.decorator.ts │ │ ├── wait.decorator.spec.ts │ │ └── wait.decorator.ts │ ├── polyfills.ts │ ├── public-api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── publish.sh └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore miscellaneous folders 2 | .angular/ 3 | .github/ 4 | .idea/ 5 | coverage/ 6 | dist/ 7 | node_modules/ 8 | tmp/ 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json", 14 | "e2e/tsconfig.json" 15 | ], 16 | "createDefaultProgram": true 17 | }, 18 | "extends": [ 19 | "plugin:@angular-eslint/recommended", 20 | "plugin:@angular-eslint/template/process-inline-templates", 21 | "eslint:recommended", 22 | "plugin:@typescript-eslint/recommended", 23 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 24 | ], 25 | "rules": { 26 | "@angular-eslint/component-selector": [ 27 | "error", 28 | { 29 | "type": "element", 30 | "prefix": "app", 31 | "style": "kebab-case" 32 | } 33 | ], 34 | "@angular-eslint/directive-selector": [ 35 | "error", 36 | { 37 | "type": "attribute", 38 | "prefix": "app", 39 | "style": "camelCase" 40 | } 41 | ], 42 | "@typescript-eslint/no-unsafe-member-access": "off", 43 | "@typescript-eslint/no-unsafe-assignment": "off", 44 | "@typescript-eslint/no-unsafe-call": "off", 45 | "@typescript-eslint/no-unsafe-return": "off", 46 | "@typescript-eslint/no-empty-function": "off", 47 | "@typescript-eslint/no-unused-vars": "off", 48 | "@typescript-eslint/no-explicit-any": "off", 49 | "@typescript-eslint/restrict-template-expressions": "off", 50 | "@typescript-eslint/ban-types": "off" 51 | } 52 | }, 53 | { 54 | "files": [ 55 | "*.html" 56 | ], 57 | "extends": [ 58 | "plugin:@angular-eslint/template/recommended" 59 | ], 60 | "rules": {} 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## I'm submitting a... 2 | 3 |

 4 | [ ] Regression (a behavior that used to work and stopped working in a new release)
 5 | [ ] Bug report  
 6 | [ ] Feature request
 7 | [ ] Documentation issue or request
 8 | 
9 | 10 | ## Packages 11 | 12 |

13 | [ ] @ngx-toolkit/cookie
14 | [ ] @ngx-toolkit/device
15 | [ ] @ngx-toolkit/logger
16 | [ ] @ngx-toolkit/spring
17 | 
18 | 19 | ## Current behavior 20 | 21 | 22 | 23 | ## Expected behavior 24 | 25 | 26 | 27 | ## Minimal reproduction of the problem with instructions 28 | 32 | 33 | ## What is the motivation / use case for changing the behavior? 34 | 35 | 36 | 37 | ## Environment 38 | 39 |

40 | Angular version: X.Y.Z
41 | Ngx-Toolkit version: X.Y.Z
42 | 
43 | Browser:
44 | - [ ] Chrome (desktop) version XX
45 | - [ ] Chrome (Android) version XX
46 | - [ ] Chrome (iOS) version XX
47 | - [ ] Firefox version XX
48 | - [ ] Safari (desktop) version XX
49 | - [ ] Safari (iOS) version XX
50 | - [ ] IE version XX
51 | - [ ] Edge version XX
52 |  
53 | For Tooling issues:
54 | - Node version: XX  
55 | - Platform:  
56 | 
57 | Others:
58 | 
59 | 
60 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## PR Checklist 2 | Please check if your PR fulfills the following requirements: 3 | 4 | - [ ] Tests for the changes have been added (for bug fixes / features) 5 | - [ ] Docs have been added / updated (for bug fixes / features) 6 | 7 | 8 | ## PR Type 9 | What kind of change does this PR introduce? 10 | 11 | 12 | ``` 13 | [ ] Bugfix 14 | [ ] Feature 15 | [ ] Code style update (formatting, local variables) 16 | [ ] Refactoring (no functional changes, no api changes) 17 | [ ] Build related changes 18 | [ ] Documentation content changes 19 | [ ] Other... Please describe: 20 | ``` 21 | 22 | ## What is the current behavior? 23 | 24 | 25 | Issue Number: N/A 26 | 27 | 28 | ## What is the new behavior? 29 | 30 | 31 | ## Does this PR introduce a breaking change? 32 | ``` 33 | [ ] Yes 34 | [ ] No 35 | ``` 36 | 37 | 38 | 39 | 40 | ## Other information 41 | -------------------------------------------------------------------------------- /.github/workflows/continuous-deployment-workflow.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | publish: 7 | name: Publish to NPM 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: '16' 14 | registry-url: https://registry.npmjs.org 15 | - run: npm ci --ignore-scripts 16 | - run: npm run lint 17 | - run: npm run test 18 | - run: npm run build:libs 19 | - run: bash publish.sh -xe 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration-workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [ push, pull_request ] 3 | jobs: 4 | checks: 5 | name: Linters 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v1 9 | - uses: actions/setup-node@v1 10 | with: 11 | node-version: '16' 12 | - run: npm ci --ignore-scripts 13 | - run: npm run lint 14 | tests: 15 | name: Tests 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v1 19 | - name: Use Node.js 16.x 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: 16.x 23 | - run: | 24 | npm ci --ignore-scripts 25 | npm run test 26 | cat ./coverage/*/lcov.info > ./coverage/lcov.info 27 | - name: Coveralls 28 | uses: coverallsapp/github-action@master 29 | with: 30 | github-token: ${{ secrets.GITHUB_TOKEN }} 31 | build: 32 | name: Build 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v1 36 | - uses: actions/setup-node@v1 37 | with: 38 | node-version: '16' 39 | - run: npm ci --ignore-scripts 40 | - run: npm run build:libs 41 | -------------------------------------------------------------------------------- /.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 | /.angular/cache 36 | /.sass-cache 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | npm-debug.log 41 | yarn-error.log 42 | testem.log 43 | /typings 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dewizz 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 | [![npm version](https://img.shields.io/npm/v/@ngx-toolkit/cookie.svg)](https://www.npmjs.com/org/ngx-toolkit) 2 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 3 | [![Build Status](https://travis-ci.org/dewizz/ngx-toolkit.svg?branch=master)](https://travis-ci.org/dewizz/ngx-toolkit) 4 | [![Coverage](https://coveralls.io/repos/github/dewizz/ngx-toolkit/badge.svg?branch=master#5)](https://coveralls.io/github/dewizz/ngx-toolkit?branch=master) 5 | [![Join the chat at https://gitter.im/ngx-toolkit/Lobby](https://badges.gitter.im/ngx-toolkit/Lobby.svg)](https://gitter.im/ngx-toolkit/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | # @ngx-toolkit 8 | > Set of components for Angular. 9 | 10 | # Table of Contents 11 | * [Packages](#packages) 12 | * [License](#license) 13 | 14 | --- 15 | 16 | # Packages 17 | The packages from this repository are published as scoped packages under [@ngx-toolkit](https://www.npmjs.com/org/ngx-toolkit) 18 | 19 | - [@ngx-toolkit/cache ](https://github.com/dewizz/ngx-toolkit/blob/master/projects/cache/README.md) - Cache Service 20 | - [@ngx-toolkit/cookie](https://github.com/dewizz/ngx-toolkit/blob/master/projects/cookie/README.md) - Cookie Service 21 | - [@ngx-toolkit/device](https://github.com/dewizz/ngx-toolkit/blob/master/projects/device/README.md) - Device detection 22 | - [@ngx-toolkit/logger](https://github.com/dewizz/ngx-toolkit/blob/master/projects/logger/README.md) - Logger Service 23 | - [@ngx-toolkit/spring](https://github.com/dewizz/ngx-toolkit/blob/master/projects/spring/README.md) - Spring Utilities 24 | - [@ngx-toolkit/utils](https://github.com/dewizz/ngx-toolkit/blob/master/projects/utils/README.md) - Common Utilities 25 | 26 | ---- 27 | 28 | # License 29 | © 2018 Dewizz 30 | 31 | [MIT](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 32 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "cli": { 4 | "defaultCollection": "@angular-eslint/schematics" 5 | }, 6 | "version": 1, 7 | "newProjectRoot": "projects", 8 | "projects": { 9 | "cache": { 10 | "projectType": "library", 11 | "root": "projects/cache", 12 | "sourceRoot": "projects/cache/src", 13 | "prefix": "lib", 14 | "architect": { 15 | "build": { 16 | "builder": "@angular-devkit/build-angular:ng-packagr", 17 | "options": { 18 | "tsConfig": "projects/cache/tsconfig.lib.json", 19 | "project": "projects/cache/ng-package.json" 20 | }, 21 | "configurations": { 22 | "production": { 23 | "tsConfig": "projects/cache/tsconfig.lib.prod.json" 24 | } 25 | } 26 | }, 27 | "test": { 28 | "builder": "@angular-devkit/build-angular:karma", 29 | "options": { 30 | "main": "projects/cache/src/test.ts", 31 | "polyfills": "projects/cache/src/polyfills.ts", 32 | "tsConfig": "projects/cache/tsconfig.spec.json", 33 | "karmaConfig": "projects/cache/karma.conf.js" 34 | } 35 | }, 36 | "lint": { 37 | "builder": "@angular-eslint/builder:lint", 38 | "options": { 39 | "lintFilePatterns": [ 40 | "projects/cache/**/*.ts", 41 | "projects/cache/**/*.html" 42 | ] 43 | } 44 | } 45 | } 46 | }, 47 | "cookie": { 48 | "projectType": "library", 49 | "root": "projects/cookie", 50 | "sourceRoot": "projects/cookie/src", 51 | "prefix": "lib", 52 | "architect": { 53 | "build": { 54 | "builder": "@angular-devkit/build-angular:ng-packagr", 55 | "options": { 56 | "tsConfig": "projects/cookie/tsconfig.lib.json", 57 | "project": "projects/cookie/ng-package.json" 58 | }, 59 | "configurations": { 60 | "production": { 61 | "tsConfig": "projects/cookie/tsconfig.lib.prod.json" 62 | } 63 | } 64 | }, 65 | "test": { 66 | "builder": "@angular-devkit/build-angular:karma", 67 | "options": { 68 | "main": "projects/cookie/src/test.ts", 69 | "polyfills": "projects/cookie/src/polyfills.ts", 70 | "tsConfig": "projects/cookie/tsconfig.spec.json", 71 | "karmaConfig": "projects/cookie/karma.conf.js" 72 | } 73 | }, 74 | "lint": { 75 | "builder": "@angular-eslint/builder:lint", 76 | "options": { 77 | "lintFilePatterns": [ 78 | "projects/cookie/**/*.ts", 79 | "projects/cookie/**/*.html" 80 | ] 81 | } 82 | } 83 | } 84 | }, 85 | "device": { 86 | "projectType": "library", 87 | "root": "projects/device", 88 | "sourceRoot": "projects/device/src", 89 | "prefix": "lib", 90 | "architect": { 91 | "build": { 92 | "builder": "@angular-devkit/build-angular:ng-packagr", 93 | "options": { 94 | "tsConfig": "projects/device/tsconfig.lib.json", 95 | "project": "projects/device/ng-package.json" 96 | }, 97 | "configurations": { 98 | "production": { 99 | "tsConfig": "projects/device/tsconfig.lib.prod.json" 100 | } 101 | } 102 | }, 103 | "test": { 104 | "builder": "@angular-devkit/build-angular:karma", 105 | "options": { 106 | "main": "projects/device/src/test.ts", 107 | "polyfills": "projects/device/src/polyfills.ts", 108 | "tsConfig": "projects/device/tsconfig.spec.json", 109 | "karmaConfig": "projects/device/karma.conf.js" 110 | } 111 | }, 112 | "lint": { 113 | "builder": "@angular-eslint/builder:lint", 114 | "options": { 115 | "lintFilePatterns": [ 116 | "projects/device/**/*.ts", 117 | "projects/device/**/*.html" 118 | ] 119 | } 120 | } 121 | } 122 | }, 123 | "logger": { 124 | "projectType": "library", 125 | "root": "projects/logger", 126 | "sourceRoot": "projects/logger/src", 127 | "prefix": "lib", 128 | "architect": { 129 | "build": { 130 | "builder": "@angular-devkit/build-angular:ng-packagr", 131 | "options": { 132 | "tsConfig": "projects/logger/tsconfig.lib.json", 133 | "project": "projects/logger/ng-package.json" 134 | }, 135 | "configurations": { 136 | "production": { 137 | "tsConfig": "projects/logger/tsconfig.lib.prod.json" 138 | } 139 | } 140 | }, 141 | "test": { 142 | "builder": "@angular-devkit/build-angular:karma", 143 | "options": { 144 | "main": "projects/logger/src/test.ts", 145 | "polyfills": "projects/logger/src/polyfills.ts", 146 | "tsConfig": "projects/logger/tsconfig.spec.json", 147 | "karmaConfig": "projects/logger/karma.conf.js" 148 | } 149 | }, 150 | "lint": { 151 | "builder": "@angular-eslint/builder:lint", 152 | "options": { 153 | "lintFilePatterns": [ 154 | "projects/logger/**/*.ts", 155 | "projects/logger/**/*.html" 156 | ] 157 | } 158 | } 159 | } 160 | }, 161 | "spring": { 162 | "projectType": "library", 163 | "root": "projects/spring", 164 | "sourceRoot": "projects/spring/src", 165 | "prefix": "lib", 166 | "architect": { 167 | "build": { 168 | "builder": "@angular-devkit/build-angular:ng-packagr", 169 | "options": { 170 | "tsConfig": "projects/spring/tsconfig.lib.json", 171 | "project": "projects/spring/ng-package.json" 172 | }, 173 | "configurations": { 174 | "production": { 175 | "tsConfig": "projects/spring/tsconfig.lib.prod.json" 176 | } 177 | } 178 | }, 179 | "test": { 180 | "builder": "@angular-devkit/build-angular:karma", 181 | "options": { 182 | "main": "projects/spring/src/test.ts", 183 | "polyfills": "projects/spring/src/polyfills.ts", 184 | "tsConfig": "projects/spring/tsconfig.spec.json", 185 | "karmaConfig": "projects/spring/karma.conf.js" 186 | } 187 | }, 188 | "lint": { 189 | "builder": "@angular-eslint/builder:lint", 190 | "options": { 191 | "lintFilePatterns": [ 192 | "projects/spring/**/*.ts", 193 | "projects/spring/**/*.html" 194 | ] 195 | } 196 | } 197 | } 198 | }, 199 | "utils": { 200 | "projectType": "library", 201 | "root": "projects/utils", 202 | "sourceRoot": "projects/utils/src", 203 | "prefix": "lib", 204 | "architect": { 205 | "build": { 206 | "builder": "@angular-devkit/build-angular:ng-packagr", 207 | "options": { 208 | "tsConfig": "projects/utils/tsconfig.lib.json", 209 | "project": "projects/utils/ng-package.json" 210 | }, 211 | "configurations": { 212 | "production": { 213 | "tsConfig": "projects/utils/tsconfig.lib.prod.json" 214 | } 215 | } 216 | }, 217 | "test": { 218 | "builder": "@angular-devkit/build-angular:karma", 219 | "options": { 220 | "main": "projects/utils/src/test.ts", 221 | "polyfills": "projects/utils/src/polyfills.ts", 222 | "tsConfig": "projects/utils/tsconfig.spec.json", 223 | "karmaConfig": "projects/utils/karma.conf.js" 224 | } 225 | }, 226 | "lint": { 227 | "builder": "@angular-eslint/builder:lint", 228 | "options": { 229 | "lintFilePatterns": [ 230 | "projects/utils/**/*.ts", 231 | "projects/utils/**/*.html" 232 | ] 233 | } 234 | } 235 | } 236 | } 237 | }, 238 | "defaultProject": "cache" 239 | } 240 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-toolkit", 3 | "version": "13.2.1", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build", 9 | "test": "npm run test:lib:device && npm run test:lib:cache && npm run test:lib:cookie && npm run test:lib:logger && npm run test:lib:spring && npm run test:lib:utils", 10 | "test:lib:device": "ng test device --watch false --code-coverage --browsers=ChromeHeadlessCI", 11 | "test:lib:cache": "ng test cache --watch false --code-coverage --browsers=ChromeHeadlessCI", 12 | "test:lib:cookie": "ng test cookie --watch false --code-coverage --browsers=ChromeHeadlessCI", 13 | "test:lib:logger": "ng test logger --watch false --code-coverage --browsers=ChromeHeadlessCI", 14 | "test:lib:spring": "ng test spring --watch false --code-coverage --browsers=ChromeHeadlessCI", 15 | "test:lib:utils": "ng test utils --watch false --code-coverage --browsers=ChromeHeadlessCI", 16 | "lint": "ng lint", 17 | "e2e": "ng e2e", 18 | "build:libs": "npm run build:lib:device && npm run build:lib:cache && npm run build:lib:cookie && npm run build:lib:logger && npm run build:lib:spring && npm run build:lib:utils", 19 | "build:lib:device": "ng build device --configuration production", 20 | "build:lib:cache": "ng build cache --configuration production", 21 | "build:lib:cookie": "ng build cookie --configuration production", 22 | "build:lib:logger": "ng build logger --configuration production", 23 | "build:lib:spring": "ng build spring --configuration production", 24 | "build:lib:utils": "ng build utils --configuration production" 25 | }, 26 | "private": true, 27 | "dependencies": { 28 | "@angular/animations": "~13.2.0", 29 | "@angular/common": "~13.2.0", 30 | "@angular/compiler": "~13.2.0", 31 | "@angular/core": "~13.2.0", 32 | "@angular/forms": "~13.2.0", 33 | "@angular/platform-browser": "~13.2.0", 34 | "@angular/platform-browser-dynamic": "~13.2.0", 35 | "@angular/router": "~13.2.0", 36 | "rxjs": "~6.5.4", 37 | "tslib": "^2.3.1", 38 | "zone.js": "~0.11.4" 39 | }, 40 | "devDependencies": { 41 | "@angular-devkit/build-angular": "~13.2.0", 42 | "@angular-eslint/builder": "~13.0.1", 43 | "@angular-eslint/eslint-plugin": "~13.0.1", 44 | "@angular-eslint/eslint-plugin-template": "~13.0.1", 45 | "@angular-eslint/schematics": "~13.0.1", 46 | "@angular-eslint/template-parser": "~13.0.1", 47 | "@angular/cli": "~13.2.0", 48 | "@angular/compiler-cli": "~13.2.0", 49 | "@angular/language-service": "~13.2.0", 50 | "@types/jasmine": "~3.10.0", 51 | "@types/node": "^12.11.1", 52 | "@typescript-eslint/eslint-plugin": "~5.10.1", 53 | "@typescript-eslint/parser": "~5.10.1", 54 | "codelyzer": "^6.0.2", 55 | "eslint": "^8.2.0", 56 | "jasmine-core": "~4.0.0", 57 | "karma": "~6.3.0", 58 | "karma-chrome-launcher": "~3.1.0", 59 | "karma-coverage": "~2.1.0", 60 | "karma-jasmine": "~4.0.0", 61 | "karma-jasmine-html-reporter": "~1.7.0", 62 | "ng-packagr": "^13.2.0", 63 | "protractor": "~7.0.0", 64 | "ts-node": "~10.4.0", 65 | "tslint": "~6.1.3", 66 | "typescript": "~4.5.2" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /projects/cache/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*", 5 | "**/*.spec.ts" 6 | ], 7 | "overrides": [ 8 | { 9 | "files": [ 10 | "*.ts" 11 | ], 12 | "parserOptions": { 13 | "project": [ 14 | "projects/cache/tsconfig.lib.json", 15 | "projects/cache/tsconfig.spec.json" 16 | ], 17 | "createDefaultProgram": true 18 | }, 19 | "rules": { 20 | "@angular-eslint/directive-selector": [ 21 | "error", 22 | { 23 | "type": "attribute", 24 | "prefix": "lib", 25 | "style": "camelCase" 26 | } 27 | ], 28 | "@angular-eslint/component-selector": [ 29 | "error", 30 | { 31 | "type": "element", 32 | "prefix": "lib", 33 | "style": "kebab-case" 34 | } 35 | ], 36 | "@typescript-eslint/no-unsafe-member-access": "off", 37 | "@typescript-eslint/no-unsafe-assignment": "off", 38 | "@typescript-eslint/no-unsafe-call": "off", 39 | "@typescript-eslint/no-unsafe-return": "off", 40 | "@typescript-eslint/no-unsafe-argument": "off", 41 | "@typescript-eslint/no-empty-function": "off", 42 | "@typescript-eslint/no-unused-vars": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/restrict-template-expressions": "off", 45 | "@typescript-eslint/ban-types": "off" 46 | } 47 | }, 48 | { 49 | "files": [ 50 | "*.html" 51 | ], 52 | "rules": {} 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /projects/cache/README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/@ngx-toolkit/cache.svg)](https://www.npmjs.com/package/@ngx-toolkit/cache) 2 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 3 | [![Build Status](https://travis-ci.org/dewizz/ngx-toolkit.svg?branch=master)](https://travis-ci.org/dewizz/ngx-toolkit) 4 | [![Coverage](https://coveralls.io/repos/github/dewizz/ngx-toolkit/badge.svg?branch=master#5)](https://coveralls.io/github/dewizz/ngx-toolkit?branch=master) 5 | [![Join the chat at https://gitter.im/ngx-toolkit/Lobby](https://badges.gitter.im/ngx-toolkit/Lobby.svg)](https://gitter.im/ngx-toolkit/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | # @ngx-toolkit/cache 8 | 9 | > Angular Cache implementation for Browser & Server platforms. 10 | 11 | # Table of contents: 12 | * [Installation](#installation) 13 | * [Annotations](#annotations) 14 | * [Cache](#cache) 15 | * [MemoryCache](#memorycache) 16 | * [StorageCache](#storagecache) 17 | * [NoOpCache](#noopcache) 18 | * [Custom Implementation](#custom-implementation) 19 | * [License](#license) 20 | 21 | --- 22 | 23 | # Installation 24 | 25 | Install the npm package. 26 | 27 | ```bash 28 | # To get the latest stable version and update package.json file: 29 | npm install @ngx-toolkit/cache --save 30 | # or 31 | yarn add @ngx-toolkit/cache 32 | ``` 33 | 34 | Registered `CacheModule` in the root Module of your application with `forRoot(caches: Cache[])` static method. 35 | 36 | ```typescript 37 | import { NgModule } from '@angular/core'; 38 | import { BrowserModule } from '@angular/platform-browser'; 39 | import { CacheModule, MemoryCache } from '@ngx-toolkit/cache'; 40 | 41 | import { AppComponent } from './app.component'; 42 | 43 | @NgModule({ 44 | imports: [ BrowserModule, CacheModule.forRoot([ 45 | new MemoryCache('myMemoryCache') 46 | ]) ], 47 | declarations: [ AppComponent ], 48 | bootstrap: [ AppComponent ] 49 | }) 50 | export class AppModule { } 51 | ``` 52 | ---- 53 | 54 | # Annotations 55 | 56 | ## CacheDefaults 57 | ````typescript 58 | /** 59 | * Allows the configuration of defaults for `CacheResult`, `CachePut`, `CacheRemove`, and `CacheRemoveAll` at the class level. 60 | * Without the method level annotations this annotation has no effect. 61 | * @param cacheName 62 | */ 63 | @CacheDefaults(cacheName: string) 64 | ```` 65 | 66 | ## CacheResult 67 | ````typescript 68 | /** 69 | * When a method annotated with `CacheResult` is invoked a cache key will be generated 70 | * and *Cache.get(key)* is called before the annotated method actually executes. 71 | * If a value is found in the cache it is returned and the annotated method is never actually executed. 72 | * If no value is found the annotated method is invoked and the returned value is stored in the cache with the generated key. 73 | * 74 | * @param params (Optional) {cacheName?: string} 75 | */ 76 | @CacheResult(params?: { cacheName?: string }) 77 | ```` 78 | 79 | ## CachePut 80 | ````typescript 81 | /** 82 | * When a method annotated with `CachePut` is invoked a cache key will be generated 83 | * and *Cache.put(key, value)* will be invoked on the specified cache storing the value marked with `CacheValue`. 84 | * 85 | * @param params (Optional) {cacheName?: string, afterInvocation: boolean = true} 86 | */ 87 | @CachePut(params?: {cacheName?: string, afterInvocation: boolean = true}) 88 | ```` 89 | 90 | ## CacheKey 91 | ````typescript 92 | /** 93 | * Marks a method argument as part of the cache key. 94 | * If no arguments are marked all arguments are used. 95 | * The exception is for a method annotated with `CachePut` where the `CacheValue` parameter is never included in the key. 96 | */ 97 | @CacheKey() 98 | ```` 99 | 100 | ## CacheValue 101 | ````typescript 102 | /** 103 | * Marks the parameter to be cached for a method annotated with `CachePut`. 104 | */ 105 | @CacheValue() 106 | ```` 107 | 108 | ## CacheRemove 109 | ````typescript 110 | /** 111 | * When a method annotated with `CacheRemove` is invoked a cache key will be generated 112 | * and *Cache.remove(key)* will be invoked on the specified cache. 113 | * The default behavior is to call *Cache.evict(key)* after the annotated method is invoked, 114 | * this behavior can be changed by setting *`afterInvocation`* to false in which case *Cache.evict(key)* 115 | * will be called before the annotated method is invoked. 116 | * 117 | * @param params (Optional) {cacheName?: string, afterInvocation: boolean = true} 118 | */ 119 | @CacheRemove(params?: {cacheName?: string, afterInvocation: boolean = true}) 120 | ```` 121 | 122 | ## CacheRemoveAll 123 | ````typescript 124 | /** 125 | * When a method annotated with `CacheRemoveAll` is invoked all elements in the specified cache will be removed via the *Cache.clear()* method. 126 | * The default behavior is to call *Cache.clear()* after the annotated method is invoked, 127 | * this behavior can be changed by setting *`afterInvocation`* to false in which case *Cache.clear()* will be called before the annotated method is invoked. 128 | * 129 | * @param params (Optional) {cacheName?: string, afterInvocation: boolean = true} 130 | */ 131 | @CacheRemoveAll(params?: {cacheName?: string, afterInvocation: boolean = true}) 132 | ```` 133 | 134 | Example: 135 | ````typescript 136 | @CacheDefaults('myCacheBean') 137 | class CacheBean { 138 | 139 | @CachePut() 140 | myMethod(@CacheKey() id: number, @CacheValue() value: any): void { 141 | ... 142 | } 143 | 144 | @CacheResult() 145 | get(id: number): any { 146 | ... 147 | } 148 | 149 | @CacheRemove() 150 | refresh(id: number): void { 151 | ... 152 | } 153 | 154 | @CacheRemoveAll() 155 | refreshAll(): void { 156 | ... 157 | } 158 | } 159 | ```` 160 | 161 | # Cache 162 | 163 | ## MemoryCache 164 | 165 | Cache in memory 166 | 167 | ```typescript 168 | import { MemoryCache } from '@ngx-toolkit/cache'; 169 | new MemoryCache('myCacheName'); 170 | ``` 171 | 172 | ## StorageCache 173 | 174 | Cache in a storage 175 | 176 | ```typescript 177 | import { StorageCache } from '@ngx-toolkit/cache'; 178 | new StorageCache('myCacheName', window.sessionStorage || window.localStorage); 179 | ``` 180 | 181 | ## NoOpCache 182 | 183 | *No cache, do nothing...* 184 | 185 | ```typescript 186 | import { NoOpCache } from '@ngx-toolkit/cache'; 187 | new NoOpCache('myCacheName'); 188 | ``` 189 | 190 | 191 | # Custom implementation 192 | 193 | You can create your own implementation. You simply need to implement the `Cache` interface: 194 | 195 | ```typescript 196 | export interface Cache { 197 | 198 | /** 199 | * Return the cache name. 200 | */ 201 | readonly name: string; 202 | 203 | /** 204 | * Return the value to which this cache maps the specified key 205 | */ 206 | get(key: string): T; 207 | 208 | /** 209 | * Associate the specified value with the specified key in this cache. 210 | * If the cache previously contained a mapping for this key, the old 211 | * value is replaced by the specified value. 212 | */ 213 | put(key: string, value: T): void; 214 | 215 | /** 216 | * Evict the mapping for this key from this cache if it is present. 217 | */ 218 | evict(key: string): void; 219 | 220 | /** 221 | * Remove all mappings from the cache. 222 | */ 223 | clear(): void; 224 | } 225 | 226 | ``` 227 | 228 | # License 229 | © 2018 Dewizz 230 | 231 | [MIT](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 232 | -------------------------------------------------------------------------------- /projects/cache/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'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/cache'), 29 | subdir: '.', 30 | reporters: [ 31 | {type: 'html'}, 32 | {type: 'lcovonly'}, 33 | {type: 'text-summary'} 34 | ] 35 | }, 36 | reporters: ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessCI'], 42 | customLaunchers: { 43 | ChromeHeadlessCI: { 44 | base: 'ChromeHeadless', 45 | flags: ['--no-sandbox'] 46 | } 47 | }, 48 | singleRun: false, 49 | restartOnFileChange: true 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /projects/cache/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/cache", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/cache/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-toolkit/cache", 3 | "version": "0.0.0-PLACEHOLDER", 4 | "description": "Angular cache with Universal support", 5 | "homepage": "https://github.com/dewizz/ngx-toolkit", 6 | "license": "MIT", 7 | "author": "Dewizz", 8 | "keywords": [ 9 | "angular", 10 | "ngx", 11 | "ngx-toolkit", 12 | "universal", 13 | "annotation", 14 | "decorator", 15 | "cache", 16 | "cache-service" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/dewizz/ngx-toolkit" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/dewizz/ngx-toolkit/issues" 24 | }, 25 | "dependencies": { 26 | "tslib": "^2.0.0" 27 | }, 28 | "peerDependencies": { 29 | "@angular/common": "^13.2.0", 30 | "@angular/core": "^13.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /projects/cache/src/lib/cache.decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import {CacheDefaults, CacheKey, CachePut, CacheRemove, CacheRemoveAll, CacheResult, CacheValue} from './cache.decorator'; 2 | import {initCacheManager} from './cache.instance'; 3 | import {MemoryCache} from './impl/memory-cache'; 4 | import {StorageCache} from './impl/storage-cache'; 5 | import {SimpleCacheManager} from './simple-cache.manager'; 6 | 7 | @CacheDefaults('cacheBean') 8 | class CacheBean { 9 | 10 | @CachePut() 11 | put(@CacheKey() id: number, @CacheValue() value: any): void { 12 | } 13 | 14 | @CacheResult() 15 | get(id: number): any { 16 | return null; 17 | } 18 | 19 | @CacheRemove() 20 | clear(id: number): void { 21 | } 22 | 23 | @CacheRemoveAll() 24 | clearAll(): void { 25 | } 26 | } 27 | 28 | describe('CacheDecorator', () => { 29 | 30 | beforeEach(() => { 31 | 32 | }); 33 | 34 | it('should throw an error if no cacheManager declared', () => { 35 | const bean: CacheBean = new CacheBean(); 36 | 37 | initCacheManager(null); 38 | expect(function() { 39 | bean.put(1, 2); 40 | }).toThrowError(); 41 | }); 42 | 43 | it('should throw an error if no cache declared', () => { 44 | const bean: CacheBean = new CacheBean(); 45 | 46 | initCacheManager(new SimpleCacheManager()); 47 | expect(function() { 48 | bean.put(1, 2); 49 | }).toThrowError(); 50 | }); 51 | 52 | it('should not has an injector', () => { 53 | const bean: CacheBean = new CacheBean(); 54 | 55 | [new MemoryCache('cacheBean'), new StorageCache('cacheBean', localStorage)].forEach(cache => { 56 | const cacheManager: SimpleCacheManager = new SimpleCacheManager(); 57 | cacheManager.addCache(cache); 58 | initCacheManager(cacheManager); 59 | 60 | 61 | bean.put(1, 'put'); 62 | bean.put(2, 'put2'); 63 | 64 | expect(bean.get(2)).toBe('put2'); 65 | 66 | expect(bean.get(3)).toBeNull(); 67 | 68 | bean.clear(2); 69 | expect(bean.get(2)).toBeNull(); 70 | 71 | expect(bean.get(1)).toBe('put'); 72 | 73 | bean.clearAll(); 74 | expect(bean.get(1)).toBeNull(); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /projects/cache/src/lib/cache.decorator.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata/Reflect'; 2 | import {getCacheManager} from './cache.instance'; 3 | import {Cache} from './cache.model'; 4 | 5 | export const METADATA_KEY_CACHE_DEFAULTS = '_cache_defaults'; 6 | export const METADATA_KEY_CACHE_KEYS = '_cache_keys'; 7 | export const METADATA_KEY_CACHE_VALUE = '_cache_value'; 8 | 9 | export interface CacheParams { 10 | cacheName?: string; 11 | } 12 | 13 | export interface CacheParamsInvoc extends CacheParams { 14 | afterInvocation?: boolean; 15 | } 16 | 17 | /** 18 | * Allows the configuration of defaults for `CacheResult`, `CachePut`, `CacheRemove`, and `CacheRemoveAll` at the class level. 19 | * Without the method level annotations this annotation has no effect. 20 | * 21 | * @param cacheName 22 | */ 23 | export function CacheDefaults(cacheName: string): ClassDecorator { 24 | return (target: Function): void => { 25 | Reflect.defineMetadata(METADATA_KEY_CACHE_DEFAULTS, cacheName, target); 26 | }; 27 | } 28 | 29 | /** 30 | * When a method annotated with `CacheResult` is invoked a cache key will be generated 31 | * and *Cache.get(key)* is called before the annotated method actually executes. 32 | * If a value is found in the cache it is returned and the annotated method is never actually executed. 33 | * If no value is found the annotated method is invoked and the returned value is stored in the cache with the generated key. 34 | * 35 | * @param params (Optional) {cacheName?: string} 36 | */ 37 | export function CacheResult(params?: CacheParams): MethodDecorator { 38 | params = getDefaultParams(params); 39 | 40 | return (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 41 | const originalMethod = descriptor.value; 42 | descriptor.value = function(...args: any[]) { 43 | const cache: Cache = getCache(target, params); 44 | const cacheKey: string = getCacheKey(target, propertyKey, args); 45 | 46 | // Find cache value 47 | let result: any = cache.get(cacheKey); 48 | 49 | // Call function & save function if no cache value 50 | if (result === undefined) { 51 | // Call function & save result 52 | result = originalMethod.apply(this, args); 53 | cache.put(cacheKey, result); 54 | } 55 | 56 | return result; 57 | }; 58 | return descriptor; 59 | }; 60 | } 61 | 62 | /** 63 | * Marks a method argument as part of the cache key. 64 | * If no arguments are marked all arguments are used. 65 | * The exception is for a method annotated with `CachePut` where the `CacheValue` parameter is never included in the key. 66 | */ 67 | export function CacheKey(): ParameterDecorator { 68 | return (target: Object, propertyKey: string | symbol, parameterIndex: number) => { 69 | const indices = Reflect.getMetadata(`${METADATA_KEY_CACHE_KEYS}_${propertyKey.toString()}`, target, propertyKey) || []; 70 | indices.push(parameterIndex); 71 | Reflect.defineMetadata(`${METADATA_KEY_CACHE_KEYS}_${propertyKey.toString()}`, indices, target, propertyKey); 72 | }; 73 | } 74 | 75 | /** 76 | * When a method annotated with `CachePut` is invoked a cache key will be generated 77 | * and *Cache.put(key, value)* will be invoked on the specified cache storing the value marked with `CacheValue`. 78 | * 79 | * @param params (Optional) {cacheName?: string, afterInvocation: boolean = true} 80 | */ 81 | export function CachePut(params?: CacheParamsInvoc): MethodDecorator { 82 | params = getDefaultParams(params); 83 | 84 | return (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 85 | const originalMethod = descriptor.value; 86 | descriptor.value = function(...args: any[]) { 87 | const cache: Cache = getCache(target, params); 88 | const indexValue: number = Reflect.getMetadata(`${METADATA_KEY_CACHE_VALUE}_${propertyKey.toString()}`, target, propertyKey); 89 | const cacheKey: string = getCacheKey(target, propertyKey, args, indexValue); 90 | 91 | if (!params.afterInvocation && indexValue && indexValue >= 0 && indexValue < args.length) { 92 | cache.put(cacheKey, args[indexValue]); 93 | } 94 | 95 | const result = originalMethod.apply(this, args); 96 | 97 | if (params.afterInvocation && indexValue && indexValue >= 0 && indexValue < args.length) { 98 | cache.put(cacheKey, args[indexValue]); 99 | } 100 | 101 | return result; 102 | }; 103 | return descriptor; 104 | }; 105 | } 106 | 107 | /** 108 | * Marks the parameter to be cached for a method annotated with `CachePut`. 109 | */ 110 | export function CacheValue(): ParameterDecorator { 111 | return (target: Object, propertyKey: string | symbol, parameterIndex: number) => { 112 | Reflect.defineMetadata(`${METADATA_KEY_CACHE_VALUE}_${propertyKey.toString()}`, parameterIndex, target, propertyKey); 113 | }; 114 | } 115 | 116 | /** 117 | * When a method annotated with `CacheRemove` is invoked a cache key will be generated 118 | * and *Cache.remove(key)* will be invoked on the specified cache. 119 | * The default behavior is to call *Cache.evict(key)* after the annotated method is invoked, 120 | * this behavior can be changed by setting *`afterInvocation`* to false in which case *Cache.evict(key)* 121 | * will be called before the annotated method is invoked. 122 | * 123 | * @param params (Optional) {cacheName?: string, afterInvocation: boolean = true} 124 | */ 125 | export function CacheRemove(params?: CacheParamsInvoc): MethodDecorator { 126 | params = getDefaultParams(params); 127 | 128 | return (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 129 | const originalMethod = descriptor.value; 130 | descriptor.value = function(...args: any[]) { 131 | const cache: Cache = getCache(target, params); 132 | const cacheKey: string = getCacheKey(target, propertyKey, args); 133 | 134 | if (!params.afterInvocation) { 135 | cache.evict(cacheKey); 136 | } 137 | 138 | const result: any = originalMethod.apply(this, args); 139 | 140 | 141 | if (params.afterInvocation) { 142 | cache.evict(cacheKey); 143 | } 144 | 145 | return result; 146 | }; 147 | return descriptor; 148 | }; 149 | } 150 | 151 | /** 152 | * When a method annotated with `CacheRemoveAll` is invoked all elements in the specified cache will be removed via the *Cache.clear()* method. 153 | * The default behavior is to call *Cache.clear()* after the annotated method is invoked, 154 | * this behavior can be changed by setting *`afterInvocation`* to false in which case *Cache.clear()* will be called before the annotated method is invoked. 155 | * 156 | * @param params (Optional) {cacheName?: string, afterInvocation: boolean = true} 157 | */ 158 | export function CacheRemoveAll(params?: CacheParamsInvoc): MethodDecorator { 159 | params = getDefaultParams(params); 160 | 161 | return (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 162 | const originalMethod = descriptor.value; 163 | descriptor.value = function(...args: any[]) { 164 | const cache: Cache = getCache(target, params); 165 | if (!params.afterInvocation) { 166 | cache.clear(); 167 | } 168 | 169 | const result: any = originalMethod.apply(this, args); 170 | 171 | if (params.afterInvocation) { 172 | cache.clear(); 173 | } 174 | 175 | return result; 176 | }; 177 | return descriptor; 178 | }; 179 | } 180 | 181 | function getDefaultParams(cacheParams: CacheParams): { afterInvocation: boolean } & CacheParams { 182 | return Object.assign({ 183 | afterInvocation: true 184 | }, cacheParams || {}); 185 | } 186 | 187 | function getCache(target: Object, params: CacheParams): Cache { 188 | if (!params.cacheName) { 189 | params.cacheName = Reflect.getMetadata(METADATA_KEY_CACHE_DEFAULTS, target.constructor) || ''; 190 | } 191 | 192 | const cache: Cache = getCacheManager().getCache(params.cacheName); 193 | if (!cache) { 194 | throw new Error(`Cache '${params.cacheName}' not found for ${target.constructor.name}`); 195 | } 196 | return cache; 197 | } 198 | 199 | function getCacheKey(target: Object, propertyKey: string | symbol, args: any[], cacheValueIndex = -1): string { 200 | if (!args) { 201 | args = []; 202 | } 203 | 204 | const indices: number[] = Reflect.getMetadata(`${METADATA_KEY_CACHE_KEYS}_${propertyKey.toString()}`, target, propertyKey); 205 | if (indices) { 206 | args = args.filter((value: any, index: number) => indices.indexOf(index) !== -1 && cacheValueIndex !== index); 207 | } else if (cacheValueIndex !== -1) { 208 | args = args.filter((value: any, index: number) => cacheValueIndex !== index); 209 | } 210 | 211 | if (args.length === 0) { 212 | throw new Error(`Couldn't generate key without params for '${propertyKey.toString()}' method of ${target.constructor.name}`); 213 | } 214 | 215 | return args.map(a => (JSON.stringify(a) || a.toString())).join('|'); 216 | } 217 | -------------------------------------------------------------------------------- /projects/cache/src/lib/cache.instance.ts: -------------------------------------------------------------------------------- 1 | import {CacheManager} from './cache.manager'; 2 | 3 | const CACHE_INSTANCE = { 4 | manager: undefined 5 | }; 6 | 7 | 8 | export function initCacheManager(cacheManager: CacheManager) { 9 | CACHE_INSTANCE.manager = cacheManager; 10 | } 11 | 12 | export function getCacheManager(): CacheManager { 13 | if (!CACHE_INSTANCE.manager) { 14 | throw new Error('No cache found, `initCacheManager` before'); 15 | } 16 | return CACHE_INSTANCE.manager; 17 | } 18 | -------------------------------------------------------------------------------- /projects/cache/src/lib/cache.manager.ts: -------------------------------------------------------------------------------- 1 | import {Cache} from './cache.model'; 2 | 3 | export interface CacheManager { 4 | /** 5 | * Return the cache associated with the given name. 6 | */ 7 | getCache(name: string): Cache; 8 | 9 | /** 10 | * Return a collection of the cache names known by this manager. 11 | */ 12 | getCacheNames(): string[]; 13 | } 14 | -------------------------------------------------------------------------------- /projects/cache/src/lib/cache.model.ts: -------------------------------------------------------------------------------- 1 | export interface Cache { 2 | 3 | /** 4 | * Return the cache name. 5 | */ 6 | readonly name: string; 7 | 8 | /** 9 | * Return the value to which this cache maps the specified key 10 | */ 11 | get(key: string): T; 12 | 13 | /** 14 | * Associate the specified value with the specified key in this cache. 15 | * If the cache previously contained a mapping for this key, the old 16 | * value is replaced by the specified value. 17 | */ 18 | put(key: string, value: T): void; 19 | 20 | /** 21 | * Evict the mapping for this key from this cache if it is present. 22 | */ 23 | evict(key: string): void; 24 | 25 | /** 26 | * Remove all mappings from the cache. 27 | */ 28 | clear(): void; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /projects/cache/src/lib/cache.module.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | import {getCacheManager, initCacheManager} from './cache.instance'; 3 | import {CacheModule} from './cache.module'; 4 | import {MemoryCache} from './impl/memory-cache'; 5 | 6 | describe('CacheModule', () => { 7 | it('should work', () => { 8 | expect(new CacheModule()).toBeDefined(); 9 | }); 10 | 11 | it('should configure cacheManager', () => { 12 | initCacheManager(null); 13 | 14 | TestBed.configureTestingModule({ 15 | imports: [CacheModule.forRoot([ 16 | new MemoryCache('simpleCache') 17 | ])] 18 | }); 19 | 20 | expect(getCacheManager()).toBeDefined(); 21 | expect(getCacheManager().getCacheNames().length).toBe(1); 22 | expect(getCacheManager().getCacheNames()[0]).toBe('simpleCache'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /projects/cache/src/lib/cache.module.ts: -------------------------------------------------------------------------------- 1 | import {CommonModule} from '@angular/common'; 2 | import {ModuleWithProviders, NgModule} from '@angular/core'; 3 | import {initCacheManager} from './cache.instance'; 4 | import {Cache} from './cache.model'; 5 | import {SimpleCacheManager} from './simple-cache.manager'; 6 | 7 | @NgModule({ 8 | imports: [CommonModule] 9 | }) 10 | export class CacheModule { 11 | /** 12 | * In root module to provide caches 13 | */ 14 | static forRoot(caches: Cache[]): ModuleWithProviders { 15 | initCacheManager(new SimpleCacheManager(caches)); 16 | 17 | return { 18 | ngModule: CacheModule 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /projects/cache/src/lib/impl/memory-cache.ts: -------------------------------------------------------------------------------- 1 | import {Cache} from '../cache.model'; 2 | 3 | export class MemoryCache implements Cache { 4 | 5 | readonly name: string; 6 | private cache: Map = new Map(); 7 | 8 | constructor(name: string) { 9 | this.name = name; 10 | } 11 | 12 | clear(): void { 13 | this.cache.clear(); 14 | } 15 | 16 | evict(key: string): void { 17 | this.cache.delete(key); 18 | } 19 | 20 | get(key: string): T { 21 | return this.cache.get(key); 22 | } 23 | 24 | put(key: string, value: T): void { 25 | this.cache.set(key, value); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /projects/cache/src/lib/impl/no-op-cache.ts: -------------------------------------------------------------------------------- 1 | import {Cache} from '../cache.model'; 2 | 3 | export class NoOpCache implements Cache { 4 | 5 | readonly name: string; 6 | 7 | constructor(name: string) { 8 | this.name = name; 9 | } 10 | 11 | clear(): void { 12 | } 13 | 14 | evict(key: string): void { 15 | } 16 | 17 | get(key: string): T { 18 | return undefined; 19 | } 20 | 21 | put(key: string, value: T): void { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /projects/cache/src/lib/impl/storage-cache.ts: -------------------------------------------------------------------------------- 1 | import {Cache} from '../cache.model'; 2 | 3 | export class StorageCache implements Cache { 4 | 5 | readonly name: string; 6 | private storage: Storage; 7 | 8 | constructor(name: string, storage: Storage) { 9 | this.name = name; 10 | this.storage = storage; 11 | } 12 | 13 | clear(): void { 14 | this.storage.clear(); 15 | } 16 | 17 | evict(key: string): void { 18 | this.storage.removeItem(key); 19 | } 20 | 21 | get(key: string): T { 22 | const value: string = this.storage.getItem(key); 23 | if (!value) { 24 | return undefined; 25 | } 26 | 27 | return JSON.parse(value) || null; 28 | } 29 | 30 | put(key: string, value: T): void { 31 | this.storage.setItem(key, JSON.stringify(value)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /projects/cache/src/lib/simple-cache.manager.ts: -------------------------------------------------------------------------------- 1 | import {CacheManager} from './cache.manager'; 2 | import {Cache} from './cache.model'; 3 | 4 | export class SimpleCacheManager implements CacheManager { 5 | private cacheMap: Map; 6 | 7 | constructor(caches?: Cache[]) { 8 | this.setCaches(caches); 9 | } 10 | 11 | getCacheNames(): string[] { 12 | return Array.from(this.cacheMap.keys()); 13 | } 14 | 15 | getCache(name: string): Cache { 16 | return this.cacheMap.get(name) || null; 17 | } 18 | 19 | addCache(cache: Cache): void { 20 | if (!cache) { 21 | throw new Error('Cache is undefined'); 22 | } 23 | this.cacheMap.set(cache.name, cache); 24 | } 25 | 26 | setCaches(caches: Cache[]): void { 27 | this.cacheMap = new Map(); 28 | if (caches) { 29 | caches.forEach(cache => this.addCache(cache)); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /projects/cache/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 recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /projects/cache/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of cache 3 | */ 4 | 5 | export {CacheModule} from './lib/cache.module'; 6 | export { 7 | CacheDefaults, CacheResult, CacheKey, CacheValue, CachePut, CacheRemove, CacheRemoveAll 8 | }from './lib/cache.decorator'; 9 | export {CacheManager} from './lib/cache.manager'; 10 | export {SimpleCacheManager} from './lib/simple-cache.manager'; 11 | export {getCacheManager, initCacheManager} from './lib/cache.instance'; 12 | export {Cache} from './lib/cache.model'; 13 | export {NoOpCache} from './lib/impl/no-op-cache'; 14 | export {StorageCache} from './lib/impl/storage-cache'; 15 | export {MemoryCache} from './lib/impl/memory-cache'; 16 | -------------------------------------------------------------------------------- /projects/cache/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/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /projects/cache/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 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": [ 20 | "src/test.ts", 21 | "**/*.spec.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /projects/cache/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "compilationMode": "partial" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/cache/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 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/cookie/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*", 5 | "**/*.spec.ts" 6 | ], 7 | "overrides": [ 8 | { 9 | "files": [ 10 | "*.ts" 11 | ], 12 | "parserOptions": { 13 | "project": [ 14 | "projects/cookie/tsconfig.lib.json", 15 | "projects/cookie/tsconfig.spec.json" 16 | ], 17 | "createDefaultProgram": true 18 | }, 19 | "rules": { 20 | "@angular-eslint/directive-selector": [ 21 | "error", 22 | { 23 | "type": "attribute", 24 | "prefix": "lib", 25 | "style": "camelCase" 26 | } 27 | ], 28 | "@angular-eslint/component-selector": [ 29 | "error", 30 | { 31 | "type": "element", 32 | "prefix": "lib", 33 | "style": "kebab-case" 34 | } 35 | ], 36 | "@typescript-eslint/no-unsafe-member-access": "off", 37 | "@typescript-eslint/no-unsafe-assignment": "off", 38 | "@typescript-eslint/no-unsafe-call": "off", 39 | "@typescript-eslint/no-unsafe-return": "off", 40 | "@typescript-eslint/no-unsafe-argument": "off", 41 | "@typescript-eslint/no-empty-function": "off", 42 | "@typescript-eslint/no-unused-vars": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/restrict-template-expressions": "off", 45 | "@typescript-eslint/ban-types": "off" 46 | } 47 | }, 48 | { 49 | "files": [ 50 | "*.html" 51 | ], 52 | "rules": {} 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /projects/cookie/README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/@ngx-toolkit/cookie.svg)](https://www.npmjs.com/package/@ngx-toolkit/cookie) 2 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 3 | [![Build Status](https://travis-ci.org/dewizz/ngx-toolkit.svg?branch=master)](https://travis-ci.org/dewizz/ngx-toolkit) 4 | [![Coverage](https://coveralls.io/repos/github/dewizz/ngx-toolkit/badge.svg?branch=master#5)](https://coveralls.io/github/dewizz/ngx-toolkit?branch=master) 5 | [![Join the chat at https://gitter.im/ngx-toolkit/Lobby](https://badges.gitter.im/ngx-toolkit/Lobby.svg)](https://gitter.im/ngx-toolkit/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | # @ngx-toolkit/cookie 8 | 9 | > Angular CookieService implementation for Browser & Server platforms. 10 | 11 | # Table of contents: 12 | * [Installation](#installation) 13 | * [Usage with...](#usage) 14 | * [annotation](#usage-annotation) 15 | * [service](#usage-service) 16 | * [API](#api) 17 | * [Cookie](#cookie) 18 | * [CookieService](#cookieservice) 19 | * [CookieOptions](#cookieoptions) 20 | * [Universal Usage](#universal-usage) 21 | * [License](#license) 22 | 23 | --- 24 | 25 | # Installation 26 | 27 | Install the npm package. 28 | 29 | ```bash 30 | # To get the latest stable version and update package.json file: 31 | npm install @ngx-toolkit/cookie --save 32 | # or 33 | yarn add @ngx-toolkit/cookie 34 | ``` 35 | 36 | Registered `CookieModule` in the root Module of your application with `forRoot(cookieOptions?: CookieOptions)` static method. 37 | *CookieOptions is optional, by default the path is set to '/' and cookies never expired.* 38 | 39 | ```typescript 40 | import { NgModule } from '@angular/core'; 41 | import { BrowserModule } from '@angular/platform-browser'; 42 | import { CookieModule } from '@ngx-toolkit/cookie'; 43 | 44 | import { AppComponent } from './app.component'; 45 | 46 | @NgModule({ 47 | imports: [ BrowserModule, CookieModule.forRoot() ], 48 | declarations: [ AppComponent ], 49 | bootstrap: [ AppComponent ] 50 | }) 51 | export class AppModule { } 52 | ``` 53 | 54 | # Usage 55 | 56 | ## Annotation 57 | ```typescript 58 | import { Component, OnInit } from '@angular/core'; 59 | import { Cookie } from '@ngx-toolkit/cookie'; 60 | 61 | @Component({ 62 | selector: 'app-root', 63 | templateUrl: './app.component.html', 64 | styleUrls: ['./app.component.css'] 65 | }) 66 | export class AppComponent implements OnInit { 67 | 68 | @Cookie('accept-cookie') 69 | acceptedCookie: boolean; 70 | 71 | acceptCookie() { 72 | this.acceptedCookie = true; 73 | } 74 | } 75 | ``` 76 | 77 | ## Service 78 | ```typescript 79 | import { Component, OnInit } from '@angular/core'; 80 | import { CookieService } from '@ngx-toolkit/cookie'; 81 | 82 | @Component({ 83 | selector: 'app-root', 84 | templateUrl: './app.component.html', 85 | styleUrls: ['./app.component.css'] 86 | }) 87 | export class AppComponent implements OnInit { 88 | 89 | acceptedCookie: boolean; 90 | 91 | constructor(private cookieService: CookieService) {} 92 | 93 | ngOnInit() { 94 | this.acceptedCookie = this.cookieService.getItem('accept-cookie') === 'true'; 95 | } 96 | 97 | acceptCookie() { 98 | this.cookieService.setItem('accept-cookie', 'true'); 99 | this.acceptedCookie = true; 100 | } 101 | } 102 | ``` 103 | 104 | # API 105 | 106 | ## Cookie 107 | 108 | Cookie annotation: 109 | ```typescript 110 | /** 111 | * Get / Set cookie 112 | * @param {string} name (default is the property name) 113 | * @param {CookieOptions} options (to override default options) 114 | */ 115 | @Cookie(name?: string, options?: CookieOptions) 116 | property: any; 117 | ``` 118 | 119 | ## CookieService 120 | 121 | The `CookieSerivce` API: 122 | 123 | ```typescript 124 | interface CookieService { 125 | 126 | /** 127 | * Get all cookies in object format. 128 | * 129 | * @returns {{[p: string]: string}} 130 | */ 131 | getAll(): { [key: string]: string }; 132 | 133 | /** 134 | * Read a cookie. If the cookie doesn't exist a null value will be returned. 135 | * 136 | * @param key The name of the cookie to read (string). 137 | * @param {string} key 138 | * @returns {string | null} 139 | */ 140 | getItem(key: string): string | null; 141 | 142 | /** 143 | * Check whether a cookie exists in the current position. 144 | * 145 | * @param key The name of the cookie to test (string). 146 | * @returns {boolean} 147 | */ 148 | hasItem(key: string): boolean; 149 | 150 | /** 151 | * Add that cookie to the storage, or update that cookie's value if it already exists. 152 | * 153 | * @param {string} The name of the cookie you want to create/update. 154 | * @param {string} the value you want to give the cookie you are creating/updating. 155 | * @param {CookieOptions} Override default options 156 | */ 157 | setItem(key: string, data?: string, options?: CookieOptions): void; 158 | 159 | /** 160 | * Delete a cookie. 161 | * 162 | * @param {string} The name of the cookie to remove 163 | * @param {CookieOptions} Override default options 164 | */ 165 | removeItem(key: string, options?: CookieOptions): void; 166 | 167 | /** 168 | * Remove all cookie. 169 | * 170 | * @param {CookieOptions} Override default options 171 | */ 172 | clear(options?: CookieOptions): void; 173 | 174 | /** 175 | * Return all cookie names. 176 | * 177 | * @returns {any} Cookie names 178 | */ 179 | keys(): string[]; 180 | 181 | /** 182 | * Returns an integer representing the number of cookie items. 183 | * 184 | * @returns {number} 185 | */ 186 | get length(): number; 187 | 188 | /** 189 | * Return the cookie name at a index. 190 | * 191 | * @param {number} The index position. 192 | * @returns {any} The cookie name or null 193 | */ 194 | key(index: number): string | null; 195 | } 196 | ``` 197 | 198 | ## CookieOptions 199 | 200 | ```typescript 201 | class CookieOptions { 202 | /** 203 | * The path from where the cookie will be readable. 204 | */ 205 | path?: string; 206 | /** 207 | * The domain from where the cookie will be readable. 208 | */ 209 | domain?: string; 210 | /** 211 | * The expiration : 212 | * - in seconds for the max-age 213 | * - Infinity for a never expiration 214 | * - Date in GMTString format 215 | * - Date object 216 | * 217 | * If not specified the cookie will expire at the end of the session. 218 | */ 219 | expires?: Date | string | number; 220 | /** 221 | * If true, the cookie will be transmitted only over secure protocol as https. 222 | */ 223 | secure?: boolean; 224 | } 225 | ``` 226 | 227 | # Universal Usage 228 | 229 | You just have to provide another `CookieFactory` implemantation. 230 | 231 | `ServerCookieFactory` implementation is available (works with express only). 232 | Sample with [@nguniversal/express-engine](https://github.com/angular/universal/tree/master/modules/express-engine): 233 | 234 | ```typescript 235 | import { NgModule } from '@angular/core'; 236 | import { ServerModule } from '@angular/platform-server'; 237 | import { REQUEST, RESPONSE } from '@nguniversal/express-engine'; 238 | import { CookieFactory, ServerCookieFactory } from '@ngx-toolkit/cookie'; 239 | 240 | import { AppModule } from './app.module'; 241 | import { AppComponent } from './app.component'; 242 | 243 | export function newCookieFactory(req: any, res: any) { 244 | return new ServerCookieFactory(req, res); 245 | } 246 | 247 | @NgModule({ 248 | imports: [ AppModule, ServerModule ], 249 | bootstrap: [ AppComponent ], 250 | providers: [ 251 | { 252 | provide: CookieFactory, 253 | useFactory: newCookieFactory, 254 | deps: [REQUEST, RESPONSE] 255 | } 256 | ], 257 | }) 258 | export class AppServerModule { } 259 | ``` 260 | 261 | ---- 262 | 263 | # License 264 | © 2018 Dewizz 265 | 266 | [MIT](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 267 | -------------------------------------------------------------------------------- /projects/cookie/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'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/cookie'), 29 | subdir: '.', 30 | reporters: [ 31 | {type: 'html'}, 32 | {type: 'lcovonly'}, 33 | {type: 'text-summary'} 34 | ] 35 | }, 36 | reporters: ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessCI'], 42 | customLaunchers: { 43 | ChromeHeadlessCI: { 44 | base: 'ChromeHeadless', 45 | flags: ['--no-sandbox'] 46 | } 47 | }, 48 | singleRun: false, 49 | restartOnFileChange: true 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /projects/cookie/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/cookie", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/cookie/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-toolkit/cookie", 3 | "version": "0.0.0-PLACEHOLDER", 4 | "description": "Angular cookie with Universal support", 5 | "homepage": "https://github.com/dewizz/ngx-toolkit", 6 | "license": "MIT", 7 | "author": "Dewizz", 8 | "keywords": [ 9 | "angular", 10 | "ngx", 11 | "ngx-toolkit", 12 | "universal", 13 | "annotation", 14 | "decorator", 15 | "cookie", 16 | "cookie-service" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/dewizz/ngx-toolkit" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/dewizz/ngx-toolkit/issues" 24 | }, 25 | "dependencies": { 26 | "tslib": "^2.0.0" 27 | }, 28 | "peerDependencies": { 29 | "@angular/common": "^13.2.0", 30 | "@angular/core": "^13.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/browser/browser-cookie.factory.ts: -------------------------------------------------------------------------------- 1 | import {DOCUMENT} from '@angular/common'; 2 | import {Inject, Injectable} from '@angular/core'; 3 | import {CookieOptions, cookiesStrToObj} from '../cookie.model'; 4 | import {CookieFactory} from '../cookie.service'; 5 | 6 | @Injectable() 7 | export class BrowserCookieFactory implements CookieFactory { 8 | private lastCookies = ''; 9 | private cookies: { [key in string]: string } = {}; 10 | 11 | constructor(@Inject(DOCUMENT) private document: any) { 12 | } 13 | 14 | getAll(): { [p: string]: string } { 15 | const cookiesStr: string = this.document.cookie; 16 | if (this.lastCookies !== cookiesStr) { 17 | this.lastCookies = cookiesStr; 18 | this.cookies = cookiesStrToObj(cookiesStr); 19 | } 20 | return this.cookies; 21 | } 22 | 23 | save(key: string, data: string, options: CookieOptions): void { 24 | this.document.cookie = `${encodeURIComponent(key)}=${data ? encodeURIComponent(data) : ''}${ 25 | options.expires ? `; expires=${( options.expires).toUTCString()}` : '' 26 | }${options.domain ? `; domain=${options.domain}` : ''}${options.path ? `; path=${options.path}` : ''}${ 27 | options.secure ? '; secure' : '' 28 | }`; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/browser/index.ts: -------------------------------------------------------------------------------- 1 | export {BrowserCookieFactory} from './browser-cookie.factory'; 2 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/cookie.decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import {BrowserCookieFactory} from './browser'; 2 | import {Cookie, COOKIE_DECORATOR_DATA} from './cookie.decorator'; 3 | import {CookieService} from './cookie.service'; 4 | 5 | describe('CookieDecorator', () => { 6 | beforeEach(() => { 7 | const service: CookieService = new CookieService(null, new BrowserCookieFactory(document)); 8 | service.clear(); 9 | 10 | COOKIE_DECORATOR_DATA.cookieService = service; 11 | }); 12 | 13 | it('should not has an injector', () => { 14 | class Test { 15 | @Cookie() test: string; 16 | } 17 | 18 | const test: Test = new Test(); 19 | expect(test.test).toBeNull(); 20 | test.test = 'data'; 21 | expect(test.test).toEqual('data'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/cookie.decorator.ts: -------------------------------------------------------------------------------- 1 | import {CookieOptions} from './cookie.model'; 2 | import {CookieService} from './cookie.service'; 3 | 4 | export interface CookieDecoratorData { 5 | cookieService?: CookieService; 6 | } 7 | 8 | export const COOKIE_DECORATOR_DATA: CookieDecoratorData = {}; 9 | 10 | /** 11 | * Property Decorator to Get / Set cookie 12 | */ 13 | export function Cookie(name?: string, options?: CookieOptions): PropertyDecorator { 14 | return function(target: any, key: string) { 15 | let _value: any; 16 | 17 | if (delete target[key]) { 18 | const cookieName: string = name || key; 19 | 20 | Object.defineProperty(target, key, { 21 | get: function() { 22 | if (COOKIE_DECORATOR_DATA.cookieService) { 23 | const cookieValue: string = COOKIE_DECORATOR_DATA.cookieService.getItem(cookieName); 24 | try { 25 | return JSON.parse(cookieValue); 26 | } catch (e) { 27 | return cookieValue; 28 | } 29 | } else { 30 | return _value; 31 | } 32 | }, 33 | set: function(value) { 34 | if (COOKIE_DECORATOR_DATA.cookieService) { 35 | COOKIE_DECORATOR_DATA.cookieService.setItem( 36 | cookieName, 37 | typeof value === 'string' ? value : JSON.stringify(value), 38 | options 39 | ); 40 | } else { 41 | _value = value; 42 | } 43 | }, 44 | enumerable: true, 45 | configurable: true 46 | }); 47 | } 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/cookie.model.ts: -------------------------------------------------------------------------------- 1 | export interface CookieOptions { 2 | /** 3 | * The path from where the cookie will be readable. 4 | */ 5 | path?: string; 6 | /** 7 | * The domain from where the cookie will be readable. 8 | */ 9 | domain?: string; 10 | /** 11 | * The expiration : 12 | * - in seconds for the max-age 13 | * - Infinity for a never expiration 14 | * - Date in GMTString format 15 | * - Date object 16 | * 17 | * If not specified the cookie will expire at the end of the session. 18 | */ 19 | expires?: Date | string | number; 20 | /** 21 | * If true, the cookie will be transmitted only over secure protocol as https. 22 | */ 23 | secure?: boolean; 24 | } 25 | 26 | /** 27 | * Convert cookies string to object 28 | * @param cookiesStr 29 | */ 30 | export function cookiesStrToObj(cookiesStr: string): { [key in string]: string } { 31 | const cookies: { [key in string]: string } = {}; 32 | if (!cookiesStr) { 33 | return cookies; 34 | } 35 | 36 | cookiesStr.split('; ').forEach(cookie => { 37 | const cookieSplited: string[] = cookie.split('='); 38 | cookies[decodeURIComponent(cookieSplited[0])] = decodeURIComponent(cookieSplited[1]); 39 | }); 40 | return cookies; 41 | } 42 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/cookie.module.spec.ts: -------------------------------------------------------------------------------- 1 | import {ApplicationInitStatus} from '@angular/core'; 2 | import {TestBed} from '@angular/core/testing'; 3 | import {COOKIE_DECORATOR_DATA} from './cookie.decorator'; 4 | import {CookieModule} from './cookie.module'; 5 | 6 | describe('CookieModule', () => { 7 | it('should work', () => { 8 | expect(new CookieModule()).toBeDefined(); 9 | }); 10 | 11 | it('should configure COOKIE_DECORATOR_DATA', async (done: DoneFn) => { 12 | await TestBed.configureTestingModule({ 13 | imports: [CookieModule.forRoot()] 14 | }) 15 | // https://github.com/angular/angular/issues/24218 16 | .get(ApplicationInitStatus).donePromise; 17 | 18 | expect(COOKIE_DECORATOR_DATA.cookieService).toBeDefined(); 19 | done(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/cookie.module.ts: -------------------------------------------------------------------------------- 1 | import {DOCUMENT} from '@angular/common'; 2 | import {APP_INITIALIZER, ModuleWithProviders, NgModule} from '@angular/core'; 3 | import {BrowserCookieFactory} from './browser/index'; 4 | import {COOKIE_DECORATOR_DATA} from './cookie.decorator'; 5 | import {CookieOptions} from './cookie.model'; 6 | import {CookieFactory, CookieService} from './cookie.service'; 7 | import {COOKIE_OPTIONS} from './cookie.token'; 8 | 9 | export function setupCookieDecorator(cookieService: CookieService) { 10 | COOKIE_DECORATOR_DATA.cookieService = cookieService; 11 | return () => null; 12 | } 13 | 14 | export function newCookieFactory(document: any) { 15 | return new BrowserCookieFactory(document); 16 | } 17 | 18 | @NgModule() 19 | export class CookieModule { 20 | /** 21 | * In root module to provide the CookieService & CookieOptions 22 | */ 23 | static forRoot(cookieOptions?: CookieOptions): ModuleWithProviders { 24 | return { 25 | ngModule: CookieModule, 26 | providers: [ 27 | { 28 | provide: COOKIE_OPTIONS, 29 | useValue: cookieOptions 30 | }, 31 | { 32 | provide: CookieFactory, 33 | useFactory: newCookieFactory, 34 | deps: [DOCUMENT] 35 | }, 36 | CookieService, 37 | { 38 | provide: APP_INITIALIZER, 39 | useFactory: setupCookieDecorator, 40 | deps: [CookieService], 41 | multi: true 42 | } 43 | ] 44 | }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/cookie.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {BrowserCookieFactory} from './browser/browser-cookie.factory'; 2 | import {CookieFactory, CookieService, DATE_MAX_EXPIRES} from './cookie.service'; 3 | 4 | describe('CookieService', () => { 5 | let service: CookieService; 6 | let factory: CookieFactory; 7 | 8 | beforeEach(() => { 9 | factory = new BrowserCookieFactory(document); 10 | service = new CookieService(null, factory); 11 | service.clear(); 12 | }); 13 | 14 | it('#service is defined', () => { 15 | expect(service).toBeDefined(); 16 | expect(service instanceof CookieService).toBeTruthy(); 17 | }); 18 | 19 | it('check unexist cookie', () => { 20 | const cookieKey = 'cookie_name'; 21 | 22 | expect(service.hasItem(cookieKey)).toBeFalsy(); 23 | expect(service.getItem(cookieKey)).toBeNull(); 24 | expect(service.keys()).toEqual([]); 25 | expect(service.length).toBe(0); 26 | }); 27 | 28 | it('#setItem should world :)', () => { 29 | const cookieKey = 'cookie_name'; 30 | const cookieValue = 'cookie_value'; 31 | 32 | service.setItem(null); 33 | 34 | service.setItem(cookieKey, cookieValue); 35 | expect(service.hasItem(cookieKey)).toBeTruthy(); 36 | expect(service.getItem(cookieKey)).toBe(cookieValue); 37 | 38 | expect(service.keys()).toEqual([cookieKey]); 39 | expect(service.length).toBe(1); 40 | }); 41 | 42 | it('check unexist cookie', () => { 43 | const cookieKey1 = 'cookie1'; 44 | const cookieKey2 = 'cookie2'; 45 | 46 | service.setItem(cookieKey1, cookieKey1); 47 | service.setItem(cookieKey2, cookieKey2); 48 | expect(service.keys()).toEqual([cookieKey1, cookieKey2]); 49 | expect(service.length).toBe(2); 50 | expect(service.key(1)).toEqual(cookieKey2); 51 | expect(service.key(2)).toEqual(null); 52 | }); 53 | 54 | it('check default options', () => { 55 | spyOn(factory, 'save'); 56 | 57 | service.setItem('test', 'test'); 58 | expect(factory.save).toHaveBeenCalledWith('test', 'test', {path: '/', expires: DATE_MAX_EXPIRES}); 59 | 60 | service.setItem('test', 'test', {path: '/toto'}); 61 | expect(factory.save).toHaveBeenCalledWith('test', 'test', {path: '/toto', expires: DATE_MAX_EXPIRES}); 62 | 63 | service.setItem('test', 'test', {path: '/toto', domain: 'dewizz.com'}); 64 | expect(factory.save).toHaveBeenCalledWith('test', 'test', { 65 | path: '/toto', 66 | expires: DATE_MAX_EXPIRES, 67 | domain: 'dewizz.com' 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/cookie.service.ts: -------------------------------------------------------------------------------- 1 | import {Inject, Injectable, Optional} from '@angular/core'; 2 | import {CookieOptions} from './cookie.model'; 3 | import {COOKIE_OPTIONS} from './cookie.token'; 4 | 5 | export const DATE_MAX_EXPIRES: Date = new Date('Fri, 31 Dec 9999 23:59:59 GMT'); 6 | export const DEFAULT_COOKIE_OPTIONS: CookieOptions = { 7 | path: '/', 8 | expires: Infinity 9 | }; 10 | 11 | export abstract class CookieFactory { 12 | /** 13 | * Get all cookies in object format. 14 | */ 15 | abstract getAll(): { [key: string]: string }; 16 | 17 | /** 18 | * Implementation (create / update / delete) 19 | */ 20 | abstract save(key: string, data: string, options: CookieOptions): void; 21 | } 22 | 23 | @Injectable() 24 | export class CookieService { 25 | private cookieOptions: CookieOptions; 26 | 27 | constructor( 28 | @Optional() 29 | @Inject(COOKIE_OPTIONS) 30 | cookieOptions: CookieOptions, 31 | private cookieFactory: CookieFactory 32 | ) { 33 | this.cookieOptions = cookieOptions || DEFAULT_COOKIE_OPTIONS; 34 | } 35 | 36 | /** 37 | * Returns an integer representing the number of cookie items. 38 | */ 39 | get length(): number { 40 | return this.keys().length; 41 | } 42 | 43 | /** 44 | * Transform to expires Date 45 | */ 46 | private static getExpiresDate(expires?: Date | string | number): Date { 47 | if (!expires) { 48 | return null; 49 | } 50 | 51 | switch (expires.constructor) { 52 | case Number: 53 | return expires === Infinity ? DATE_MAX_EXPIRES : new Date( expires * 1000 + Date.now()); 54 | case String: 55 | return new Date( expires); 56 | default: 57 | return expires; 58 | } 59 | } 60 | 61 | /** 62 | * Get all cookies in object format. 63 | */ 64 | getAll(): { [key: string]: string } { 65 | return this.cookieFactory.getAll(); 66 | } 67 | 68 | /** 69 | * Read a cookie. If the cookie doesn't exist a null value will be returned. 70 | */ 71 | getItem(key: string): string | null { 72 | return this.getAll()[key] || null; 73 | } 74 | 75 | /** 76 | * Check whether a cookie exists in the current position. 77 | */ 78 | hasItem(key: string): boolean { 79 | return Object.prototype.hasOwnProperty.call(this.getAll(), key); 80 | } 81 | 82 | /** 83 | * Return all cookie names. 84 | */ 85 | keys(): string[] { 86 | return Object.keys(this.getAll()); 87 | } 88 | 89 | /** 90 | * Return the cookie name at a index. 91 | */ 92 | key(index: number): string | null { 93 | return this.keys()[index] || null; 94 | } 95 | 96 | /** 97 | * Add that cookie to the storage, or update that cookie's value if it already exists. 98 | */ 99 | setItem(key: string, data?: string, options?: CookieOptions): void { 100 | if (!key) { 101 | return; 102 | } 103 | 104 | // Merge options 105 | options = Object.assign({}, this.cookieOptions, options || {}); 106 | 107 | // Remove data 108 | if (!data) { 109 | options.expires = 'Thu, 01 Jan 1970 00:00:00 GMT'; 110 | } 111 | 112 | // Convert expires to Date 113 | if (options.expires) { 114 | options.expires = CookieService.getExpiresDate(options.expires); 115 | } 116 | 117 | this.cookieFactory.save(key, data, options); 118 | } 119 | 120 | /** 121 | * Delete a cookie. 122 | */ 123 | removeItem(key: string, options?: CookieOptions): void { 124 | return this.setItem(key, null, options); 125 | } 126 | 127 | /** 128 | * Remove all cookie. 129 | */ 130 | clear(options?: CookieOptions): void { 131 | this.keys().forEach(key => { 132 | this.removeItem(key, options); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/cookie.token.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import {CookieOptions} from './cookie.model'; 3 | 4 | export const COOKIE_OPTIONS = new InjectionToken('COOKIE_OPTIONS'); 5 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/server/index.ts: -------------------------------------------------------------------------------- 1 | export {ServerCookieFactory} from './server-cookie.factory'; 2 | -------------------------------------------------------------------------------- /projects/cookie/src/lib/server/server-cookie.factory.ts: -------------------------------------------------------------------------------- 1 | import {CookieOptions, cookiesStrToObj} from '../cookie.model'; 2 | import {CookieFactory} from '../cookie.service'; 3 | 4 | export class ServerCookieFactory implements CookieFactory { 5 | private cookies: { [key in string]: string } = {}; 6 | 7 | constructor(request: any, private response: any) { 8 | if (request && request.headers) { 9 | this.cookies = cookiesStrToObj(request.headers.cookie); 10 | } 11 | } 12 | 13 | getAll(): { [p: string]: string } { 14 | return this.cookies; 15 | } 16 | 17 | save(key: string, data: string, options: CookieOptions): void { 18 | if (!data) { 19 | delete this.cookies[key]; 20 | this.response.clearCookie(key, options); 21 | } else { 22 | this.cookies[key] = data; 23 | this.response.cookie(key, data, options); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /projects/cookie/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 recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /projects/cookie/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of cookie 3 | */ 4 | 5 | export {CookieOptions} from './lib/cookie.model'; 6 | export {COOKIE_OPTIONS} from './lib/cookie.token'; 7 | export {CookieFactory, CookieService} from './lib/cookie.service'; 8 | export {CookieModule} from './lib/cookie.module'; 9 | export {BrowserCookieFactory} from './lib/browser/index'; 10 | export {ServerCookieFactory} from './lib/server/index'; 11 | export {Cookie} from './lib/cookie.decorator'; 12 | -------------------------------------------------------------------------------- /projects/cookie/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/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /projects/cookie/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 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": [ 20 | "src/test.ts", 21 | "**/*.spec.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /projects/cookie/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "compilationMode": "partial" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/cookie/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 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/device/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*", 5 | "**/*.spec.ts" 6 | ], 7 | "overrides": [ 8 | { 9 | "files": [ 10 | "*.ts" 11 | ], 12 | "parserOptions": { 13 | "project": [ 14 | "projects/device/tsconfig.lib.json", 15 | "projects/device/tsconfig.spec.json" 16 | ], 17 | "createDefaultProgram": true 18 | }, 19 | "rules": { 20 | "@angular-eslint/directive-selector": [ 21 | "error", 22 | { 23 | "type": "attribute", 24 | "prefix": "lib", 25 | "style": "camelCase" 26 | } 27 | ], 28 | "@angular-eslint/component-selector": [ 29 | "error", 30 | { 31 | "type": "element", 32 | "prefix": "lib", 33 | "style": "kebab-case" 34 | } 35 | ], 36 | "@typescript-eslint/no-unsafe-member-access": "off", 37 | "@typescript-eslint/no-unsafe-assignment": "off", 38 | "@typescript-eslint/no-unsafe-call": "off", 39 | "@typescript-eslint/no-unsafe-return": "off", 40 | "@typescript-eslint/no-unsafe-argument": "off", 41 | "@typescript-eslint/no-empty-function": "off", 42 | "@typescript-eslint/no-unused-vars": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/restrict-template-expressions": "off", 45 | "@typescript-eslint/ban-types": "off" 46 | } 47 | }, 48 | { 49 | "files": [ 50 | "*.html" 51 | ], 52 | "rules": {} 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /projects/device/README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/@ngx-toolkit/device.svg)](https://www.npmjs.com/package/@ngx-toolkit/device) 2 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 3 | [![Build Status](https://travis-ci.org/dewizz/ngx-toolkit.svg?branch=master)](https://travis-ci.org/dewizz/ngx-toolkit) 4 | [![Coverage](https://coveralls.io/repos/github/dewizz/ngx-toolkit/badge.svg?branch=master#5)](https://coveralls.io/github/dewizz/ngx-toolkit?branch=master) 5 | [![Join the chat at https://gitter.im/ngx-toolkit/Lobby](https://badges.gitter.im/ngx-toolkit/Lobby.svg)](https://gitter.im/ngx-toolkit/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | # @ngx-toolkit/device 8 | > Angular Device detection for Browser & Server platforms 9 | 10 | # Table of contents: 11 | * [Installation](#installation) 12 | * [Usage](#usage) 13 | * [API](#api) 14 | * [Device](#device) 15 | * [Universal Usage](#universal-usage) 16 | * [License](#license) 17 | 18 | --- 19 | 20 | # Installation 21 | 22 | Install the npm package. 23 | 24 | ```bash 25 | # To get the latest stable version and update package.json file: 26 | npm install @ngx-toolkit/device --save 27 | # or 28 | yarn add @ngx-toolkit/device 29 | ``` 30 | 31 | Registered `DeviceModule` in the root Module of your application with `forRoot()` static method. 32 | 33 | ```typescript 34 | import { NgModule } from '@angular/core'; 35 | import { BrowserModule } from '@angular/platform-browser'; 36 | import { DeviceModule } from '@ngx-toolkit/device'; 37 | 38 | import { AppComponent } from './app.component'; 39 | 40 | @NgModule({ 41 | imports: [ BrowserModule, DeviceModule.forRoot() ], 42 | declarations: [ AppComponent ], 43 | bootstrap: [ AppComponent ] 44 | }) 45 | export class AppModule { } 46 | ``` 47 | 48 | # Usage 49 | 50 | ```typescript 51 | import { Component, OnInit, Inject } from '@angular/core'; 52 | import { DEVICE, Device } from '@ngx-toolkit/device'; 53 | 54 | @Component({ 55 | selector: 'app-root', 56 | templateUrl: './app.component.html', 57 | styleUrls: ['./app.component.css'] 58 | }) 59 | export class AppComponent implements OnInit { 60 | 61 | constructor(@Inject(DEVICE) device: Device) { 62 | console.log(device.isMobile()); 63 | } 64 | 65 | } 66 | ``` 67 | 68 | # API 69 | 70 | ## Device 71 | 72 | The `Device` API: 73 | 74 | ```typescript 75 | enum DeviceType { 76 | TABLET, 77 | MOBILE, 78 | NORMAL 79 | } 80 | 81 | enum DevicePlatform { 82 | ANDROID, 83 | IOS, 84 | UNKNOWN 85 | } 86 | 87 | interface Device { 88 | type: DeviceType; 89 | platform: DevicePlatform; 90 | 91 | isNormal(): boolean; 92 | isMobile(): boolean; 93 | isTablet(): boolean; 94 | } 95 | ``` 96 | 97 | # Universal Usage 98 | 99 | You just have to provide an UserAgent. 100 | 101 | Sample with [@nguniversal/express-engine](https://github.com/angular/universal/tree/master/modules/express-engine): 102 | 103 | ```typescript 104 | import { NgModule } from '@angular/core'; 105 | import { ServerModule } from '@angular/platform-server'; 106 | import { REQUEST } from '@nguniversal/express-engine'; 107 | import { USER_AGENT } from '@ngx-toolkit/device'; 108 | 109 | import { AppModule } from './app.module'; 110 | import { AppComponent } from './app.component'; 111 | 112 | export function userAgentFactory(request: any) { 113 | return request.get('User-Agent'); 114 | } 115 | 116 | @NgModule({ 117 | imports: [ AppModule, ServerModule ], 118 | bootstrap: [ AppComponent ], 119 | providers: [ 120 | { 121 | provide: USER_AGENT, 122 | useFactory: userAgentFactory, 123 | deps: [REQUEST] 124 | } 125 | ], 126 | }) 127 | export class AppServerModule { } 128 | ``` 129 | 130 | ---- 131 | 132 | # License 133 | © 2018 Dewizz 134 | 135 | [MIT](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 136 | -------------------------------------------------------------------------------- /projects/device/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'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/device'), 29 | subdir: '.', 30 | reporters: [ 31 | {type: 'html'}, 32 | {type: 'lcovonly'}, 33 | {type: 'text-summary'} 34 | ] 35 | }, 36 | reporters: ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessCI'], 42 | customLaunchers: { 43 | ChromeHeadlessCI: { 44 | base: 'ChromeHeadless', 45 | flags: ['--no-sandbox'] 46 | } 47 | }, 48 | singleRun: false, 49 | restartOnFileChange: true 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /projects/device/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/device", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/device/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-toolkit/device", 3 | "version": "0.0.0-PLACEHOLDER", 4 | "description": "Angular device user-agent detection with Universal support", 5 | "homepage": "https://github.com/dewizz/ngx-toolkit", 6 | "license": "MIT", 7 | "author": "Dewizz", 8 | "keywords": [ 9 | "angular", 10 | "ngx", 11 | "ngx-toolkit", 12 | "universal", 13 | "user-agent", 14 | "decorator", 15 | "device", 16 | "device-detection" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/dewizz/ngx-toolkit" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/dewizz/ngx-toolkit/issues" 24 | }, 25 | "dependencies": { 26 | "tslib": "^2.0.0" 27 | }, 28 | "peerDependencies": { 29 | "@angular/core": "^13.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /projects/device/src/lib/device.model.ts: -------------------------------------------------------------------------------- 1 | export enum DeviceType { 2 | TABLET = 0, 3 | MOBILE, 4 | NORMAL 5 | } 6 | 7 | export enum DevicePlatform { 8 | ANDROID = 0, 9 | IOS, 10 | UNKNOWN 11 | } 12 | 13 | export class Device { 14 | type: DeviceType; 15 | platform: DevicePlatform; 16 | 17 | constructor(type: DeviceType = DeviceType.NORMAL, platform: DevicePlatform = DevicePlatform.UNKNOWN) { 18 | this.type = type; 19 | this.platform = platform; 20 | } 21 | 22 | isNormal(): boolean { 23 | return this.type === DeviceType.NORMAL; 24 | } 25 | 26 | isMobile(): boolean { 27 | return this.type === DeviceType.MOBILE; 28 | } 29 | 30 | isTablet(): boolean { 31 | return this.type === DeviceType.TABLET; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /projects/device/src/lib/device.module.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | import {Device, DevicePlatform} from './device.model'; 3 | import {DeviceModule} from './device.module'; 4 | import {DEVICE, USER_AGENT} from './device.token'; 5 | 6 | describe('DeviceModule', () => { 7 | it('should work', () => { 8 | expect(new DeviceModule()).toBeDefined(); 9 | }); 10 | 11 | it('should has a device', () => { 12 | TestBed.configureTestingModule({imports: [DeviceModule.forRoot()]}); 13 | 14 | const device: Device = TestBed.get(DEVICE); 15 | expect(device).toBeDefined(); 16 | }); 17 | 18 | it('should has a device with specific UserAgent', () => { 19 | TestBed.configureTestingModule({ 20 | imports: [DeviceModule.forRoot()], 21 | providers: [ 22 | { 23 | provide: USER_AGENT, 24 | useValue: 25 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Mobile/11A465 Twitter for iPhone' 26 | } 27 | ] 28 | }); 29 | 30 | const device: Device = TestBed.get(DEVICE); 31 | expect(device).toBeDefined(); 32 | expect(device.isMobile()).toBeTruthy(); 33 | expect(device.platform).toBe(DevicePlatform.IOS); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /projects/device/src/lib/device.module.ts: -------------------------------------------------------------------------------- 1 | import {DOCUMENT} from '@angular/common'; 2 | import {ModuleWithProviders, NgModule, Optional} from '@angular/core'; 3 | import {Device} from './device.model'; 4 | import {DeviceService} from './device.service'; 5 | import {DEVICE, USER_AGENT} from './device.token'; 6 | 7 | export function deviceResolverFactory(userAgent?: string, document?: any): Device { 8 | if (!userAgent && document) { 9 | userAgent = document.defaultView?.navigator?.userAgent; 10 | } 11 | 12 | return DeviceService.resolveDevice(userAgent); 13 | } 14 | 15 | @NgModule() 16 | export class DeviceModule { 17 | /** 18 | * In root module to provide the DEVICE 19 | */ 20 | static forRoot(): ModuleWithProviders { 21 | return { 22 | ngModule: DeviceModule, 23 | providers: [ 24 | { 25 | provide: DEVICE, 26 | useFactory: deviceResolverFactory, 27 | deps: [[new Optional(), USER_AGENT], [new Optional(), DOCUMENT]] 28 | } 29 | ] 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /projects/device/src/lib/device.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {Device, DevicePlatform, DeviceType} from './device.model'; 2 | import {DeviceService} from './device.service'; 3 | 4 | describe('DeviceService', () => { 5 | it('devices is mobiles', () => { 6 | let device: Device; 7 | 8 | // Android 9 | [ 10 | 'Mozilla/5.0 (Linux; Android 6.0.1; SM-G920V Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36', 11 | 'Mozilla/5.0 (Linux; Android 5.1.1; SM-G928X Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36', 12 | 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586', 13 | 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6P Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36', 14 | 'Mozilla/5.0 (Linux; Android 6.0.1; E6653 Build/32.2.A.0.253) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36', 15 | 'Mozilla/5.0 (Linux; Android 6.0; HTC One M9 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36', 16 | 'Opera/12.02 (Android 4.1; Linux; Opera Mobi/ADR-1111101157; U; en-US) Presto/2.9.201 Version/12.02' 17 | ].forEach(UA => { 18 | device = DeviceService.resolveDevice(UA); 19 | expect(device.type).toBe(DeviceType.MOBILE); 20 | expect(device.isMobile()).toBeTruthy(); 21 | expect(device.platform).toBe(DevicePlatform.ANDROID); 22 | }); 23 | 24 | // Iphone / Ipod 25 | [ 26 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Mobile/11A465 Twitter for iPhone', 27 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_1_2 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Mobile/10B146', 28 | 'Mozilla/5.0 (iPod touch; CPU iPhone OS 7_0_3 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B511 Safari/9537.53', 29 | 'Mozilla/5.0 (iPod; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A405 Safari/7534.48.3' 30 | ].forEach(UA => { 31 | device = DeviceService.resolveDevice(UA); 32 | expect(device.type).toBe(DeviceType.MOBILE); 33 | expect(device.isMobile()).toBeTruthy(); 34 | expect(device.platform).toBe(DevicePlatform.IOS); 35 | }); 36 | 37 | // Others 38 | [ 39 | 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+', 40 | 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en-US) AppleWebKit/534.1+ (KHTML, like Gecko)', 41 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)', 42 | 'NokiaE66/GoBrowser/2.0.297', 43 | 'Nokia5320XpressMusic/GoBrowser/1.6.91', 44 | 'Opera/9.80 (J2ME/MIDP; Opera Mini/9.80 (J2ME/22.478; U; en) Presto/2.5.25 Version/10.54', 45 | 'Opera/9.80 (Windows NT 6.1; Opera Mobi/49; U; en) Presto/2.4.18 Version/10.00' 46 | ].forEach(UA => { 47 | device = DeviceService.resolveDevice(UA); 48 | expect(device.type).toBe(DeviceType.MOBILE); 49 | expect(device.isMobile()).toBeTruthy(); 50 | expect(device.platform).toBe(DevicePlatform.UNKNOWN); 51 | }); 52 | }); 53 | 54 | it('devices is tablets', () => { 55 | let device: Device; 56 | 57 | // Android 58 | [ 59 | 'Mozilla/5.0 (Linux; Android 7.0; Pixel C Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36', 60 | 'MMozilla/5.0 (Linux; Android 6.0.1; SGP771 Build/32.2.A.0.253; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36', 61 | 'Mozilla/5.0 (Linux; Android 5.1.1; SHIELD Tablet Build/LMY48C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Safari/537.36', 62 | 'Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-T550 Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/3.3 Chrome/38.0.2125.102 Safari/537.36', 63 | 'Mozilla/5.0 (Linux; Android 4.4.3; KFTHWI Build/KTU84M) AppleWebKit/537.36 (KHTML, like Gecko) Silk/47.1.79 like Chrome/47.0.2526.80 Safari/537.36' 64 | ].forEach(UA => { 65 | device = DeviceService.resolveDevice(UA); 66 | expect(device.type).toBe(DeviceType.TABLET); 67 | expect(device.isTablet()).toBeTruthy(); 68 | expect(device.platform).toBe(DevicePlatform.ANDROID); 69 | }); 70 | 71 | // IPad 72 | [ 73 | 'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10', 74 | 'Mozilla/5.0 (iPad; U; CPU OS 4_3_5 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8L1 Safari/6533.18.5' 75 | ].forEach(UA => { 76 | device = DeviceService.resolveDevice(UA); 77 | expect(device.type).toBe(DeviceType.TABLET); 78 | expect(device.isTablet()).toBeTruthy(); 79 | expect(device.platform).toBe(DevicePlatform.IOS); 80 | }); 81 | 82 | // Other 83 | [ 84 | 'Mozilla/5.0 (Linux; U; en-US) AppleWebKit/528.5+ (KHTML, like Gecko, Safari/528.5+) Version/4.0 Kindle/3.0 (screen 600x800; rotate)' 85 | ].forEach(UA => { 86 | device = DeviceService.resolveDevice(UA); 87 | expect(device.type).toBe(DeviceType.TABLET); 88 | expect(device.isTablet()).toBeTruthy(); 89 | expect(device.platform).toBe(DevicePlatform.UNKNOWN); 90 | }); 91 | }); 92 | 93 | it('devices is normal (Desktop)', () => { 94 | let device: Device; 95 | 96 | [ 97 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246', 98 | 'Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36', 99 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9', 100 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36', 101 | 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1', 102 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36' 103 | ].forEach(UA => { 104 | device = DeviceService.resolveDevice(UA); 105 | expect(device.type).toBe(DeviceType.NORMAL); 106 | expect(device.isNormal()).toBeTruthy(); 107 | expect(device.platform).toBe(DevicePlatform.UNKNOWN); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /projects/device/src/lib/device.service.ts: -------------------------------------------------------------------------------- 1 | import {Device, DevicePlatform, DeviceType} from './device.model'; 2 | 3 | /** 4 | * see https://github.com/spring-projects/spring-mobile 5 | */ 6 | export class DeviceService { 7 | private static KNOWN_MOBILE_USER_AGENT_PREFIXES: string[] = [ 8 | 'w3c ', 9 | 'w3c-', 10 | 'acs-', 11 | 'alav', 12 | 'alca', 13 | 'amoi', 14 | 'avan', 15 | 'benq', 16 | 'bird', 17 | 'blac', 18 | 'blaz', 19 | 'brew', 20 | 'cell', 21 | 'cldc', 22 | 'cmd-', 23 | 'dang', 24 | 'doco', 25 | 'eric', 26 | 'hipt', 27 | 'htc_', 28 | 'inno', 29 | 'ipaq', 30 | 'ipod', 31 | 'jigs', 32 | 'kddi', 33 | 'keji', 34 | 'leno', 35 | 'lg-c', 36 | 'lg-d', 37 | 'lg-g', 38 | 'lge-', 39 | 'lg/u', 40 | 'maui', 41 | 'maxo', 42 | 'midp', 43 | 'mits', 44 | 'mmef', 45 | 'mobi', 46 | 'mot-', 47 | 'moto', 48 | 'mwbp', 49 | 'nec-', 50 | 'newt', 51 | 'noki', 52 | 'palm', 53 | 'pana', 54 | 'pant', 55 | 'phil', 56 | 'play', 57 | 'port', 58 | 'prox', 59 | 'qwap', 60 | 'sage', 61 | 'sams', 62 | 'sany', 63 | 'sch-', 64 | 'sec-', 65 | 'send', 66 | 'seri', 67 | 'sgh-', 68 | 'shar', 69 | 'sie-', 70 | 'siem', 71 | 'smal', 72 | 'smar', 73 | 'sony', 74 | 'sph-', 75 | 'symb', 76 | 't-mo', 77 | 'teli', 78 | 'tim-', 79 | 'tosh', 80 | 'tsm-', 81 | 'upg1', 82 | 'upsi', 83 | 'vk-v', 84 | 'voda', 85 | 'wap-', 86 | 'wapa', 87 | 'wapi', 88 | 'wapp', 89 | 'wapr', 90 | 'webc', 91 | 'winw', 92 | 'winw', 93 | 'xda ', 94 | 'xda-' 95 | ]; 96 | 97 | private static KNOWN_MOBILE_USER_AGENT_KEYWORDS: string[] = [ 98 | 'blackberry', 99 | 'webos', 100 | 'ipod', 101 | 'lge vx', 102 | 'midp', 103 | 'maemo', 104 | 'mmp', 105 | 'mobile', 106 | 'netfront', 107 | 'hiptop', 108 | 'nintendo DS', 109 | 'novarra', 110 | 'openweb', 111 | 'opera mobi', 112 | 'opera mini', 113 | 'palm', 114 | 'psp', 115 | 'phone', 116 | 'smartphone', 117 | 'symbian', 118 | 'up.browser', 119 | 'up.link', 120 | 'wap', 121 | 'windows ce' 122 | ]; 123 | 124 | private static KNOWN_TABLET_USER_AGENT_KEYWORDS: string[] = ['ipad', 'playbook', 'hp-tablet', 'kindle']; 125 | 126 | static resolveDevice(userAgent: string): Device { 127 | if (!userAgent) { 128 | return new Device(DeviceType.NORMAL, DevicePlatform.UNKNOWN); 129 | } 130 | 131 | userAgent = userAgent.toLowerCase(); 132 | 133 | /** 134 | * Tablet Detection 135 | */ 136 | 137 | // Apple 138 | if (userAgent.includes('ipad')) { 139 | return new Device(DeviceType.TABLET, DevicePlatform.IOS); 140 | } 141 | 142 | const isMobile: boolean = 143 | userAgent.includes('mobile') || 144 | DeviceService.KNOWN_MOBILE_USER_AGENT_KEYWORDS.some(mobileUserAgent => userAgent.includes(mobileUserAgent)); 145 | if (!isMobile) { 146 | // Android 147 | if (userAgent.includes('android')) { 148 | return new Device(DeviceType.TABLET, DevicePlatform.ANDROID); 149 | } 150 | // Kindle Fire 151 | if (userAgent.includes('silk')) { 152 | return new Device(DeviceType.TABLET, DevicePlatform.UNKNOWN); 153 | } 154 | } 155 | // From keywords 156 | if (DeviceService.KNOWN_TABLET_USER_AGENT_KEYWORDS.some(tabletUserAgent => userAgent.includes(tabletUserAgent))) { 157 | return new Device(DeviceType.TABLET, DevicePlatform.UNKNOWN); 158 | } 159 | 160 | /** 161 | * Mobile detection 162 | */ 163 | // From prefix 164 | if ( 165 | userAgent.length >= 4 && 166 | DeviceService.KNOWN_MOBILE_USER_AGENT_PREFIXES.indexOf(userAgent.substring(0, 4)) !== -1 167 | ) { 168 | return new Device(DeviceType.MOBILE, DevicePlatform.UNKNOWN); 169 | } 170 | // Android 171 | if (userAgent.includes('android')) { 172 | return new Device(DeviceType.MOBILE, DevicePlatform.ANDROID); 173 | } 174 | // Apple 175 | if (userAgent.includes('iphone') || userAgent.includes('ipod') || userAgent.includes('ipad')) { 176 | return new Device(DeviceType.MOBILE, DevicePlatform.IOS); 177 | } 178 | // From keywords 179 | if (isMobile) { 180 | return new Device(DeviceType.MOBILE, DevicePlatform.UNKNOWN); 181 | } 182 | 183 | /** 184 | * => Normal device 185 | */ 186 | return new Device(DeviceType.NORMAL, DevicePlatform.UNKNOWN); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /projects/device/src/lib/device.token.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import {Device} from './device.model'; 3 | 4 | export const USER_AGENT = new InjectionToken('USER_AGENT'); 5 | export const DEVICE = new InjectionToken('DEVICE'); 6 | -------------------------------------------------------------------------------- /projects/device/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 recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /projects/device/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of device 3 | */ 4 | 5 | export {DeviceType, DevicePlatform, Device} from './lib/device.model'; 6 | export {USER_AGENT, DEVICE} from './lib/device.token'; 7 | export {DeviceModule} from './lib/device.module'; 8 | 9 | -------------------------------------------------------------------------------- /projects/device/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/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /projects/device/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 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": [ 20 | "src/test.ts", 21 | "**/*.spec.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /projects/device/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "compilationMode": "partial" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/device/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 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/logger/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*", 5 | "**/*.spec.ts" 6 | ], 7 | "overrides": [ 8 | { 9 | "files": [ 10 | "*.ts" 11 | ], 12 | "parserOptions": { 13 | "project": [ 14 | "projects/logger/tsconfig.lib.json", 15 | "projects/logger/tsconfig.spec.json" 16 | ], 17 | "createDefaultProgram": true 18 | }, 19 | "rules": { 20 | "@angular-eslint/directive-selector": [ 21 | "error", 22 | { 23 | "type": "attribute", 24 | "prefix": "lib", 25 | "style": "camelCase" 26 | } 27 | ], 28 | "@angular-eslint/component-selector": [ 29 | "error", 30 | { 31 | "type": "element", 32 | "prefix": "lib", 33 | "style": "kebab-case" 34 | } 35 | ], 36 | "@typescript-eslint/no-unsafe-member-access": "off", 37 | "@typescript-eslint/no-unsafe-assignment": "off", 38 | "@typescript-eslint/no-unsafe-call": "off", 39 | "@typescript-eslint/no-unsafe-return": "off", 40 | "@typescript-eslint/no-unsafe-argument": "off", 41 | "@typescript-eslint/no-empty-function": "off", 42 | "@typescript-eslint/no-unused-vars": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/restrict-template-expressions": "off", 45 | "@typescript-eslint/ban-types": "off" 46 | } 47 | }, 48 | { 49 | "files": [ 50 | "*.html" 51 | ], 52 | "rules": {} 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /projects/logger/README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/@ngx-toolkit/logger.svg)](https://www.npmjs.com/package/@ngx-toolkit/logger) 2 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 3 | [![Build Status](https://travis-ci.org/dewizz/ngx-toolkit.svg?branch=master)](https://travis-ci.org/dewizz/ngx-toolkit) 4 | [![Coverage](https://coveralls.io/repos/github/dewizz/ngx-toolkit/badge.svg?branch=master#5)](https://coveralls.io/github/dewizz/ngx-toolkit?branch=master) 5 | [![Join the chat at https://gitter.im/ngx-toolkit/Lobby](https://badges.gitter.im/ngx-toolkit/Lobby.svg)](https://gitter.im/ngx-toolkit/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | # @ngx-toolkit/logger 8 | > Angular LoggerService (with default `ConsoleLoggerService` implementation) 9 | 10 | # Table of contents: 11 | * [Installation](#installation) 12 | * [Usage](#usage) 13 | * [LoggerService](#loggerservice) 14 | * [Custom Implementation](#custom-implementation) 15 | * [Logger rxjs operator](#logger-rxjs-operator) 16 | * [Logger decorator](#logger-decorator) 17 | * [License](#license) 18 | 19 | --- 20 | 21 | # Installation 22 | 23 | Install the npm package. 24 | 25 | ```bash 26 | # To get the latest stable version and update package.json file: 27 | npm install @ngx-toolkit/logger --save 28 | # or 29 | yarn add @ngx-toolkit/logger 30 | ``` 31 | 32 | Import `LoggerModule` in the root Module of your application with `forRoot(level?: Level)` to choose your log level. If you don't specify a level, you haven't any log. 33 | 34 | ```typescript 35 | import { NgModule, isDevMode } from '@angular/core'; 36 | import { BrowserModule } from '@angular/platform-browser'; 37 | import { LoggerModule, Level } from '@ngx-toolkit/logger'; 38 | 39 | import { AppComponent } from './app.component'; 40 | 41 | const LOG_LEVEL: Level = isDevMode() ? Level.INFO : Level.ERROR; 42 | 43 | @NgModule({ 44 | imports: [ BrowserModule, LoggerModule.forRoot(LOG_LEVEL) ], 45 | declarations: [ AppComponent ], 46 | bootstrap: [ AppComponent ] 47 | }) 48 | export class AppModule { } 49 | ``` 50 | 51 | # Usage 52 | 53 | ```typescript 54 | import { Component, OnInit } from '@angular/core'; 55 | import { LoggerService } from '@ngx-toolkit/logger'; 56 | 57 | @Component({ 58 | selector: 'app-root', 59 | templateUrl: './app.component.html', 60 | styleUrls: ['./app.component.css'] 61 | }) 62 | export class AppComponent implements OnInit { 63 | constructor(private logger: LoggerService) {} 64 | 65 | ngOnInit() { 66 | this.logger.info('OnInit my AppCommponent'); 67 | } 68 | } 69 | ``` 70 | 71 | # LoggerService 72 | 73 | The `LoggerSerivce` API: 74 | 75 | ```typescript 76 | /** 77 | * Outputs an error message. 78 | */ 79 | error(message?: any, ...optionalParams: any[]) {} 80 | 81 | /** 82 | * Outputs a warning message. 83 | */ 84 | warn(message?: any, ...optionalParams: any[]) {} 85 | 86 | /** 87 | * Outputs an informational message. 88 | */ 89 | info(message?: any, ...optionalParams: any[]) {} 90 | 91 | /** 92 | * Outputs a debug message. 93 | */ 94 | debug(message?: any, ...optionalParams: any[]) {} 95 | 96 | /** 97 | * Outputs a message. 98 | */ 99 | log(message?: any, ...optionalParams: any[]) {} 100 | ``` 101 | 102 | # Custom implementation 103 | 104 | You can create you own implementation. Our sample with the [winston](https://github.com/winstonjs/winston) API: 105 | 106 | ```typescript 107 | import { Inject, Injectable } from '@angular/core'; 108 | import { LoggerService, LOGGER_LEVEL, Level } from '@ngx-toolkit/logger'; 109 | import winston from 'winston'; 110 | 111 | @Injectable() 112 | export class WinstonLoggerService extends LoggerService { 113 | private logger: any; 114 | 115 | constructor(@Inject(LOGGER_LEVEL) level: Level) { 116 | super(); 117 | 118 | this.logger = winston.createLogger({ 119 | transports: [ 120 | new winston.transports.File({ 121 | filename: 'combined.log', 122 | level: 'info' 123 | }) 124 | ] 125 | }); 126 | } 127 | 128 | info(message?: any, ...optionalParams: any[]) { 129 | this.logger.info(message, optionalParams); 130 | } 131 | 132 | ... 133 | } 134 | ``` 135 | 136 | To register WinstonLoggerService in the Angular application, provide the `LoggerModule` with `forRoot(level?: Level, provider?: Type,)` as: 137 | 138 | ```typescript 139 | import { NgModule, isDevMode } from '@angular/core'; 140 | import { BrowserModule } from '@angular/platform-browser'; 141 | import { LoggerModule, Level } from '@ngx-toolkit/logger'; 142 | 143 | import { WinstonLoggerService } from './winston-logger.service'; 144 | import { AppComponent } from './app.component'; 145 | 146 | const LOG_LEVEL: Level = isDevMode() ? Level.INFO : Level.ERROR; 147 | 148 | @NgModule({ 149 | imports: [ BrowserModule, LoggerModule.forRoot(LOG_LEVEL, WinstonLoggerService) ], 150 | declarations: [ AppComponent ], 151 | bootstrap: [ AppComponent ] 152 | }) 153 | export class AppModule { } 154 | ``` 155 | 156 | # Logger rxjs operator 157 | 158 | - logger(message: string, 159 | nextLevel: Level = Level.INFO, 160 | errorLevel: Level = Level.ERROR, 161 | completeLevel?: Level): MonoTypeOperatorFunction 162 | 163 | ```typescript 164 | import { Component, OnInit } from '@angular/core'; 165 | import { logger } from '@ngx-toolkit/logger'; 166 | import { timer } from 'rxjs'; 167 | 168 | @Component({ 169 | selector: 'app-root', 170 | templateUrl: './app.component.html', 171 | styleUrls: ['./app.component.css'] 172 | }) 173 | export class AppComponent implements OnInit { 174 | constructor() {} 175 | 176 | ngOnInit() { 177 | timer(1000, 2000).pipe( 178 | logger('timer') 179 | ).subscribe(); 180 | } 181 | } 182 | ``` 183 | 184 | # Logger decorator 185 | 186 | - Log(message?: string, level: Level = Level.INFO) 187 | - Debug(message?: string) : Alias of Log(message?: string, Level.DEBUG) 188 | 189 | ```typescript 190 | import { Component, OnInit } from '@angular/core'; 191 | import { Debug } from '@ngx-toolkit/logger'; 192 | 193 | @Component({ 194 | selector: 'app-root', 195 | templateUrl: './app.component.html', 196 | styleUrls: ['./app.component.css'] 197 | }) 198 | export class AppComponent implements OnInit { 199 | constructor() {} 200 | 201 | @Debug() 202 | action(param: string) { 203 | return "result"; 204 | } 205 | } 206 | ``` 207 | 208 | ---- 209 | 210 | # License 211 | © 2018 Dewizz 212 | 213 | [MIT](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 214 | -------------------------------------------------------------------------------- /projects/logger/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'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/logger'), 29 | subdir: '.', 30 | reporters: [ 31 | {type: 'html'}, 32 | {type: 'lcovonly'}, 33 | {type: 'text-summary'} 34 | ] 35 | }, 36 | reporters: ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessCI'], 42 | customLaunchers: { 43 | ChromeHeadlessCI: { 44 | base: 'ChromeHeadless', 45 | flags: ['--no-sandbox'] 46 | } 47 | }, 48 | singleRun: false, 49 | restartOnFileChange: true 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /projects/logger/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/logger", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-toolkit/logger", 3 | "version": "0.0.0-PLACEHOLDER", 4 | "description": "Angular logger abstraction", 5 | "homepage": "https://github.com/dewizz/ngx-toolkit", 6 | "license": "MIT", 7 | "author": "Dewizz", 8 | "keywords": [ 9 | "angular", 10 | "ngx", 11 | "ngx-toolkit", 12 | "log", 13 | "logger" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/dewizz/ngx-toolkit" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/dewizz/ngx-toolkit/issues" 21 | }, 22 | "dependencies": { 23 | "tslib": "^2.0.0" 24 | }, 25 | "peerDependencies": { 26 | "rxjs": "^6.5.0", 27 | "@angular/core": "^13.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /projects/logger/src/lib/console-logger.service.spec.ts: -------------------------------------------------------------------------------- 1 | import {ConsoleLoggerService} from './console-logger.service'; 2 | import {Level} from './level.model'; 3 | import {LoggerService} from './logger.service'; 4 | 5 | describe('ConsoleLoggerService', () => { 6 | let service: LoggerService; 7 | const toSpy: any = { 8 | log: function(message?: any, ...optionalParams: any[]) { 9 | // NOTHING 10 | } 11 | }; 12 | 13 | beforeEach(() => { 14 | // Override console 15 | ['error', 'warn', 'info', 'debug', 'log'].forEach(level => { 16 | console[level] = (...args: any[]) => { 17 | toSpy.log.apply(toSpy, args); 18 | }; 19 | }); 20 | 21 | spyOn(toSpy, 'log'); 22 | }); 23 | 24 | it('check console logger called', () => { 25 | service = new ConsoleLoggerService(Level.LOG); 26 | 27 | const consoleArgs: any = 'args'; 28 | 29 | service.error('error', consoleArgs); 30 | expect(toSpy.log).toHaveBeenCalledWith('error', consoleArgs); 31 | 32 | service.warn('warn', consoleArgs); 33 | expect(toSpy.log).toHaveBeenCalledWith('warn', consoleArgs); 34 | 35 | service.info('info', consoleArgs); 36 | expect(toSpy.log).toHaveBeenCalledWith('info', consoleArgs); 37 | 38 | service.debug('debug', consoleArgs); 39 | expect(toSpy.log).toHaveBeenCalledWith('debug', consoleArgs); 40 | 41 | service.log('log', consoleArgs); 42 | expect(toSpy.log).toHaveBeenCalledWith('log', consoleArgs); 43 | }); 44 | 45 | it('check console logger level', () => { 46 | service = new ConsoleLoggerService(Level.WARN); 47 | 48 | service.error('error'); 49 | expect(toSpy.log).toHaveBeenCalledWith('error'); 50 | 51 | service.warn('warn'); 52 | expect(toSpy.log).toHaveBeenCalledWith('warn'); 53 | 54 | service.info('info'); 55 | expect(toSpy.log).not.toHaveBeenCalledWith('info'); 56 | 57 | service.debug('debug'); 58 | expect(toSpy.log).not.toHaveBeenCalledWith('debug'); 59 | 60 | service.log('log'); 61 | expect(toSpy.log).not.toHaveBeenCalledWith('log'); 62 | }); 63 | 64 | it('check console logger logLevel', () => { 65 | service = new ConsoleLoggerService(Level.LOG); 66 | 67 | const args: any = {data: {foo: 'bar'}}; 68 | service.logLevel(Level.INFO, 'test', args); 69 | expect(toSpy.log).toHaveBeenCalledWith('test', args); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /projects/logger/src/lib/console-logger.service.ts: -------------------------------------------------------------------------------- 1 | import {Inject, Injectable, Optional} from '@angular/core'; 2 | import {Level} from './level.model'; 3 | import {LOGGER_LEVEL} from './level.token'; 4 | import {LoggerService} from './logger.service'; 5 | 6 | @Injectable() 7 | export class ConsoleLoggerService extends LoggerService { 8 | constructor(@Optional() @Inject(LOGGER_LEVEL) level: Level) { 9 | super(); 10 | 11 | if (level) { 12 | Object.keys(Level) 13 | .filter(s => isNaN(+s) && level >= Level[s]) 14 | .forEach(levelName => { 15 | const methodName: string = levelName.toLowerCase(); 16 | this[methodName] = console[methodName].bind(console); 17 | }); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /projects/logger/src/lib/level.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Log levels 3 | */ 4 | export enum Level { 5 | ERROR, 6 | WARN, 7 | INFO, 8 | DEBUG, 9 | LOG 10 | } 11 | -------------------------------------------------------------------------------- /projects/logger/src/lib/level.token.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import {Level} from './level.model'; 3 | 4 | export const LOGGER_LEVEL = new InjectionToken('LOGGER_LEVEL'); 5 | -------------------------------------------------------------------------------- /projects/logger/src/lib/logger.decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import {ConsoleLoggerService} from './console-logger.service'; 2 | import {Level} from './level.model'; 3 | import {Debug, LOGGER_DECORATOR_DATA} from './logger.decorator'; 4 | 5 | describe('LoggerDecorator', () => { 6 | beforeEach(() => { 7 | LOGGER_DECORATOR_DATA.loggerService = new ConsoleLoggerService(Level.DEBUG); 8 | 9 | spyOn(LOGGER_DECORATOR_DATA.loggerService, 'debug'); 10 | }); 11 | 12 | it('should work', () => { 13 | class Test { 14 | @Debug('increment') 15 | toto(n: number): number { 16 | return n + 1; 17 | } 18 | } 19 | 20 | const test: Test = new Test(); 21 | expect(test.toto(1)).toBe(2); 22 | expect(LOGGER_DECORATOR_DATA.loggerService.debug).toHaveBeenCalledWith('increment', [1], 2); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /projects/logger/src/lib/logger.decorator.ts: -------------------------------------------------------------------------------- 1 | import {Level} from './level.model'; 2 | import {LoggerService} from './logger.service'; 3 | 4 | export interface LoggerDecoratorData { 5 | loggerService?: LoggerService; 6 | } 7 | 8 | export const LOGGER_DECORATOR_DATA: LoggerDecoratorData = {}; 9 | 10 | export function Debug(message?: string): MethodDecorator { 11 | return Log(message, Level.DEBUG); 12 | } 13 | 14 | export function Log(message?: string, level: Level = Level.INFO): MethodDecorator { 15 | return (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 16 | const originalMethod = descriptor.value; 17 | descriptor.value = function(...args: any[]) { 18 | const result = originalMethod.apply(this, args); 19 | if (LOGGER_DECORATOR_DATA.loggerService) { 20 | LOGGER_DECORATOR_DATA.loggerService.logLevel(level, message || `Call ${propertyKey.toString()}`, args, result); 21 | } 22 | return result; 23 | }; 24 | return descriptor; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /projects/logger/src/lib/logger.module.spec.ts: -------------------------------------------------------------------------------- 1 | import {ApplicationInitStatus} from '@angular/core'; 2 | import {TestBed} from '@angular/core/testing'; 3 | import {ConsoleLoggerService} from './console-logger.service'; 4 | import {Level} from './level.model'; 5 | import {LOGGER_DECORATOR_DATA} from './logger.decorator'; 6 | import {LoggerModule} from './logger.module'; 7 | import {LoggerService} from './logger.service'; 8 | 9 | describe('LoggerModule', () => { 10 | it('should work', () => { 11 | expect(new LoggerModule()).toBeDefined(); 12 | }); 13 | 14 | it('should instantiate service', () => { 15 | TestBed.configureTestingModule({imports: [LoggerModule.forRoot(Level.LOG, ConsoleLoggerService)]}); 16 | 17 | const service: LoggerService = TestBed.get(LoggerService); 18 | expect(service instanceof ConsoleLoggerService).toBeTruthy(); 19 | }); 20 | 21 | it('should configure LOGGER_DECORATOR_DATA', async (done: DoneFn) => { 22 | await TestBed.configureTestingModule({ 23 | imports: [LoggerModule.forRoot()] 24 | }) 25 | // https://github.com/angular/angular/issues/24218 26 | .get(ApplicationInitStatus).donePromise; 27 | 28 | expect(LOGGER_DECORATOR_DATA.loggerService).toBeDefined(); 29 | done(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /projects/logger/src/lib/logger.module.ts: -------------------------------------------------------------------------------- 1 | import {APP_INITIALIZER, ModuleWithProviders, NgModule, Type} from '@angular/core'; 2 | import {ConsoleLoggerService} from './console-logger.service'; 3 | import {Level} from './level.model'; 4 | import {LOGGER_LEVEL} from './level.token'; 5 | import {LOGGER_DECORATOR_DATA} from './logger.decorator'; 6 | import {LoggerService} from './logger.service'; 7 | 8 | export function setupLoggerDecorator(loggerService: LoggerService) { 9 | LOGGER_DECORATOR_DATA.loggerService = loggerService; 10 | return () => null; 11 | } 12 | 13 | @NgModule() 14 | export class LoggerModule { 15 | static forRoot(level: Level = null, provider: Type = ConsoleLoggerService): ModuleWithProviders { 16 | return { 17 | ngModule: LoggerModule, 18 | providers: [ 19 | { 20 | provide: LOGGER_LEVEL, 21 | useValue: level 22 | }, 23 | { 24 | provide: LoggerService, 25 | useClass: provider 26 | }, 27 | { 28 | provide: APP_INITIALIZER, 29 | useFactory: setupLoggerDecorator, 30 | deps: [LoggerService], 31 | multi: true 32 | } 33 | ] 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /projects/logger/src/lib/logger.rxjs.ts: -------------------------------------------------------------------------------- 1 | import {MonoTypeOperatorFunction} from 'rxjs'; 2 | import {tap} from 'rxjs/operators'; 3 | import {Level} from './level.model'; 4 | import {LOGGER_DECORATOR_DATA} from './logger.decorator'; 5 | 6 | export function logger(message: string, 7 | nextLevel: Level = Level.INFO, 8 | errorLevel: Level = Level.ERROR, 9 | completeLevel?: Level): MonoTypeOperatorFunction { 10 | return tap(x => { 11 | if (LOGGER_DECORATOR_DATA.loggerService) { 12 | LOGGER_DECORATOR_DATA.loggerService.logLevel(nextLevel, message, x); 13 | } 14 | }, e => { 15 | if (LOGGER_DECORATOR_DATA.loggerService) { 16 | LOGGER_DECORATOR_DATA.loggerService.logLevel(errorLevel, message, e); 17 | } 18 | }, () => { 19 | if (LOGGER_DECORATOR_DATA.loggerService && completeLevel) { 20 | LOGGER_DECORATOR_DATA.loggerService.logLevel(completeLevel, message); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /projects/logger/src/lib/logger.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Level} from './level.model'; 3 | 4 | @Injectable() 5 | export class LoggerService { 6 | 7 | /** 8 | * Outputs an error message. 9 | */ 10 | error(message?: any, ...optionalParams: any[]) { 11 | } 12 | 13 | /** 14 | * Outputs a warning message. 15 | */ 16 | warn(message?: any, ...optionalParams: any[]) { 17 | } 18 | 19 | /** 20 | * Outputs an informational message. 21 | */ 22 | info(message?: any, ...optionalParams: any[]) { 23 | } 24 | 25 | /** 26 | * Outputs a debug message. 27 | */ 28 | debug(message?: any, ...optionalParams: any[]) { 29 | } 30 | 31 | /** 32 | * Outputs a message. 33 | */ 34 | log(message?: any, ...optionalParams: any[]) { 35 | } 36 | 37 | /** 38 | * Outputs a message. 39 | */ 40 | logLevel(level: Level, message?: any, ...optionalParams: any[]) { 41 | switch (level) { 42 | case Level.ERROR : 43 | this.error(message, ...optionalParams); 44 | break; 45 | case Level.WARN : 46 | this.warn(message, ...optionalParams); 47 | break; 48 | case Level.INFO : 49 | this.info(message, ...optionalParams); 50 | break; 51 | case Level.DEBUG : 52 | this.debug(message, ...optionalParams); 53 | break; 54 | case Level.LOG : 55 | this.log(message, ...optionalParams); 56 | break; 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /projects/logger/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 recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /projects/logger/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of logger 3 | */ 4 | 5 | export {Level} from './lib/level.model'; 6 | export {LOGGER_LEVEL} from './lib/level.token'; 7 | export {LoggerService} from './lib/logger.service'; 8 | export {ConsoleLoggerService} from './lib/console-logger.service'; 9 | export {LoggerModule} from './lib/logger.module'; 10 | export {Log, Debug} from './lib/logger.decorator'; 11 | export {logger} from './lib/logger.rxjs'; 12 | -------------------------------------------------------------------------------- /projects/logger/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/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /projects/logger/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 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": [ 20 | "src/test.ts", 21 | "**/*.spec.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /projects/logger/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "compilationMode": "partial" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/logger/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 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/spring/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*", 5 | "**/*.spec.ts" 6 | ], 7 | "overrides": [ 8 | { 9 | "files": [ 10 | "*.ts" 11 | ], 12 | "parserOptions": { 13 | "project": [ 14 | "projects/spring/tsconfig.lib.json", 15 | "projects/spring/tsconfig.spec.json" 16 | ], 17 | "createDefaultProgram": true 18 | }, 19 | "rules": { 20 | "@angular-eslint/directive-selector": [ 21 | "error", 22 | { 23 | "type": "attribute", 24 | "prefix": "lib", 25 | "style": "camelCase" 26 | } 27 | ], 28 | "@angular-eslint/component-selector": [ 29 | "error", 30 | { 31 | "type": "element", 32 | "prefix": "lib", 33 | "style": "kebab-case" 34 | } 35 | ], 36 | "@typescript-eslint/no-unsafe-member-access": "off", 37 | "@typescript-eslint/no-unsafe-assignment": "off", 38 | "@typescript-eslint/no-unsafe-call": "off", 39 | "@typescript-eslint/no-unsafe-return": "off", 40 | "@typescript-eslint/no-unsafe-argument": "off", 41 | "@typescript-eslint/no-empty-function": "off", 42 | "@typescript-eslint/no-unused-vars": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/restrict-template-expressions": "off", 45 | "@typescript-eslint/ban-types": "off" 46 | } 47 | }, 48 | { 49 | "files": [ 50 | "*.html" 51 | ], 52 | "rules": {} 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /projects/spring/README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/@ngx-toolkit/spring.svg)](https://www.npmjs.com/package/@ngx-toolkit/spring) 2 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 3 | [![Build Status](https://travis-ci.org/dewizz/ngx-toolkit.svg?branch=master)](https://travis-ci.org/dewizz/ngx-toolkit) 4 | [![Coverage](https://coveralls.io/repos/github/dewizz/ngx-toolkit/badge.svg?branch=master#5)](https://coveralls.io/github/dewizz/ngx-toolkit?branch=master) 5 | [![Join the chat at https://gitter.im/ngx-toolkit/Lobby](https://badges.gitter.im/ngx-toolkit/Lobby.svg)](https://gitter.im/ngx-toolkit/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | # @ngx-toolkit/spring 8 | > Angular Spring utilities 9 | 10 | # Table of contents: 11 | * [Installation](#installation) 12 | * [Spring Data](#spring-data) 13 | * [License](#license) 14 | 15 | --- 16 | 17 | # Installation 18 | 19 | Install the npm package. 20 | 21 | ```bash 22 | # To get the latest stable version and update package.json file: 23 | npm install @ngx-toolkit/spring --save 24 | # or 25 | yarn add @ngx-toolkit/spring 26 | ``` 27 | 28 | # Spring Data 29 | 30 | Some [Spring Data Commons](https://github.com/spring-projects/spring-data-commons) classes: 31 | 32 | - `Page` type 33 | - `Sort(orders: string | string[] | Order[], direction: Direction = 'ASC')` 34 | - `PageRequest(page: number = 0, size: number = 20, sort?: Sort)` with `toHttpParams(options: { 35 | fromString?: string; 36 | fromObject?: { 37 | [param: string]: string | string[]; 38 | }; 39 | encoder?: HttpParameterCodec; 40 | } = {} as { 41 | fromString?: string; 42 | fromObject?: { 43 | [param: string]: string | string[]; 44 | }; 45 | encoder?: HttpParameterCodec; 46 | })` method to convert value in request params 47 | 48 | ---- 49 | 50 | # License 51 | © 2018 Dewizz 52 | 53 | [MIT](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 54 | -------------------------------------------------------------------------------- /projects/spring/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'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/spring'), 29 | subdir: '.', 30 | reporters: [ 31 | {type: 'html'}, 32 | {type: 'lcovonly'}, 33 | {type: 'text-summary'} 34 | ] 35 | }, 36 | reporters: ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessCI'], 42 | customLaunchers: { 43 | ChromeHeadlessCI: { 44 | base: 'ChromeHeadless', 45 | flags: ['--no-sandbox'] 46 | } 47 | }, 48 | singleRun: false, 49 | restartOnFileChange: true 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /projects/spring/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/spring", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/spring/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-toolkit/spring", 3 | "version": "0.0.0-PLACEHOLDER", 4 | "description": "Angular spring utilities", 5 | "homepage": "https://github.com/dewizz/ngx-toolkit", 6 | "license": "MIT", 7 | "author": "Dewizz", 8 | "keywords": [ 9 | "angular", 10 | "ngx", 11 | "ngx-toolkit", 12 | "spring", 13 | "spring-data" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/dewizz/ngx-toolkit" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/dewizz/ngx-toolkit/issues" 21 | }, 22 | "dependencies": { 23 | "tslib": "^2.0.0" 24 | }, 25 | "peerDependencies": { 26 | "@angular/common": "^13.2.0", 27 | "@angular/core": "^13.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /projects/spring/src/lib/spring-data.model.ts: -------------------------------------------------------------------------------- 1 | import {HttpParams, HttpParamsOptions} from '@angular/common/http'; 2 | 3 | export const DEFAULT_PAGEABLE_SIZE = 25; 4 | 5 | export class PageRequest { 6 | readonly page: number; 7 | readonly size: number; 8 | readonly sort: Sort; 9 | 10 | constructor(page = 0, size: number = DEFAULT_PAGEABLE_SIZE, sort?: ISort) { 11 | this.page = page; 12 | this.size = size; 13 | this.sort = sort; 14 | } 15 | 16 | toHttpParams( 17 | options: HttpParamsOptions = {} 18 | ): HttpParams { 19 | let params: HttpParams = new HttpParams(options) 20 | // Add page & size 21 | .set('page', `${this.page}`) 22 | .set('size', `${this.size}`); 23 | 24 | // Add orders 25 | if (this.sort && this.sort.orders) { 26 | const groupedSort: { [key in Direction]: string[] } = this.sort.orders.reduce( 27 | (groupedProperty: any, order: Order) => { 28 | groupedProperty[order.direction] = groupedProperty[order.direction] || []; 29 | groupedProperty[order.direction].push(order.property); 30 | return groupedProperty; 31 | }, 32 | {} as { [key in Direction]: string[] } 33 | ); 34 | 35 | Object.keys(groupedSort).forEach(direction => { 36 | params = params.append('sort', `${groupedSort[direction].join(',')},${direction}`); 37 | }); 38 | } 39 | 40 | return params; 41 | } 42 | } 43 | 44 | export interface Page { 45 | // the total amount of elements 46 | totalElements: number; 47 | // the number of total pages 48 | totalPages: number; 49 | // the page content 50 | content: T[]; 51 | // the number of the current Slice 52 | number: number; 53 | // the number of elements currently on this Slice 54 | numberOfElements: number; 55 | // the size of the Slice 56 | size: number; 57 | // the sorting parameters for the Slice 58 | sort: Sort; 59 | // whether the current Slice is the first one 60 | first: boolean; 61 | // whether the current Slice is the last one 62 | last: boolean; 63 | } 64 | 65 | export type Direction = 'ASC' | 'DESC'; 66 | export type NullHandling = 'NATIVE' | 'NULLS_FIRST' | 'NULLS_LAST'; 67 | export const DEFAULT_DIRECTION: Direction = 'ASC'; 68 | 69 | export class Sort implements ISort { 70 | readonly orders: Order[]; 71 | 72 | constructor(orders: string | string[] | Order[], direction: Direction = DEFAULT_DIRECTION) { 73 | if (orders) { 74 | if (typeof orders === 'string') { 75 | this.orders = [ 76 | { 77 | property: orders , 78 | direction 79 | } 80 | ]; 81 | } else if (orders.length > 0) { 82 | if (typeof orders[0] === 'string') { 83 | this.orders = (orders as string[]).map(property => { 84 | return { 85 | property, 86 | direction 87 | }; 88 | }); 89 | } else { 90 | this.orders = orders as Order[]; 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | export interface ISort { 98 | orders: Order[]; 99 | } 100 | 101 | export interface Order { 102 | property: string; 103 | direction: Direction; 104 | nullHandlingHint?: NullHandling; 105 | } 106 | -------------------------------------------------------------------------------- /projects/spring/src/lib/spring-data.spec.ts: -------------------------------------------------------------------------------- 1 | import {DEFAULT_DIRECTION, DEFAULT_PAGEABLE_SIZE, Order, PageRequest, Sort} from './spring-data.model'; 2 | 3 | describe('Spring Data', () => { 4 | 5 | describe('Sort', () => { 6 | it('should works with string property', () => { 7 | const sort = new Sort('property'); 8 | expect(sort.orders.length).toBe(1); 9 | expect(sort.orders[0].property).toBe('property'); 10 | expect(sort.orders[0].direction).toBe(DEFAULT_DIRECTION); 11 | }); 12 | 13 | it('should works with string array properties', () => { 14 | const sort = new Sort(['property1', 'property2']); 15 | expect(sort.orders.length).toBe(2); 16 | expect(sort.orders[0].property).toBe('property1'); 17 | expect(sort.orders[0].direction).toBe(DEFAULT_DIRECTION); 18 | }); 19 | 20 | it('should works with order array properties', () => { 21 | const order: Order = {property: 'property', direction: 'DESC'}; 22 | const sort = new Sort([order], 'DESC'); 23 | expect(sort.orders.length).toBe(1); 24 | expect(sort.orders[0]).toBe(order); 25 | }); 26 | }); 27 | 28 | describe('PageRequest', () => { 29 | it('should works without param', () => { 30 | const pageRequest = new PageRequest(); 31 | const httpParams = pageRequest.toHttpParams(); 32 | expect(httpParams.toString()).toBe('page=0&size=25'); 33 | }); 34 | 35 | it('should works with default http param', () => { 36 | const pageRequest = new PageRequest(1, 50); 37 | const httpParams = pageRequest.toHttpParams({ 38 | fromObject: { 39 | fakeParam: 'fakeValue' 40 | } 41 | }); 42 | expect(httpParams).toBeDefined(); 43 | expect(httpParams.has('fakeParam')).toBeTrue(); 44 | expect(httpParams.toString()).toBe('fakeParam=fakeValue&page=1&size=50'); 45 | }); 46 | 47 | it('should works with sort param', () => { 48 | const pageRequest = new PageRequest(0, DEFAULT_PAGEABLE_SIZE, new Sort('sortProperty')); 49 | const httpParams = pageRequest.toHttpParams(); 50 | expect(httpParams).toBeDefined(); 51 | expect(httpParams.toString()).toBe('page=0&size=25&sort=sortProperty,ASC'); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /projects/spring/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 recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /projects/spring/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of spring 3 | */ 4 | 5 | export * from './lib/spring-data.model'; 6 | 7 | -------------------------------------------------------------------------------- /projects/spring/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/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /projects/spring/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 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": [ 20 | "src/test.ts", 21 | "**/*.spec.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /projects/spring/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "compilationMode": "partial" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/spring/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 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/utils/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*", 5 | "**/*.spec.ts" 6 | ], 7 | "overrides": [ 8 | { 9 | "files": [ 10 | "*.ts" 11 | ], 12 | "parserOptions": { 13 | "project": [ 14 | "projects/utils/tsconfig.lib.json", 15 | "projects/utils/tsconfig.spec.json" 16 | ], 17 | "createDefaultProgram": true 18 | }, 19 | "rules": { 20 | "@angular-eslint/directive-selector": [ 21 | "error", 22 | { 23 | "type": "attribute", 24 | "prefix": "lib", 25 | "style": "camelCase" 26 | } 27 | ], 28 | "@angular-eslint/component-selector": [ 29 | "error", 30 | { 31 | "type": "element", 32 | "prefix": "lib", 33 | "style": "kebab-case" 34 | } 35 | ], 36 | "@typescript-eslint/no-unsafe-member-access": "off", 37 | "@typescript-eslint/no-unsafe-assignment": "off", 38 | "@typescript-eslint/no-unsafe-call": "off", 39 | "@typescript-eslint/no-unsafe-return": "off", 40 | "@typescript-eslint/no-unsafe-argument": "off", 41 | "@typescript-eslint/no-empty-function": "off", 42 | "@typescript-eslint/no-unused-vars": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/restrict-template-expressions": "off", 45 | "@typescript-eslint/ban-types": "off" 46 | } 47 | }, 48 | { 49 | "files": [ 50 | "*.html" 51 | ], 52 | "rules": {} 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /projects/utils/README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/@ngx-toolkit/utils.svg)](https://www.npmjs.com/package/@ngx-toolkit/utils) 2 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 3 | [![Build Status](https://travis-ci.org/dewizz/ngx-toolkit.svg?branch=master)](https://travis-ci.org/dewizz/ngx-toolkit) 4 | [![Coverage](https://coveralls.io/repos/github/dewizz/ngx-toolkit/badge.svg?branch=master#5)](https://coveralls.io/github/dewizz/ngx-toolkit?branch=master) 5 | [![Join the chat at https://gitter.im/ngx-toolkit/Lobby](https://badges.gitter.im/ngx-toolkit/Lobby.svg)](https://gitter.im/ngx-toolkit/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | # @ngx-toolkit/utils 8 | > Angular common utilities 9 | 10 | # Table of contents: 11 | * [Installation](#installation) 12 | * [Queue](#queue) 13 | * [Wait](#wait) 14 | * [Once](#once) 15 | * [License](#license) 16 | 17 | --- 18 | 19 | # Installation 20 | 21 | Install the npm package. 22 | 23 | ```bash 24 | # To get the latest stable version and update package.json file: 25 | npm install @ngx-toolkit/utils --save 26 | # or 27 | yarn add @ngx-toolkit/utils 28 | ``` 29 | 30 | # Queue 31 | 32 | Queue annotation: 33 | 34 | ```typescript 35 | import { Queue } from '@ngx-toolkit/utils'; 36 | ... 37 | 38 | class MyComponent { 39 | /** 40 | * Put the method call in a queue and wait for a Promise / Subscription / method execution 41 | * /!\ the method result is modified => Return a Promise 42 | * @param {number} queue limit (default: no limit) 43 | * @param {string} queue name (default: method name) 44 | */ 45 | @Queue(limit?: number, name?: string) 46 | method(): Promise | Subscription | any | void; 47 | } 48 | ``` 49 | 50 | # Wait 51 | 52 | Wait annotation (shortcut of @Queue(1)): 53 | 54 | ```typescript 55 | import { Wait } from '@ngx-toolkit/utils'; 56 | ... 57 | 58 | class MyComponent { 59 | /** 60 | * Wait for a Promise / Subscription before to be re-executed 61 | * /!\ the method result is modified => Return a Promise 62 | * @param {string} wait name (default: method name) 63 | */ 64 | @Wait(name?: string) 65 | method(): Promise | Subscription | any | void; 66 | } 67 | ``` 68 | 69 | # Once 70 | 71 | Once annotation: 72 | 73 | ```typescript 74 | import { Once } from '@ngx-toolkit/utils'; 75 | ... 76 | 77 | class MyComponent { 78 | /** 79 | * mark a method to be executed no more than once even if called several times 80 | * @param {string} name (default: method name) 81 | */ 82 | @Once(name?: string) 83 | method(): Promise | Subscription | any | void; 84 | } 85 | ``` 86 | 87 | ---- 88 | 89 | # License 90 | © 2018 Dewizz 91 | 92 | [MIT](https://github.com/dewizz/ngx-toolkit/blob/master/LICENSE) 93 | -------------------------------------------------------------------------------- /projects/utils/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'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/utils'), 29 | subdir: '.', 30 | reporters: [ 31 | {type: 'html'}, 32 | {type: 'lcovonly'}, 33 | {type: 'text-summary'} 34 | ] 35 | }, 36 | reporters: ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessCI'], 42 | customLaunchers: { 43 | ChromeHeadlessCI: { 44 | base: 'ChromeHeadless', 45 | flags: ['--no-sandbox'] 46 | } 47 | }, 48 | singleRun: false, 49 | restartOnFileChange: true 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /projects/utils/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/utils", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-toolkit/utils", 3 | "version": "0.0.0-PLACEHOLDER", 4 | "description": "Angular common utilities", 5 | "homepage": "https://github.com/dewizz/ngx-toolkit", 6 | "license": "MIT", 7 | "author": "Dewizz", 8 | "keywords": [ 9 | "angular", 10 | "ngx", 11 | "ngx-toolkit", 12 | "annotation", 13 | "decorator", 14 | "utils", 15 | "utilities" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/dewizz/ngx-toolkit" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/dewizz/ngx-toolkit/issues" 23 | }, 24 | "dependencies": { 25 | "tslib": "^2.0.0" 26 | }, 27 | "peerDependencies": { 28 | "@angular/core": "^13.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /projects/utils/src/lib/functions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Deferred 3 | */ 4 | export class Deferred { 5 | resolve: (value?: T | PromiseLike) => void; 6 | reject: (reason?: any) => void; 7 | promise: Promise; 8 | 9 | constructor() { 10 | this.promise = new Promise((resolve, reject) => { 11 | this.resolve = resolve; 12 | this.reject = reject; 13 | }); 14 | 15 | Object.freeze(> this); 16 | } 17 | } 18 | 19 | /** 20 | * Determine if the argument is shaped like a Promise 21 | */ 22 | export function isPromise(obj: any): obj is Promise { 23 | return !!obj && typeof obj.then === 'function'; 24 | } 25 | -------------------------------------------------------------------------------- /projects/utils/src/lib/once.decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import {Once} from './once.decorator'; 2 | 3 | describe('OnceDecorator', () => { 4 | it('should executed once', () => { 5 | class Test { 6 | called = 0; 7 | 8 | @Once('name') 9 | func1() { 10 | this.called++; 11 | } 12 | 13 | @Once('name') 14 | func2() { 15 | this.called++; 16 | } 17 | } 18 | 19 | const test: Test = new Test(); 20 | 21 | test.func1(); 22 | test.func1(); 23 | test.func2(); 24 | 25 | expect(test.called).toEqual(1); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /projects/utils/src/lib/once.decorator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * mark a method to be executed no more than once even if called several times 3 | */ 4 | export function Once(name?: string): MethodDecorator { 5 | return function(target: Object, 6 | propertyKey: string | symbol, 7 | descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor { 8 | 9 | // Init 10 | name = name || propertyKey.toString(); 11 | const originalMethod = descriptor.value; 12 | 13 | descriptor.value = function(...args: any[]) { 14 | if (!target[`_ngxQueue_${name}`]) { 15 | target[`_ngxQueue_${name}`] = true; 16 | return originalMethod.apply(this, args); 17 | } 18 | }; 19 | 20 | return descriptor; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /projects/utils/src/lib/queue.decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import {Observable, Subscription, throwError, timer} from 'rxjs'; 2 | import {concatMap, delay, tap} from 'rxjs/operators'; 3 | import {Queue} from './queue.decorator'; 4 | 5 | const WAIT_TIME = 50; 6 | const QUEUE_NUMBER = 2; 7 | 8 | class ClassTest { 9 | incr = 0; 10 | 11 | get waitObs(): Observable { 12 | return timer(WAIT_TIME); 13 | } 14 | 15 | @Queue(QUEUE_NUMBER) 16 | obsSuccessFunc(incr = 1): Subscription { 17 | return this.waitObs 18 | .subscribe(() => this.plus(incr)); 19 | } 20 | 21 | @Queue(QUEUE_NUMBER) 22 | obsErrorFunc(incr = 1): Subscription { 23 | return this.waitObs.pipe( 24 | concatMap(() => throwError('error')) 25 | ) 26 | .subscribe(() => { 27 | }, () => this.plus(incr)); 28 | } 29 | 30 | @Queue(QUEUE_NUMBER) 31 | promiseSuccessFunc(incr = 1): Promise { 32 | return this.waitObs.toPromise().then(() => this.plus(incr)); 33 | } 34 | 35 | @Queue(QUEUE_NUMBER) 36 | promiseErrorFunc(incr = 1): Promise { 37 | return this.promiseSuccessFunc().then(() => { 38 | throw new Error('error'); 39 | }); 40 | } 41 | 42 | @Queue(QUEUE_NUMBER) 43 | classicSuccessFunc(incr = 1): number { 44 | return this.plus(incr); 45 | } 46 | 47 | @Queue(QUEUE_NUMBER) 48 | classicErrorFunc(incr = 1): void { 49 | this.plus(incr); 50 | throw new Error('error'); 51 | } 52 | 53 | private plus(n: number): number { 54 | this.incr += n; 55 | return this.incr; 56 | } 57 | } 58 | 59 | function testIt(classTest: ClassTest, fn: Function, done: DoneFn, firstPassValue: number = 2, secondPassValue: number = 3) { 60 | fn.apply(classTest); 61 | timer(WAIT_TIME / 2).subscribe(() => { 62 | fn.apply(classTest); 63 | fn.apply(classTest); 64 | }); 65 | 66 | timer(WAIT_TIME * 3).pipe( 67 | tap(() => { 68 | expect(classTest.incr).toEqual(firstPassValue); 69 | 70 | fn.apply(classTest); 71 | }), 72 | delay(WAIT_TIME * 1.1) 73 | ) 74 | .subscribe(() => { 75 | expect(classTest.incr).toEqual(secondPassValue); 76 | done(); 77 | }); 78 | } 79 | 80 | describe('QueueDecorator', () => { 81 | let klass: ClassTest; 82 | 83 | beforeEach(() => { 84 | klass = new ClassTest(); 85 | }); 86 | 87 | it('should works with observable success', (done: DoneFn) => { 88 | testIt(klass, klass.obsSuccessFunc, done); 89 | }); 90 | it('should works with observable error', (done: DoneFn) => { 91 | testIt(klass, klass.obsErrorFunc, done); 92 | }); 93 | it('should works with promise success', (done: DoneFn) => { 94 | testIt(klass, klass.promiseSuccessFunc, done); 95 | }); 96 | it('should works with promise error', (done: DoneFn) => { 97 | testIt(klass, klass.promiseErrorFunc, done); 98 | }); 99 | it('should works with function success', (done: DoneFn) => { 100 | testIt(klass, klass.classicSuccessFunc, done, 3, 4); 101 | }); 102 | it('should works with function error', (done: DoneFn) => { 103 | testIt(klass, klass.classicErrorFunc, done, 3, 4); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /projects/utils/src/lib/queue.decorator.ts: -------------------------------------------------------------------------------- 1 | import {Subscription} from 'rxjs'; 2 | import {Deferred, isPromise} from './functions'; 3 | 4 | export interface QueueData { 5 | queue: Function[]; 6 | promise: Promise; 7 | } 8 | 9 | /** 10 | * Put the method call in a queue and wait for a Promise / Subscription / method execution 11 | * /!\ the method result is modified => Return a Promise 12 | */ 13 | export function Queue(limit?: number, name?: string): MethodDecorator { 14 | return function( 15 | target: Object, 16 | propertyKey: string | symbol, 17 | descriptor: TypedPropertyDescriptor 18 | ): TypedPropertyDescriptor | void { 19 | // Init 20 | name = name || propertyKey.toString(); 21 | const data: QueueData = (target[`_ngxQueue_${name}`] = target[`_ngxQueue_${name}`] || { 22 | queue: [], 23 | promise: Promise.resolve() 24 | }); 25 | const originalMethod = descriptor.value; 26 | 27 | // Change method 28 | descriptor.value = function(...args: any[]) { 29 | // Push Future call 30 | const deferred: Deferred = new Deferred(); 31 | 32 | // Ignore next call 33 | if (limit && data.queue.length >= limit) { 34 | deferred.resolve(); 35 | } else { 36 | data.queue.push(() => { 37 | try { 38 | const result: any = originalMethod.apply(this, args); 39 | 40 | if (isPromise(result)) { 41 | result.then(r => deferred.resolve(r), e => deferred.reject(e)); 42 | } else if (result instanceof Subscription) { 43 | result.add(() => deferred.resolve()); 44 | } else { 45 | deferred.resolve(result); 46 | } 47 | } catch (e) { 48 | deferred.reject(e); 49 | } 50 | 51 | return deferred.promise; 52 | }); 53 | 54 | // Queue 55 | data.promise = data.promise 56 | // Call 57 | .then(() => data.queue[0].apply(this)) 58 | // Pop 59 | .then(() => data.queue.pop(), () => data.queue.pop()); 60 | } 61 | 62 | // Return promise 63 | return deferred.promise; 64 | }; 65 | 66 | return descriptor; 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /projects/utils/src/lib/wait.decorator.spec.ts: -------------------------------------------------------------------------------- 1 | import {Observable, Subscription, timer} from 'rxjs'; 2 | import {delay, tap} from 'rxjs/operators'; 3 | import {Wait} from './wait.decorator'; 4 | 5 | const WAIT_TIME = 50; 6 | 7 | class ClassTest { 8 | incr = 0; 9 | 10 | get waitObs(): Observable { 11 | return timer(WAIT_TIME); 12 | } 13 | 14 | @Wait() 15 | obsSuccessFunc(incr = 1): Subscription { 16 | return this.waitObs 17 | .subscribe(() => this.plus(incr)); 18 | } 19 | 20 | private plus(n: number): number { 21 | this.incr += n; 22 | return this.incr; 23 | } 24 | } 25 | 26 | function testIt(classTest: ClassTest, fn: Function, done: DoneFn, firstPassValue: number = 2, secondPassValue: number = 3) { 27 | fn.apply(classTest); 28 | 29 | timer(WAIT_TIME * 1.1).subscribe(() => { 30 | fn.apply(classTest); 31 | fn.apply(classTest); 32 | }); 33 | 34 | timer(WAIT_TIME * 2.2).pipe( 35 | tap(() => { 36 | expect(classTest.incr).toEqual(firstPassValue); 37 | 38 | fn.apply(classTest); 39 | }), 40 | delay(WAIT_TIME * 1.1) 41 | ).subscribe(() => { 42 | expect(classTest.incr).toEqual(secondPassValue); 43 | done(); 44 | }); 45 | } 46 | 47 | describe('WaitDecorator', () => { 48 | let klass: ClassTest; 49 | 50 | beforeEach(() => { 51 | klass = new ClassTest(); 52 | }); 53 | 54 | it('should works with observable success', (done: DoneFn) => { 55 | testIt(klass, klass.obsSuccessFunc, done); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /projects/utils/src/lib/wait.decorator.ts: -------------------------------------------------------------------------------- 1 | import {Subscription} from 'rxjs'; 2 | import {Deferred, isPromise} from './functions'; 3 | 4 | export interface WaitData { 5 | wait?: Function; 6 | promise: Promise; 7 | } 8 | 9 | /** 10 | * Wait for a Promise / Subscription before to be re-executed 11 | * /!\ the method result is modified => Return a Promise 12 | */ 13 | export function Wait(name?: string): MethodDecorator { 14 | return function( 15 | target: Object, 16 | propertyKey: string | symbol, 17 | descriptor: TypedPropertyDescriptor 18 | ): TypedPropertyDescriptor | void { 19 | // Init 20 | name = name || propertyKey.toString(); 21 | const data: WaitData = (target[`_ngxWait_${name}`] = target[`_ngxWait_${name}`] || { 22 | promise: Promise.resolve() 23 | }); 24 | const originalMethod = descriptor.value; 25 | 26 | // Change method 27 | descriptor.value = function(...args: any[]) { 28 | // Push Future call 29 | const deferred: Deferred = new Deferred(); 30 | 31 | // Ignore next call 32 | if (!data.wait) { 33 | data.wait = () => { 34 | try { 35 | const result: any = originalMethod.apply(this, args); 36 | 37 | if (isPromise(result)) { 38 | result.then(r => deferred.resolve(r), e => deferred.reject(e)); 39 | } else if (result instanceof Subscription) { 40 | result.add(() => deferred.resolve()); 41 | } else { 42 | deferred.resolve(result); 43 | } 44 | } catch (e) { 45 | deferred.reject(e); 46 | } 47 | 48 | return deferred.promise; 49 | }; 50 | 51 | // Execute 52 | data.promise = data.promise 53 | // Call 54 | .then(() => data.wait.apply(this)) 55 | // Pop 56 | .then(() => data.wait = null, () => data.wait = null); 57 | } else { 58 | deferred.resolve(); 59 | } 60 | 61 | // Return promise 62 | return deferred.promise; 63 | }; 64 | 65 | return descriptor; 66 | }; 67 | } 68 | 69 | -------------------------------------------------------------------------------- /projects/utils/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 recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /projects/utils/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of utils 3 | */ 4 | 5 | export {Queue} from './lib/queue.decorator'; 6 | export {Wait} from './lib/wait.decorator'; 7 | export {Once} from './lib/once.decorator'; 8 | -------------------------------------------------------------------------------- /projects/utils/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/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /projects/utils/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 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": [ 20 | "src/test.ts", 21 | "**/*.spec.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /projects/utils/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "compilationMode": "partial" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/utils/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 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | 5 | # get current version 6 | PACKAGE_VERSION=$(cat package.json \ 7 | | grep version \ 8 | | head -1 \ 9 | | awk -F: '{ print $2 }' \ 10 | | sed 's/[",]//g' \ 11 | | tr -d '[[:space:]]') 12 | PLACEHOLDER="0.0.0-PLACEHOLDER" 13 | TFILE="/tmp/out.tmp.$$" 14 | 15 | # change version & publish each lib 16 | for module in dist/* 17 | do 18 | sed "s/$PLACEHOLDER/$PACKAGE_VERSION/g" "$module/package.json" > $TFILE && mv $TFILE "$module/package.json" 19 | cp LICENSE "$module/LICENSE" 20 | npm publish --access public "./$module" 21 | done 22 | -------------------------------------------------------------------------------- /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": "esnext", 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 | "cache": [ 23 | "dist/cache/cache", 24 | "dist/cache" 25 | ], 26 | "cookie": [ 27 | "dist/cookie/cookie", 28 | "dist/cookie" 29 | ], 30 | "device": [ 31 | "dist/device/device", 32 | "dist/device" 33 | ], 34 | "logger": [ 35 | "dist/logger/logger", 36 | "dist/logger" 37 | ], 38 | "spring": [ 39 | "dist/spring/spring", 40 | "dist/spring" 41 | ], 42 | "utils": [ 43 | "dist/utils/utils", 44 | "dist/utils" 45 | ] 46 | } 47 | }, 48 | "angularCompilerOptions": { 49 | "fullTemplateTypeCheck": true, 50 | "strictInjectionParameters": true 51 | } 52 | } --------------------------------------------------------------------------------