├── .all-contributorsrc ├── .browserslistrc ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ ├── build-deploy.yml │ └── coverage.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── angular.json ├── bin └── generate-app.js ├── cypress.config.ts ├── cypress ├── coverage.webpack.js ├── e2e │ └── spec.cy.ts ├── fixtures │ └── example.json ├── plugins │ └── index.js ├── support │ ├── commands.ts │ └── e2e.ts └── tsconfig.json ├── jest.config.js ├── logoForThisRepo.png ├── package-lock.json ├── package.json ├── purgecss.config.js ├── run-purgecss.js ├── setup-jest.ts ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── core │ │ ├── components │ │ │ └── .gitkeep │ │ ├── core.module.ts │ │ ├── guards │ │ │ ├── auth.guard.spec.ts │ │ │ └── auth.guard.ts │ │ ├── models │ │ │ └── auth.model.ts │ │ └── services │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── broadcaster.service.spec.ts │ │ │ ├── broadcaster.service.ts │ │ │ ├── constants.ts │ │ │ └── http-req-res.interceptor.ts │ ├── features │ │ ├── after-login │ │ │ ├── after-login-routing.module.ts │ │ │ ├── after-login.component.html │ │ │ ├── after-login.component.scss │ │ │ ├── after-login.component.spec.ts │ │ │ ├── after-login.component.ts │ │ │ └── after-login.module.ts │ │ └── before-login │ │ │ ├── before-login-routing.module.ts │ │ │ ├── before-login.component.html │ │ │ ├── before-login.component.scss │ │ │ ├── before-login.component.spec.ts │ │ │ ├── before-login.component.ts │ │ │ └── before-login.module.ts │ └── shared │ │ ├── animations │ │ └── .gitkeep │ │ ├── components │ │ └── scam │ │ │ ├── scam.component.html │ │ │ ├── scam.component.scss │ │ │ ├── scam.component.spec.ts │ │ │ └── scam.component.ts │ │ ├── directives │ │ └── .gitkeep │ │ ├── index.ts │ │ ├── models │ │ └── .gitkeep │ │ ├── pipes │ │ └── .gitkeep │ │ ├── shared.module.ts │ │ └── validators │ │ └── .gitkeep ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles │ ├── _variables.scss │ ├── mat-theme │ │ ├── _mat_core.scss │ │ ├── _mat_override.scss │ │ └── _mat_theme.scss │ ├── shared │ │ ├── _animations.scss │ │ └── _global.scss │ └── styles.scss └── tsconfig.app.json ├── tailwind.config.js ├── test-config.helper.ts ├── tsconfig.json └── tsconfig.spec.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "sardapv", 10 | "name": "Pranav Sarda", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/14892874?v=4", 12 | "profile": "http://pranavsarda.hashnode.dev/about", 13 | "contributions": [ 14 | "code", 15 | "blog", 16 | "doc", 17 | "example", 18 | "design" 19 | ] 20 | }, 21 | { 22 | "login": "sjetha", 23 | "name": "Shafiq Jetha", 24 | "avatar_url": "https://avatars.githubusercontent.com/u/1066864?v=4", 25 | "profile": "https://github.com/sjetha", 26 | "contributions": [ 27 | "test", 28 | "code", 29 | "maintenance" 30 | ] 31 | }, 32 | { 33 | "login": "DeekshithRajBasa", 34 | "name": "Deekshith Raj Basa", 35 | "avatar_url": "https://avatars.githubusercontent.com/u/37568816?v=4", 36 | "profile": "http://deekshithrajbasa.github.io", 37 | "contributions": [ 38 | "code", 39 | "example" 40 | ] 41 | }, 42 | { 43 | "login": "sapatgit", 44 | "name": "Saptarshi Majumdar", 45 | "avatar_url": "https://avatars.githubusercontent.com/u/21025626?v=4", 46 | "profile": "https://github.com/sapatgit", 47 | "contributions": [ 48 | "doc", 49 | "code" 50 | ] 51 | } 52 | ], 53 | "contributorsPerLine": 7, 54 | "projectName": "angular-material-starter-template", 55 | "projectOwner": "sardapv", 56 | "repoType": "github", 57 | "repoHost": "https://github.com", 58 | "skipCi": true 59 | } 60 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["projects/**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "parserOptions": { 8 | "project": ["tsconfig.json"], 9 | "createDefaultProgram": true 10 | }, 11 | "extends": [ 12 | "plugin:@angular-eslint/recommended", 13 | "plugin:@angular-eslint/template/process-inline-templates", 14 | "prettier" 15 | ], 16 | "rules": { 17 | "@angular-eslint/directive-selector": [ 18 | "error", 19 | { 20 | "type": "attribute", 21 | "prefix": "app", 22 | "style": "camelCase" 23 | } 24 | ], 25 | "@angular-eslint/component-selector": [ 26 | "error", 27 | { 28 | "type": "element", 29 | "prefix": "app", 30 | "style": "kebab-case" 31 | } 32 | ] 33 | } 34 | }, 35 | { 36 | "files": ["*.html"], 37 | "extends": ["plugin:@angular-eslint/template/recommended", "prettier"], 38 | "rules": {} 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json merge=npm-merge-driver -------------------------------------------------------------------------------- /.github/workflows/build-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Lint-Prettier-Prettier:verify-Tests-CypressTests-Build-Purgecss-Deploy 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 #this installs node and npm for us 15 | with: 16 | node-version: '14.x' 17 | - uses: actions/cache@v1 18 | with: 19 | path: ~/.npm 20 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 21 | restore-keys: | 22 | ${{ runner.os }}-node- 23 | - name: Install npm dependencies 24 | run: | 25 | npm install 26 | - name: Prettier 27 | run: | 28 | npm run prettier 29 | - name: Prettier Verify 30 | run: | 31 | npm run prettier:verify 32 | - name: Lint 33 | run: | 34 | npm run lint 35 | # - name: Cypress Test // bug-doesn't return back to cli, 36 | # run: | 37 | # npm run e2e:ci 38 | - name: Test 39 | run: | 40 | npm run test 41 | - name: Build 42 | run: | 43 | npm run final-build 44 | - name: Deploy 45 | uses: JamesIves/github-pages-deploy-action@releases/v3 46 | with: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | BRANCH: gh-pages 49 | FOLDER: dist 50 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Running Code Coverage 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 #this installs node and npm for us 13 | with: 14 | node-version: '14.x' 15 | - uses: actions/cache@v1 16 | with: 17 | path: ~/.npm 18 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 19 | restore-keys: | 20 | ${{ runner.os }}-node- 21 | 22 | - name: Install npm dependencies 23 | run: | 24 | npm install 25 | 26 | - name: Checkout repository 27 | uses: actions/checkout@v2 28 | with: 29 | fetch-depth: 2 30 | 31 | - name: Set up Node.js ${{ matrix.node-version }} 32 | uses: actions/setup-node@v1 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | 36 | - name: Install dependencies 37 | run: npm install 38 | 39 | - name: Run unit tests 40 | run: npm run test:coverage 41 | 42 | # - name: Run Cypress tests //bug-https://github.com/cypress-io/cypress/issues/23026 43 | # run: npm run e2e:ci 44 | 45 | # - name: Convert test results 46 | # run: npx nyc report --reporter=json 47 | 48 | # - name: Upload coverage to Codecov 49 | # uses: codecov/codecov-action@v1 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | .editorconfig 3 | 4 | .version 5 | .angular 6 | # compiled output 7 | /dist 8 | /tmp 9 | /out-tsc 10 | # Only exists if Bazel was run 11 | /bazel-out 12 | 13 | # dependencies 14 | /node_modules 15 | 16 | # profiling files 17 | chrome-profiler-events*.json 18 | speed-measure-plugin*.json 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | .vscode/settings.json 32 | .vscode/tasks.json 33 | .vscode/launch.json 34 | .vscode/extensions.json 35 | .history/* 36 | 37 | # misc 38 | /.sass-cache 39 | /connect.lock 40 | /coverage 41 | /.nyc_output 42 | /libpeerconnection.log 43 | npm-debug.log 44 | yarn-error.log 45 | testem.log 46 | /typings 47 | 48 | # System Files 49 | .DS_Store 50 | Thumbs.db 51 | 52 | # Cypress artefacts 53 | cypress/videos/** 54 | cypress/screenshots/** 55 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # Here you can add all commands you want to run sequentially 5 | # for e.g run pretiier on changed files and run lint or run tests 6 | # if failed no commits will be done 7 | 8 | npm run prettier:staged 9 | npm run lint 10 | #npm run test 11 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | dist 4 | build 5 | node_modules -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 120, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "quoteProps": "as-needed", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "angular.ng-template", 4 | "bradlc.vscode-tailwindcss", // tailwind auto suggestions 5 | "esbenp.prettier-vscode", //pretifier 6 | "mike-co.import-sorter", //sorts imports 7 | "johnpapa.Angular2", //angular snippets 8 | "formulahendry.auto-rename-tag", //auto renames tags 9 | "dbaeumer.vscode-eslint", 10 | "josefbiehler.cypress-fixture-intellisense", 11 | "ms-vscode.test-adapter-converter", 12 | "ghaschel.vscode-angular-html", 13 | "connorshea.vscode-test-explorer-status-bar" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Pranav Sarda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-material-starter-template (ng-new-app 🍄) 2 | 3 | ## Package has been updated with everything latest, if you find any issues, please report. PRs welcome 4 | 5 | _Note: Cypress RUN and Code Coverage is disabled from pipeline until officially fixed by Cypress Team_ 6 | 7 | [![Lint-Prettier-Prettier:verify-Tests-CypressTests-Build-Purgecss-Deploy](https://github.com/sardapv/angular-material-starter-template/actions/workflows/build-deploy.yml/badge.svg)](https://github.com/sardapv/angular-material-starter-template/actions/workflows/build-deploy.yml) ![GitHub](https://img.shields.io/github/license/sardapv/ng-material-starter-template) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/sardapv/angular-material-starter-template) 8 | 9 | ![logo](logoForThisRepo.png?raw=true) 10 | 11 | > This boilerplate comes with bunch of preconfigured stuffs and best practises to help you kickstart your project easier & quicker... 12 | 13 | If you like this project, do leave a 🌟 ! 😊 14 | 15 | [Sample Demo 🚀](https://sardapv.github.io/angular-material-starter-template/) (only shows that Tailwind, Material components are integrated) 16 | 17 | # How to get started? 18 | 19 | Make sure you have node.js latest stable & @angular-cli installed on your system before running below commands 20 | 21 | - Run 22 | 23 | ```bash 24 | npx ng-new-app your-app-name 25 | ``` 26 | 27 | if you don't have npx, Run - 28 | 29 | ```bash 30 | sudo npm i ng-new-app -g 31 | ``` 32 | 33 | and 34 | 35 | ```bash 36 | ng-new-app your-app-name 37 | ``` 38 | 39 | - Read instructions carefully after its successful, refer readme if any doubt. 40 | - change package.json for project, version & author details 41 | - Run`npm start` and start coding 😉 42 | 43 | # What's inside the boilerplate? 44 | 45 | > Keep reading for detailed summary 🍷 46 | 47 | - Project Structure inspired form [Rik De Vos's blog](https://medium.com/dev-jam/5-tips-best-practices-to-organize-your-angular-project-e900db08702e) - tl:dr 3 main modules 48 | 49 | - **CoreModule** - only to be imported in Appmodule 50 | 51 | - Auth Guard with basic check 52 | - Basic Auth service (Refer model in model folder & change accordingly) 53 | 54 | - login 55 | - refreshtoken 56 | - storetoken 57 | - getTokens 58 | - logout 59 | 60 | - A broadcaster service included which listens to your key:value pair of events anywhere in app. Here is how to use to share data across modules,components 61 | 62 | - ```ts 63 | constructor(private _broadcatser: BroadcasterService) {} 64 | ``` 65 | - to broadcast and listen anywhere 66 | 67 | ```ts 68 | this._broadcatser.broadcast('mykey', 'myvalue'); 69 | 70 | // to listen inside any component inject service there and do simply below 71 | 72 | /* use this service with takeUntil from rxJS and local Subject & 73 | * destroy in OnDestroy to prevent memory leaks 74 | */ 75 | 76 | this._broadcatser.listen('mykey').subscribe({ 77 | next:(data) => console.log(data) // your broadcasted value 78 | }) 79 | } 80 | ``` 81 | 82 | - **FeatureModule** - all lazyloaded pages/modules goes here 83 | 84 | - `before-login` modules 85 | - `after-login` modules 86 | 87 | - **SharedModule** - folder with bunch of SCAM (Single Component Angular Module) modules only to be shared globally and imported in feature modules 88 | 89 | - Can have custom`components, pipes, directives` as SCAM pattern (sample scam component as independent module included: recommended rather than creating big shared module) 90 | - Custom`Pipes, Directives, Components, Models, Validators` folders to organise 91 | - index.ts provided for shared.module.ts (to organise imports only for items to include in`shared.module.ts`) 92 | 93 | - annotations `@shared`, `@feature`, `@core` added in `tsconfig.json` for easy import 94 | 95 | - HTTP Request Interceptor 👀️ 96 | 97 | - in service.ts use endpoints without baseurl as e.g '/action/endpoint' 98 | - request cloner 99 | - header modifier 100 | - success and error handler 101 | - refresh token handler (inspired from [Rich Franzmeier's blog](https://www.intertech.com/author/rich-franzmeier/ 'Posts by Rich Franzmeier')) 102 | 103 | - Tailwind and post-build PurgeCSS Configuration 104 | 105 | - Tailwind configuration with font, theme and other properties (refer`tailwind.config.js`) 106 | - Note: Tailwind's own purgecss only takes care of tailwind classes, for overall application, post-build purgecss is best (I will write an article explaining why) 107 | 108 | - Angular Material Component & CDK integrated 109 | 110 | - Material theme starter pack included, just change colors,font inside`_mat_*.scss` files 111 | 112 | - Superpowerd with `Jest` for unit testing and `Cypress` for e2e testing (instead of karma and protractor). Special Thanks to contribution by [@sjetha](https://github.com/sjetha) for this and eslint integration. 113 | - ESLint integrated as recommended by Angular 114 | - Prettier configured (with resolved conflicts between ESLint) - no VS extension being used by team? run command to check if follows rules/ run prettier on all in one go! Thanks to [@deekshithrajbasa](https://github.com/deekshithrajbasa) for this. 115 | - Global route-loader progressbar on top like github, using [ngx-loading-bar](https://github.com/aitboudad/ngx-loading-bar) package 116 | - Self-XSS warning for use of console on prod build. Inspect & Check console [here](https://sardapv.github.io/angular-material-starter-template/) 117 | - Icons and Typography (CDN links - index.html) 118 | 119 | - Angular Material Icons added 120 | - Default Poppins, OpenSans font integrated 121 | 122 | - pollyfills (for safari) '_web-animations-js_' added for animations support inside _@Component_ decorator 123 | - Local source analyzing tools `webpack-bundle-analyzer` and `source-map-explorer`, Local prod-build deploy and test with purgecss 124 | - [new✨] Pre-commit husky hook, run whatever command you want before git commit. By default prettifying changed/staged files and running lint is enabled, you can comment out run tests too. Refer .husky/pre-commit file for this operations. This helps you maintian same quality across team workspaces. 125 | - [new✨] Test-coverage report and its workflow (github action). Check index.html in generated coverage folder. Thanks to [@sapatgit](https://github.com/@sapatgit) for adding this and [@sjetha](https://github.com/@sjetha) for improvising it with best solution. 126 | 127 | # There is a command for everything you need! 🧑🏻‍💻 128 | 129 | | command | What it does? | Thanks to Plugin | 130 | | ------------------------- | :------------------------------------------------------------------- | :------------------------------------------------------------------------------- | 131 | | `npm start` | Starts the server in dev mode 🤷🏻‍♂️ | | 132 | | `npm run lint` | Runs ESLint on project | | 133 | | `npm run prettier` | Runs prettier on entire src folder | | 134 | | `npm run prettier:verify` | Runs prettier-check and throws error if fails | | 135 | | `npm run prettier:staged` | Runs prettier on only staged (changed) files | | 136 | | `npm run final-build` | Takes prod build and runs PurgeCSS script | | 137 | | `npm run prod-test` | Takes a final-build deployes on local server and give you url to run | [serve](https://www.npmjs.com/package/serve) | 138 | | `npm run purgecss` | Run PurgeCSS job to reduced style.css size into few kbs | [purgecss](https://www.npmjs.com/package/purgecss) | 139 | | `npm run source` | Takes a final-build and opens up source-map-explorer view | [source-map-explorer](https://www.npmjs.com/package/source-map-explorer) | 140 | | `npm run webpack-analyze` | Takes a final-build and opens up webpack-bundle-analyzer view | [webpack-bundle-analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer) | 141 | | `npm run test` | Runs all the jests test cases | [@briebug/jest-schematic](https://www.npmjs.com/package/@briebug/jest-schematic) | 142 | | `npm run test:coverage` | Runs all the jests test cases and generate coverage report | [@briebug/jest-schematic](https://www.npmjs.com/package/@briebug/jest-schematic) | 143 | | `npm run e2e` | Opens up Cypress View to run your e2e tests in browser | [@cypress/schematic](https://www.npmjs.com/package/@cypress/schematic) | 144 | | `npm run e2e:ci` | Runs cypress tests in console (used for CI/CD) | [@cypress/schematic](https://www.npmjs.com/package/@cypress/schematic) | 145 | 146 | ### Plan 147 | 148 | Under `auth-login-page branch`, there is a sample login page designed. 149 | Looking for contributors to build signup, forgot password pages and integration with Firebase with a minimal configuration to change for any new project. PRs welcome✌🏻. Look for project/issues tab. 150 | 151 | ## Contributors ✨ 152 | 153 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |

Pranav Sarda

💻 📝 📖 💡 🎨

Shafiq Jetha

⚠️ 💻 🚧

Deekshith Raj Basa

💻 💡

Saptarshi Majumdar

📖 💻
166 | 167 | 168 | 169 | 170 | 171 | 172 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 173 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ng-new-app": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | }, 12 | "@schematics/angular:application": { 13 | "strict": true 14 | } 15 | }, 16 | "root": "", 17 | "sourceRoot": "src", 18 | "prefix": "app", 19 | "architect": { 20 | "build": { 21 | "builder": "@angular-devkit/build-angular:browser", 22 | "options": { 23 | "outputPath": "dist", 24 | "index": "src/index.html", 25 | "main": "src/main.ts", 26 | "polyfills": "src/polyfills.ts", 27 | "tsConfig": "src/tsconfig.app.json", 28 | "inlineStyleLanguage": "scss", 29 | "assets": ["src/favicon.ico", "src/assets"], 30 | "styles": ["src/styles/styles.scss"], 31 | "stylePreprocessorOptions": { 32 | "includePaths": ["src/styles/shared"] 33 | }, 34 | "scripts": [] 35 | }, 36 | "configurations": { 37 | "production": { 38 | "buildOptimizer": true, 39 | "optimization": true, 40 | "vendorChunk": false, 41 | "extractLicenses": true, 42 | "sourceMap": false, 43 | "namedChunks": false, 44 | "outputHashing": "all", 45 | "budgets": [ 46 | { 47 | "type": "initial", 48 | "maximumWarning": "3mb", 49 | "maximumError": "3mb" 50 | }, 51 | { 52 | "type": "anyComponentStyle", 53 | "maximumWarning": "3mb", 54 | "maximumError": "4kb" 55 | } 56 | ], 57 | "fileReplacements": [ 58 | { 59 | "replace": "src/environments/environment.ts", 60 | "with": "src/environments/environment.prod.ts" 61 | } 62 | ] 63 | }, 64 | "development": { 65 | "buildOptimizer": false, 66 | "optimization": false, 67 | "vendorChunk": true, 68 | "extractLicenses": false, 69 | "sourceMap": true, 70 | "namedChunks": true 71 | } 72 | }, 73 | "defaultConfiguration": "production" 74 | }, 75 | "serve": { 76 | "builder": "ngx-build-plus:dev-server", 77 | "configurations": { 78 | "production": { 79 | "browserTarget": "ng-new-app:build:production" 80 | }, 81 | "development": { 82 | "browserTarget": "ng-new-app:build:development" 83 | } 84 | }, 85 | "defaultConfiguration": "development", 86 | "options": { "browserTarget": "ng-new-app:build", "extraWebpackConfig": "./cypress/coverage.webpack.js" } 87 | }, 88 | "extract-i18n": { 89 | "builder": "@angular-devkit/build-angular:extract-i18n", 90 | "options": { 91 | "browserTarget": "ng-new-app:build" 92 | } 93 | }, 94 | "test": { 95 | "builder": "@angular-builders/jest:run", 96 | "options": { 97 | "tsConfig": "tsconfig.spec.json", 98 | "inlineStyleLanguage": ["scss"], 99 | "assets": ["src/favicon.ico", "src/assets"], 100 | "styles": ["src/styles.scss"], 101 | "scripts": [] 102 | } 103 | }, 104 | "lint": { 105 | "builder": "@angular-eslint/builder:lint", 106 | "options": { 107 | "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] 108 | } 109 | }, 110 | "cypress-run": { 111 | "builder": "@cypress/schematic:cypress", 112 | "options": { 113 | "devServerTarget": "ng-new-app:serve" 114 | }, 115 | "configurations": { 116 | "production": { 117 | "devServerTarget": "ng-new-app:production" 118 | } 119 | } 120 | }, 121 | "cypress-open": { 122 | "builder": "@cypress/schematic:cypress", 123 | "options": { 124 | "devServerTarget": "ng-new-app:serve", 125 | "watch": true, 126 | "headless": false 127 | }, 128 | "configurations": { 129 | "production": { 130 | "devServerTarget": "ng-new-app:serve:production" 131 | } 132 | } 133 | }, 134 | "e2e": { 135 | "builder": "@cypress/schematic:cypress", 136 | "options": { 137 | "devServerTarget": "ng-new-app:serve", 138 | "watch": true, 139 | "headless": false 140 | }, 141 | "configurations": { 142 | "production": { 143 | "devServerTarget": "ng-new-app:serve:production" 144 | } 145 | } 146 | } 147 | } 148 | } 149 | }, 150 | "defaultProject": "ng-new-app", 151 | "cli": { 152 | "defaultCollection": "@angular-eslint/schematics" 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /bin/generate-app.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const { execSync } = require('child_process'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | 7 | if (process.argv.length < 3) { 8 | console.log('You have to provide a name to your new angular app 🤨'); 9 | console.log('For example :'); 10 | console.log(' npx ng-new-app my-app'); 11 | process.exit(1); 12 | } 13 | 14 | const projectName = process.argv[2]; 15 | const currentPath = process.cwd(); 16 | const projectPath = path.join(currentPath, projectName); 17 | const git_repo = 'https://github.com/sardapv/angular-material-starter-template.git'; 18 | const features = [ 19 | 'New Version Updatee: Now Supports, Angular, Tailwind, Jest, Cypress @ Latest', 20 | '-------------------------------------------------------', 21 | 'Scalable Project Structure, annotations configured, Global style.scss configured', 22 | 'Angular Material Component, Icons, Typography & CDK integrated (just change _variables and colors)', 23 | 'Utility first Tailwind CSS, some of the custom configuration added', 24 | 'Jest & Cypress (No karma & Protractor)', 25 | 'Post build PurgeCSS', 26 | 'HTTP Interceptor with Request cloners, baseURL prefixer, session expiry handler, global req-res error handler', 27 | 'Boilerplate auth service and auth guard with common methods', 28 | 'Global route-loader progressbar', 29 | 'Custom broadcaster service utlising RxJs Subject for cross modules, component communication in key:value format', 30 | 'ESLint Integrated', 31 | 'Prettier Configured (exclusive of ESLint with no conflicts)', 32 | 'Husky hooks preventing git commits unless all rules passed', 33 | 'source-map-explorer and webpack-bundle-analyzer for prod build inspection of modules, you choose', 34 | 'Local prod-build deploy and test on server', 35 | 'Github Actions Pipeline configured i.e prettify -> prettify:verify -> lint -> jest & cypress tests -> build -> purgecss -> deploy', 36 | 'Prod build console warning, Bunch of custom commands, refer table in readme (https://github.com/sardapv/angular-material-starter-template#readme)', 37 | ]; 38 | 39 | try { 40 | fs.mkdirSync(projectPath); 41 | } catch (err) { 42 | if (err.code === 'EEXIST') { 43 | console.log(`The file ${projectName} already exist in the current directory, please give it another name.`); 44 | } else { 45 | console.log(error); 46 | } 47 | process.exit(1); 48 | } 49 | 50 | async function main() { 51 | try { 52 | console.log('\x1b[32m', '--------------------------------------------------------------------', '\x1b[0m'); 53 | console.log('\x1b[33m', 'ng-new-app 🚀: Downloading & Setting up the project structure...', '\x1b[0m'); 54 | console.log('\x1b[32m', '--------------------------------------------------------------------', '\x1b[0m'); 55 | execSync(`git clone --depth 1 ${git_repo} ${projectPath}`, { stdio: 'inherit' }); 56 | 57 | console.log('step 1/4 ✅'); 58 | process.chdir(projectPath); 59 | console.log('\x1b[32m', '------------------------------------------------------', '\x1b[0m'); 60 | console.log('\x1b[33m', 'ng-new-app 🚀: Hang on, installing dependencies...', '\x1b[33m'); 61 | console.log('\x1b[32m', '------------------------------------------------------', '\x1b[0m'); 62 | 63 | execSync('npm install', { stdio: 'inherit' }); 64 | console.log('step 2/4 ✅'); 65 | 66 | console.log('\x1b[32m', '--------------------------------------------', '\x1b[0m'); 67 | console.log('\x1b[33m', 'ng-new-app 🚀: Cleaning useless stuff...', '\x1b[33m'); 68 | console.log('\x1b[32m', '--------------------------------------------', '\x1b[0m'); 69 | 70 | execSync('npx rimraf ./.git'); 71 | execSync('npx rimraf ./bin'); 72 | execSync('npx rimraf ./logoForThisRepo.png'); 73 | execSync('npx rimraf ./all-contributorsrc'); 74 | execSync('npx rimraf ./README.md'); 75 | fs.rmdirSync(path.join(projectPath, 'bin'), { recursive: true }); 76 | console.log('step 3/4 ✅'); 77 | console.log( 78 | '\x1b[32m', 79 | '------------------------------------------------------------------------------------------------- ', 80 | '\x1b[0m' 81 | ); 82 | console.log( 83 | '\x1b[33m', 84 | 'ng-new-app 🚀: Yay! Boilerplate setup is successful and is ready with all below features! 🎉 ', 85 | '\x1b[33m' 86 | ); 87 | console.log( 88 | '\x1b[32m', 89 | '-------------------------------------------------------------------------------------------------', 90 | '\x1b[0m' 91 | ); 92 | 93 | features.forEach((x, i) => console.log('\x1b[32m', `${i + 1}. ${x}`, '\x1b[33m')); 94 | console.log( 95 | '\x1b[32m', 96 | '-------------------------------------------------------------------------------------------------', 97 | '\x1b[0m' 98 | ); 99 | 100 | console.log('\x1b[31m', 'Read this before you proceed', '\x1b[0m'); 101 | console.log('\x1b[34m', ` 1. cd ${projectName}`); 102 | console.log(` 2. search and replace 'ng-new-app' in all files with your app-name using editor ⚠️`); 103 | console.log(` 3. Edit package.json and change starting details like author, description, git url, etc.`); 104 | console.log(` 4. If API calls supported, configure baseurl in environment*.ts files (don't end with /)`); 105 | console.log(' 5. Run npm start', '\x1b[0m'); 106 | console.log(); 107 | console.log('step 4/4 ⏳'); 108 | console.log(); 109 | console.log( 110 | '\x1b[32m', 111 | 'Check Readme.md (https://github.com/sardapv/angular-material-starter-template#readme) for more information.', 112 | '\x1b[0m' 113 | ); 114 | console.log( 115 | '\x1b[32m', 116 | '-----------------------------------------------------------------------------------------------------------', 117 | '\x1b[0m' 118 | ); 119 | console.log(); 120 | console.log( 121 | '\x1b[36m', 122 | `If you find any issues, want to suggest enhancements. Welcome to PRs & feedbacks✌🏻!\n Author of this project would love to have your star ⭐️ on repo if you find this helpful 😇.\n Share with your friends! Github Repo: https://github.com/sardapv/angular-material-starter-template`, 123 | '\x1b[0m' 124 | ); 125 | console.log(); 126 | } catch (error) { 127 | console.log(error); 128 | } 129 | } 130 | main(); 131 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | 3 | export default defineConfig({ 4 | videosFolder: 'cypress/videos', 5 | screenshotsFolder: 'cypress/screenshots', 6 | fixturesFolder: 'cypress/fixtures', 7 | e2e: { 8 | // We've imported your old cypress plugins here. 9 | // You may want to clean this up later by importing these. 10 | setupNodeEvents(on, config) { 11 | return require('./cypress/plugins/index.js')(on, config); 12 | }, 13 | baseUrl: 'http://localhost:4200', 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /cypress/coverage.webpack.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.(js|ts)$/, 6 | loader: 'istanbul-instrumenter-loader', 7 | options: { esModules: true }, 8 | enforce: 'post', 9 | include: require('path').join(__dirname, '..', 'src'), 10 | exclude: [/\.(e2e|spec)\.ts$/, /node_modules/, /(ngfactory|ngstyle)\.js/], 11 | }, 12 | ], 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /cypress/e2e/spec.cy.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | it('should test after login page', () => { 4 | const today = dayjs().format('M/D/YYYY'); 5 | cy.visit('/after-login'); 6 | cy.get('.mat-focus-indicator') 7 | .click() 8 | .then(() => { 9 | cy.get('.mat-calendar-body-active').click(); 10 | }); 11 | cy.get('#mat-input-0').invoke('val').should('equal', today); 12 | }); 13 | 14 | it('should test before login page', () => { 15 | cy.visit('/before-login'); 16 | cy.get('.font-bold').should('have.text', 'This is flexbox example using tailwind'); 17 | cy.get('app-before-login > p').should('have.text', 'before-login works!'); 18 | }); 19 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config); 3 | return config; 4 | }; 5 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example namespace declaration will help 3 | // with Intellisense and code completion in your 4 | // IDE or Text Editor. 5 | // *********************************************** 6 | // declare namespace Cypress { 7 | // interface Chainable { 8 | // customCommand(param: any): typeof customCommand; 9 | // } 10 | // } 11 | // 12 | // function customCommand(param: any): void { 13 | // console.warn(param); 14 | // } 15 | // 16 | // NOTE: You can use it like so: 17 | // Cypress.Commands.add('customCommand', customCommand); 18 | // 19 | // *********************************************** 20 | // This example commands.js shows you how to 21 | // create various custom commands and overwrite 22 | // existing commands. 23 | // 24 | // For more comprehensive examples of custom 25 | // commands please read more here: 26 | // https://on.cypress.io/custom-commands 27 | // *********************************************** 28 | // 29 | // 30 | // -- This is a parent command -- 31 | // Cypress.Commands.add("login", (email, password) => { ... }) 32 | // 33 | // 34 | // -- This is a child command -- 35 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 36 | // 37 | // 38 | // -- This is a dual command -- 39 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 40 | // 41 | // 42 | // -- This will overwrite an existing command -- 43 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 44 | -------------------------------------------------------------------------------- /cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // When a command from ./commands is ready to use, import with `import './commands'` syntax 17 | // import './commands'; 18 | import '@cypress/code-coverage/support'; 19 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "compilerOptions": { 5 | "sourceMap": false, 6 | "types": ["cypress"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleNameMapper: { 3 | '@core/(.*)': '/src/app/core/$1', 4 | '@shared/(.*)': '/src/app/shared/$1', 5 | }, 6 | roots: ['/src'], 7 | preset: 'jest-preset-angular', 8 | setupFilesAfterEnv: ['./setup-jest.ts'], 9 | collectCoverage: true, 10 | coverageReporters: ['lcov'], 11 | coverageDirectory: './coverage/jest', 12 | collectCoverageFrom: ['**/*.ts'], 13 | globalSetup: 'jest-preset-angular/global-setup', 14 | }; 15 | -------------------------------------------------------------------------------- /logoForThisRepo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/logoForThisRepo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-new-app", 3 | "version": "1.3.0", 4 | "description": "Angular 14 boilerplate that comes with Material-UI, Tailwind3, Purgecss, Jest & Cypress Support, Optimal project structure, Interceptor with already some code, sourcemap analyzer tools, all pre-configured and much more...", 5 | "homepage": "https://github.com/sardapv/angular-material-starter-template#readme", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/sardapv/angular-material-starter-template.git" 9 | }, 10 | "keywords": [ 11 | "angular", 12 | "boilerplate", 13 | "starter-kit", 14 | "material-template", 15 | "tailwind-template", 16 | "husky", 17 | "code-coverage", 18 | "light-house" 19 | ], 20 | "author": "Pranav Sarda ", 21 | "license": "MIT", 22 | "scripts": { 23 | "ng": "ng", 24 | "prepare": "husky install", 25 | "start": "ng serve", 26 | "build": "ng build --base-href ./ ", 27 | "watch": "ng build --watch --configuration development", 28 | "prod-test": "npm run final-build && serve -s dist/", 29 | "source": "ng build --sourceMap=true --namedChunks=true && ./node_modules/.bin/source-map-explorer dist/*.js", 30 | "webpack-analyze": "ng build --sourceMap=true --namedChunks=true --stats-json && ./node_modules/.bin/webpack-bundle-analyzer dist/stats.json", 31 | "purgecss": "node run-purgecss.js", 32 | "final-build": "npm run build && npm run purgecss", 33 | "test": "ng test", 34 | "test:coverage": "ng test --coverage", 35 | "lint": "ng lint", 36 | "e2e": "ng run ng-new-app:cypress-open", 37 | "e2e:ci": "ng run ng-new-app:cypress-run", 38 | "prettier": "prettier --config ./.prettierrc --write \"src/{app,environments}/**/*{.ts,.html,.scss,.json}\"", 39 | "prettier:verify": "prettier --config ./.prettierrc --check \"src/{app,environments}/**/*{.ts,.html,.scss,.json}\"", 40 | "prettier:staged": "pretty-quick --staged" 41 | }, 42 | "bin": { 43 | "ng-new-app": "./bin/generate-app.js" 44 | }, 45 | "dependencies": { 46 | "@angular/animations": "^14.1.0", 47 | "@angular/cdk": "^14.1.0", 48 | "@angular/common": "^14.1.0", 49 | "@angular/compiler": "^14.1.0", 50 | "@angular/core": "^14.1.0", 51 | "@angular/forms": "^14.1.0", 52 | "@angular/material": "^14.1.0", 53 | "@angular/platform-browser": "^14.1.0", 54 | "@angular/platform-browser-dynamic": "^14.1.0", 55 | "@angular/router": "^14.1.0", 56 | "@cypress/schematic": "^2.0.0", 57 | "@ngx-loading-bar/core": "^5.1.1", 58 | "@ngx-loading-bar/router": "^5.1.1", 59 | "rxjs": "~7.5.0", 60 | "tslib": "^2.3.0", 61 | "web-animations-js": "^2.3.2", 62 | "zone.js": "~0.11.4" 63 | }, 64 | "devDependencies": { 65 | "@angular-builders/jest": "14.0.0", 66 | "@angular-devkit/build-angular": "~14.1.0", 67 | "@angular-eslint/builder": "~14.0.2", 68 | "@angular-eslint/eslint-plugin": "~14.0.2", 69 | "@angular-eslint/eslint-plugin-template": "~14.0.2", 70 | "@angular-eslint/schematics": "~14.0.2", 71 | "@angular-eslint/template-parser": "~14.0.2", 72 | "@angular/cli": "~14.1.0", 73 | "@angular/compiler-cli": "~14.1.0", 74 | "@briebug/jest-schematic": "^4.0.0", 75 | "@cypress/code-coverage": "^3.10.0", 76 | "@schematics/angular": "^14.1.0", 77 | "@types/jasmine": "~4.0.3", 78 | "@types/jest": "28.1.6", 79 | "@types/node": "^18.6.2", 80 | "@typescript-eslint/eslint-plugin": "5.29.0", 81 | "@typescript-eslint/parser": "5.29.0", 82 | "autoprefixer": "^10.4.8", 83 | "cypress": "10.3.1", 84 | "dayjs": "^1.11.4", 85 | "eslint": "^8.18.0", 86 | "eslint-config-prettier": "^8.5.0", 87 | "husky": "^8.0.1", 88 | "istanbul-instrumenter-loader": "^3.0.1", 89 | "jest": "28.1.3", 90 | "jest-preset-angular": "^12.2.0", 91 | "ngx-build-plus": "^14.0.0", 92 | "prettier": "2.7.1", 93 | "pretty-quick": "^3.1.3", 94 | "purgecss": "^4.1.3", 95 | "rimraf": "^3.0.2", 96 | "serve": "^14.0.1", 97 | "source-map-explorer": "^2.5.2", 98 | "tailwindcss": "^3.1.7", 99 | "typescript": "~4.7.4", 100 | "webpack-bundle-analyzer": "^4.5.0" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /purgecss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['./dist/*.{html,js}'], 3 | 4 | defaultExtractor: (content) => { 5 | /* 6 | * this is taken from tailwind's sourcecode to prevent used tailwind classes with specail characters like 7 | * @ - \ / to prevent in post-build purge script 8 | * */ 9 | 10 | const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || []; 11 | const innerMatches = content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || []; 12 | 13 | return broadMatches.concat(innerMatches); 14 | }, 15 | 16 | css: ['./dist/*.css'], 17 | options: { 18 | safelist: [], // add if any special exceptions 19 | }, 20 | output: './dist/', 21 | }; -------------------------------------------------------------------------------- /run-purgecss.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec; 2 | 3 | /** 4 | * or you can refer to https://dev.to/dylanvdmerwe/reduce-angular-style-size-using-purgecss-to-remove-unused-styles-3b2k 5 | * for full comparision script to show before and after results 6 | * */ 7 | 8 | console.log('Run PurgeCSS...'); 9 | 10 | exec( 11 | 'purgecss --config ./purgecss.config.js', 12 | function (error, stdout, stderr) { 13 | console.log('PurgeCSS completed Yay'); 14 | } 15 | ); -------------------------------------------------------------------------------- /setup-jest.ts: -------------------------------------------------------------------------------- 1 | /* global mocks for jsdom */ 2 | const mock = () => { 3 | let storage: { [key: string]: string } = {}; 4 | return { 5 | getItem: (key: string) => (key in storage ? storage[key] : null), 6 | setItem: (key: string, value: string) => (storage[key] = value || ''), 7 | removeItem: (key: string) => delete storage[key], 8 | clear: () => (storage = {}), 9 | }; 10 | }; 11 | 12 | Object.defineProperty(window, 'localStorage', { value: mock() }); 13 | Object.defineProperty(window, 'sessionStorage', { value: mock() }); 14 | Object.defineProperty(window, 'getComputedStyle', { 15 | value: () => ['-webkit-appearance'], 16 | }); 17 | 18 | Object.defineProperty(document.body.style, 'transform', { 19 | value: () => { 20 | return { 21 | enumerable: true, 22 | configurable: true, 23 | }; 24 | }, 25 | }); 26 | 27 | /* output shorter and more meaningful Zone error stack traces */ 28 | // Error.stackTraceLimit = 2; 29 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { 6 | path: '', 7 | loadChildren: () => import('./features/after-login/after-login.module').then((m) => m.AfterLoginModule), 8 | // change to before if session resume is not supported in your app 9 | }, 10 | { 11 | path: 'after-login', 12 | loadChildren: () => import('./features/after-login/after-login.module').then((m) => m.AfterLoginModule), 13 | }, 14 | { 15 | path: 'before-login', 16 | loadChildren: () => import('./features/before-login/before-login.module').then((m) => m.BeforeLoginModule), 17 | }, 18 | { 19 | path: '**', 20 | redirectTo: 'after-login', // or 404 module 21 | }, 22 | ]; 23 | 24 | @NgModule({ 25 | imports: [RouterModule.forRoot(routes)], 26 | exports: [RouterModule], 27 | }) 28 | export class AppRoutingModule {} 29 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 10 | 11 |
12 |
This is flexbox example using tailwind
13 |
14 | 15 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { AppComponent } from './app.component'; 5 | 6 | let component: ComponentFixture; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ declarations: [AppComponent], schemas: [NO_ERRORS_SCHEMA] }); 10 | }); 11 | 12 | it('should create', () => { 13 | component = TestBed.createComponent(AppComponent); 14 | expect(component).toBeTruthy(); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | import { takeUntil } from 'rxjs/operators'; 3 | 4 | import { Component, OnDestroy } from '@angular/core'; 5 | import { BroadcasterService } from '@core/services/broadcaster.service'; 6 | 7 | @Component({ 8 | selector: 'app-root', 9 | templateUrl: './app.component.html', 10 | styleUrls: ['./app.component.scss'], 11 | }) 12 | export class AppComponent implements OnDestroy { 13 | $destroy: Subject = new Subject(); 14 | 15 | constructor(private _broadcatser: BroadcasterService) { 16 | // app component broadasting 17 | this._broadcatser.broadcast('mykey', 'myvalue'); 18 | //set dummy token just to enable auth guard for after-login module 19 | localStorage.setItem('token', 'dummy'); 20 | 21 | /** 22 | * do this in other page, for e.g I'm doing here only 23 | * use this service with takeUntil from rxJS and local Subject to prevent memory leaks like shown 24 | */ 25 | this._broadcatser 26 | .listen('mykey') 27 | .pipe(takeUntil(this.$destroy)) 28 | .subscribe({ 29 | next: (data) => console.log(data), // your broadcasted value 30 | }); 31 | } 32 | 33 | ngOnDestroy() { 34 | this.$destroy.next(); 35 | this.$destroy.complete(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; 2 | import { NgModule } from '@angular/core'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | import { CoreModule } from '@core/core.module'; 6 | import { HTTPReqResInterceptor } from '@core/services/http-req-res.interceptor'; 7 | import { environment } from '@env'; 8 | import { LoadingBarRouterModule } from '@ngx-loading-bar/router'; 9 | 10 | import { AppRoutingModule } from './app-routing.module'; 11 | import { AppComponent } from './app.component'; 12 | 13 | @NgModule({ 14 | declarations: [AppComponent], 15 | imports: [ 16 | BrowserModule, 17 | BrowserAnimationsModule, 18 | AppRoutingModule, 19 | CoreModule, 20 | HttpClientModule, 21 | LoadingBarRouterModule, 22 | ], 23 | providers: [ 24 | { provide: 'BASE_URL', useValue: environment.baseurl }, 25 | { 26 | provide: HTTP_INTERCEPTORS, 27 | useClass: HTTPReqResInterceptor, 28 | multi: true, 29 | }, 30 | ], 31 | bootstrap: [AppComponent], 32 | }) 33 | export class AppModule {} 34 | -------------------------------------------------------------------------------- /src/app/core/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/core/components/.gitkeep -------------------------------------------------------------------------------- /src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule, Optional, SkipSelf } from '@angular/core'; 3 | 4 | @NgModule({ 5 | declarations: [], 6 | imports: [CommonModule], 7 | exports: [], 8 | }) 9 | export class CoreModule { 10 | /* make sure CoreModule is imported only by the AppModule and noone else */ 11 | constructor(@Optional() @SkipSelf() presentInParent: CoreModule) { 12 | if (presentInParent) { 13 | throw new Error('CoreModule is already loaded. Import only in AppModule'); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/core/guards/auth.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { AuthGuard } from './auth.guard'; 4 | 5 | let guard: AuthGuard; 6 | 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ providers: [AuthGuard], imports: [HttpClientTestingModule] }); 9 | }); 10 | 11 | it('should create', () => { 12 | guard = TestBed.inject(AuthGuard); 13 | expect(guard).toBeTruthy(); 14 | }); 15 | -------------------------------------------------------------------------------- /src/app/core/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router'; 2 | 3 | import { Injectable } from '@angular/core'; 4 | import { AuthService } from '@core/services/auth.service'; 5 | 6 | @Injectable({ providedIn: 'root' }) 7 | export class AuthGuard implements CanActivate { 8 | constructor(private _authservice: AuthService) {} 9 | 10 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 11 | return this._authservice.isLoggedIn(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/core/models/auth.model.ts: -------------------------------------------------------------------------------- 1 | export class AuthToken { 2 | access_token!: string; 3 | refresh_token!: string; 4 | } 5 | 6 | export class UserDetail { 7 | username!: string; 8 | password!: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/core/services/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 3 | import { AuthService } from './auth.service'; 4 | 5 | let service: AuthService; 6 | 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ providers: [AuthService], imports: [HttpClientTestingModule] }); 9 | }); 10 | 11 | it('should create', () => { 12 | service = TestBed.inject(AuthService); 13 | expect(service).toBeTruthy(); 14 | }); 15 | -------------------------------------------------------------------------------- /src/app/core/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { AuthToken, UserDetail } from '@core/models/auth.model'; 2 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; 3 | 4 | import { CONSTANTS } from './constants'; 5 | import { Injectable } from '@angular/core'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class AuthService { 9 | private headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'); 10 | 11 | constructor(private _http: HttpClient) {} 12 | 13 | login(userdetails: UserDetail) { 14 | const body = new HttpParams() 15 | .set(CONSTANTS.USERNAME, userdetails.username) 16 | .set(CONSTANTS.PASSWORD, userdetails.password); 17 | 18 | return this._http.post('', body.toString(), { 19 | headers: this.headers, 20 | }); 21 | } 22 | 23 | logout() { 24 | const body = new HttpParams().set(CONSTANTS.REFRESH_TOKEN, this.getRefreshToken()!); 25 | this._http.post('', body.toString(), { headers: this.headers }).subscribe({ 26 | next: () => window.location.reload(), 27 | complete: () => { 28 | localStorage.clear(); 29 | }, 30 | error: () => { 31 | localStorage.clear(); 32 | window.location.reload(); 33 | }, 34 | }); 35 | } 36 | 37 | isLoggedIn() { 38 | return this.getToken() ? true : false; // add your strong logic 39 | } 40 | 41 | storeToken(token: AuthToken) { 42 | localStorage.setItem('token', token.access_token); 43 | localStorage.setItem(CONSTANTS.REFRESH_TOKEN, token.refresh_token); 44 | } 45 | 46 | getToken() { 47 | return localStorage.getItem('token'); 48 | } 49 | 50 | refreshToken() { 51 | const body = new HttpParams().set(CONSTANTS.REFRESH_TOKEN, this.getRefreshToken()!); 52 | return this._http.post('', body.toString(), { 53 | headers: this.headers, 54 | }); 55 | } 56 | 57 | getRefreshToken() { 58 | return localStorage.getItem(CONSTANTS.REFRESH_TOKEN); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/core/services/broadcaster.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { BroadcasterService } from './broadcaster.service'; 3 | 4 | let service: BroadcasterService; 5 | 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ providers: [BroadcasterService] }); 8 | }); 9 | 10 | it('should create', () => { 11 | service = TestBed.inject(BroadcasterService); 12 | expect(service).toBeTruthy(); 13 | }); 14 | -------------------------------------------------------------------------------- /src/app/core/services/broadcaster.service.ts: -------------------------------------------------------------------------------- 1 | import { Observable, Subject } from 'rxjs'; 2 | import { filter, map } from 'rxjs/operators'; 3 | 4 | import { Injectable } from '@angular/core'; 5 | 6 | interface BroadCastObject { 7 | key: any; 8 | value: any; 9 | } 10 | 11 | @Injectable({ providedIn: 'root' }) 12 | 13 | /* just single subject to emit across application key-value pair */ 14 | export class BroadcasterService { 15 | private _eventQueue: Subject; 16 | 17 | constructor() { 18 | this._eventQueue = new Subject(); 19 | } 20 | 21 | broadcast(key: any, value: any) { 22 | this._eventQueue.next({ key, value }); 23 | } 24 | 25 | unsubscribe() { 26 | this._eventQueue.complete(); 27 | } 28 | 29 | listen(key: any): Observable { 30 | return this._eventQueue.asObservable().pipe( 31 | filter((e) => e.key === key), 32 | map((e) => e.value) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/core/services/constants.ts: -------------------------------------------------------------------------------- 1 | export abstract class CONSTANTS { 2 | static readonly DISMISS = 'dismiss-alert'; 3 | static readonly ERROR = 'error-alert'; 4 | static readonly SHOW_LOADER = 'show-loader'; 5 | static readonly AUTH_HEADER = 'Authorization'; 6 | static readonly REFRESH_TOKEN = 'refresh_token'; 7 | static readonly USERNAME = 'username'; 8 | static readonly PASSWORD = 'password'; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/core/services/http-req-res.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpErrorResponse, 3 | HttpEvent, 4 | HttpHandler, 5 | HttpInterceptor, 6 | HttpRequest, 7 | HttpResponse, 8 | } from '@angular/common/http'; 9 | 10 | import { Inject, Injectable } from '@angular/core'; 11 | import { AuthToken } from '@core/models/auth.model'; 12 | import { BehaviorSubject, Observable, throwError } from 'rxjs'; 13 | import { catchError, filter, finalize, switchMap, take, tap } from 'rxjs/operators'; 14 | import { AuthService } from './auth.service'; 15 | import { BroadcasterService } from './broadcaster.service'; 16 | import { CONSTANTS } from './constants'; 17 | 18 | @Injectable() 19 | export class HTTPReqResInterceptor implements HttpInterceptor { 20 | isalreadyRefreshing: boolean = false; 21 | private tokenSubject: BehaviorSubject = new BehaviorSubject(null); 22 | 23 | constructor( 24 | @Inject('BASE_URL') private _baseUrl: string, 25 | private _broadcaster: BroadcasterService, 26 | private _authservice: AuthService 27 | ) {} 28 | 29 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 30 | this._broadcaster.broadcast(CONSTANTS.SHOW_LOADER, true); 31 | const newReq = req.clone({ 32 | url: this._baseUrl + req.url, 33 | headers: req.headers.set('custom_header', 'value'), 34 | }); 35 | 36 | return next.handle(newReq).pipe( 37 | tap((e) => { 38 | if (e instanceof HttpResponse) { 39 | this.handleSuccess(e.body); 40 | } 41 | }), 42 | catchError((err) => this.handleError(newReq, next, err)), 43 | finalize(() => { 44 | this._broadcaster.broadcast(CONSTANTS.SHOW_LOADER, false); 45 | }) 46 | ); 47 | } 48 | 49 | handleError(newRequest: HttpRequest, next: HttpHandler, err: any) { 50 | if (err instanceof HttpErrorResponse && err.status === 401) { 51 | return this.handle401(newRequest, next); 52 | } else { 53 | this._broadcaster.broadcast(CONSTANTS.ERROR, { 54 | error: 'Something went wrong', 55 | timeout: 5000, 56 | }); 57 | } 58 | return throwError(err); 59 | } 60 | 61 | handleSuccess(body: any) { 62 | /* handle success actions here */ 63 | } 64 | 65 | addToken(request: HttpRequest, newToken: AuthToken) { 66 | return request.clone({ 67 | headers: request.headers.set(CONSTANTS.AUTH_HEADER, `Bearer ${newToken.access_token}`), 68 | }); 69 | } 70 | 71 | /* Refresh handler referred from https://www.intertech.com/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/ */ 72 | handle401(request: HttpRequest, next: HttpHandler) { 73 | if (!this.isalreadyRefreshing) { 74 | // don't want to have multiple refresh request when multiple unauthorized requests 75 | this.isalreadyRefreshing = true; 76 | // so that new subscribers don't trigger switchmap part and stay in queue till new token received 77 | this.tokenSubject.next(null); 78 | return this._authservice.refreshToken().pipe( 79 | switchMap((newToken: AuthToken) => { 80 | if (newToken) { 81 | // update token store & publish new token, yay!! 82 | this._authservice.storeToken(newToken); 83 | this.tokenSubject.next(newToken); 84 | return next.handle(this.addToken(request, newToken)); 85 | } 86 | // no new token received | something messed up 87 | this._authservice.logout(); 88 | return throwError('no refresh token found'); 89 | }), 90 | catchError((error) => { 91 | this._authservice.logout(); 92 | return throwError(error); 93 | }), 94 | finalize(() => (this.isalreadyRefreshing = false)) 95 | ); 96 | } else { 97 | /* if tab is kept running and this isalreadyRefreshing is still true, 98 | user clicks another menu, req initiated but refresh failed */ 99 | if (this.isalreadyRefreshing && request.url.includes('refresh')) { 100 | this._authservice.logout(); 101 | } 102 | // new token ready subscribe -> every skipped request will be retried with fresh token 103 | return this.tokenSubject.pipe( 104 | filter((token: AuthToken) => token != null), 105 | take(1), // complete the stream 106 | switchMap((token: AuthToken) => { 107 | return next.handle(this.addToken(request, token)); 108 | }) 109 | ); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/app/features/after-login/after-login-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { AuthGuard } from '@core/guards/auth.guard'; 4 | 5 | import { AfterLoginComponent } from './after-login.component'; 6 | 7 | const routes: Routes = [{ path: '', component: AfterLoginComponent, canActivate: [AuthGuard] }]; 8 | 9 | @NgModule({ 10 | imports: [RouterModule.forChild(routes)], 11 | exports: [RouterModule], 12 | }) 13 | export class AfterLoginRoutingModule {} 14 | -------------------------------------------------------------------------------- /src/app/features/after-login/after-login.component.html: -------------------------------------------------------------------------------- 1 |
2 |

This is after login page rendered inside router-outlet with Auth Guard returning true

3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /src/app/features/after-login/after-login.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/features/after-login/after-login.component.scss -------------------------------------------------------------------------------- /src/app/features/after-login/after-login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { MatNativeDateModule } from '@angular/material/core'; 4 | import { MatDatepickerModule } from '@angular/material/datepicker'; 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | import { ScamComponent } from '@shared/components/scam/scam.component'; 7 | 8 | import { SharedModule } from '../../shared/shared.module'; 9 | import { AfterLoginComponent } from './after-login.component'; 10 | 11 | describe('AfterLoginComponent', () => { 12 | let component: AfterLoginComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(waitForAsync(() => { 16 | TestBed.configureTestingModule({ 17 | declarations: [AfterLoginComponent], 18 | imports: [ScamComponent, SharedModule, MatDatepickerModule, MatNativeDateModule, BrowserAnimationsModule], 19 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 20 | }).compileComponents(); 21 | })); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(AfterLoginComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/features/after-login/after-login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-after-login', 5 | templateUrl: './after-login.component.html', 6 | styleUrls: ['./after-login.component.scss'], 7 | }) 8 | export class AfterLoginComponent {} 9 | -------------------------------------------------------------------------------- /src/app/features/after-login/after-login.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { ScamComponent } from '@shared/components/scam/scam.component'; 4 | import { SharedModule } from '@shared/shared.module'; 5 | 6 | import { AfterLoginRoutingModule } from './after-login-routing.module'; 7 | import { AfterLoginComponent } from './after-login.component'; 8 | 9 | @NgModule({ 10 | declarations: [AfterLoginComponent], 11 | imports: [CommonModule, AfterLoginRoutingModule, SharedModule, ScamComponent], 12 | }) 13 | export class AfterLoginModule {} 14 | -------------------------------------------------------------------------------- /src/app/features/before-login/before-login-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { BeforeLoginComponent } from './before-login.component'; 4 | 5 | const routes: Routes = [{ path: '', component: BeforeLoginComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule], 10 | }) 11 | export class BeforeLoginRoutingModule {} 12 | -------------------------------------------------------------------------------- /src/app/features/before-login/before-login.component.html: -------------------------------------------------------------------------------- 1 |

before-login works!

2 | -------------------------------------------------------------------------------- /src/app/features/before-login/before-login.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/features/before-login/before-login.component.scss -------------------------------------------------------------------------------- /src/app/features/before-login/before-login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { SharedModule } from '../../shared/shared.module'; 4 | 5 | import { BeforeLoginComponent } from './before-login.component'; 6 | 7 | describe('BeforeLoginComponent', () => { 8 | let component: BeforeLoginComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach( 12 | waitForAsync(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [BeforeLoginComponent], 15 | imports: [SharedModule], 16 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 17 | }).compileComponents(); 18 | }) 19 | ); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(BeforeLoginComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/app/features/before-login/before-login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-before-login', 5 | templateUrl: './before-login.component.html', 6 | styleUrls: ['./before-login.component.scss'], 7 | }) 8 | export class BeforeLoginComponent {} 9 | -------------------------------------------------------------------------------- /src/app/features/before-login/before-login.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { BeforeLoginRoutingModule } from './before-login-routing.module'; 5 | import { BeforeLoginComponent } from './before-login.component'; 6 | import { SharedModule } from '@shared/shared.module'; 7 | 8 | @NgModule({ 9 | declarations: [BeforeLoginComponent], 10 | imports: [CommonModule, BeforeLoginRoutingModule, SharedModule], 11 | }) 12 | export class BeforeLoginModule {} 13 | -------------------------------------------------------------------------------- /src/app/shared/animations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/shared/animations/.gitkeep -------------------------------------------------------------------------------- /src/app/shared/components/scam/scam.component.html: -------------------------------------------------------------------------------- 1 |
2 |

This is shared component (as module) using mat-datepicker refer scam-demo folder

3 | 4 | 5 | Choose a date 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /src/app/shared/components/scam/scam.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/shared/components/scam/scam.component.scss -------------------------------------------------------------------------------- /src/app/shared/components/scam/scam.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { MatNativeDateModule } from '@angular/material/core'; 4 | import { MatDatepickerModule } from '@angular/material/datepicker'; 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | 7 | import { SharedModule } from '../../shared.module'; 8 | import { ScamComponent } from './scam.component'; 9 | 10 | describe('ScamComponent', () => { 11 | let component: ScamComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(waitForAsync(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [], 17 | imports: [ScamComponent, SharedModule, MatDatepickerModule, MatNativeDateModule, BrowserAnimationsModule], 18 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 19 | }).compileComponents(); 20 | })); 21 | 22 | beforeEach(() => { 23 | fixture = TestBed.createComponent(ScamComponent); 24 | component = fixture.componentInstance; 25 | fixture.detectChanges(); 26 | }); 27 | 28 | it('should create', () => { 29 | expect(component).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/shared/components/scam/scam.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | import { MatNativeDateModule } from '@angular/material/core'; 4 | import { MatDatepickerModule } from '@angular/material/datepicker'; 5 | import { MatFormFieldModule } from '@angular/material/form-field'; 6 | import { MatInputModule } from '@angular/material/input'; 7 | 8 | @Component({ 9 | selector: 'app-scam', 10 | templateUrl: './scam.component.html', 11 | styleUrls: ['./scam.component.scss'], 12 | imports: [MatDatepickerModule, MatFormFieldModule, MatNativeDateModule, MatInputModule], 13 | standalone: true, 14 | }) 15 | export class ScamComponent { 16 | control = new FormControl(); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/shared/directives/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/shared/directives/.gitkeep -------------------------------------------------------------------------------- /src/app/shared/index.ts: -------------------------------------------------------------------------------- 1 | export const components: any[] = []; 2 | 3 | export const directives: any[] = []; 4 | 5 | export const pipes: any[] = []; 6 | -------------------------------------------------------------------------------- /src/app/shared/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/shared/models/.gitkeep -------------------------------------------------------------------------------- /src/app/shared/pipes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/shared/pipes/.gitkeep -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import * as shared from '@shared/index'; 2 | 3 | import { CommonModule } from '@angular/common'; 4 | import { NgModule } from '@angular/core'; 5 | 6 | @NgModule({ 7 | declarations: [...shared.components, ...shared.directives, ...shared.pipes], 8 | imports: [CommonModule], 9 | exports: [...shared.components, ...shared.directives, ...shared.pipes], 10 | }) 11 | export class SharedModule {} 12 | -------------------------------------------------------------------------------- /src/app/shared/validators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/app/shared/validators/.gitkeep -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | baseurl: 'https://yourapiendpointhere.app', 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | baseurl: 'https://yourapiendpointhere.app', 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardapv/angular-material-starter-template/d05bcfd0c2820c8978045e26bd58b7bae98ec7f6/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MyApp 6 | 7 | 8 | 9 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | //show this warning only on prod mode 10 | if (window) { 11 | selfXSSWarning(); 12 | } 13 | } 14 | 15 | platformBrowserDynamic() 16 | .bootstrapModule(AppModule) 17 | .catch((err) => console.error(err)); 18 | 19 | function selfXSSWarning() { 20 | setTimeout(() => { 21 | console.log('%c!!! STOP !!!', 'font-weight:bold; font: 6em Arial; color: red; '); 22 | console.log( 23 | `\n%cThis is a browser feature intended for developers. Using this console may allow\ 24 | attackers to impersonate you and steal your information using an attack called Self-XSS.\ 25 | Do not enter or paste code that you do not understand.`, 26 | 'font-weight:bold; font: 3em Arial; color: red;' 27 | ); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js'; // Included with Angular CLI. 61 | 62 | /*************************************************************************************************** 63 | * APPLICATION IMPORTS 64 | */ 65 | /** ALL Firefox, safari , IE11 browsers require the following to support `@angular/animation`. **/ 66 | import 'web-animations-js'; // Run `npm install --save web-animations-js`. 67 | -------------------------------------------------------------------------------- /src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | $primary: #ef5a5b; 2 | $secondary: #19273f; 3 | $success: #95c602; 4 | $danger: #ff1717; 5 | $yellow: #f2b619; 6 | $light: #fff; 7 | $dark: #000; 8 | $grey: #3c3d44; 9 | 10 | $primary-font: 'Poppins'; 11 | -------------------------------------------------------------------------------- /src/styles/mat-theme/_mat_core.scss: -------------------------------------------------------------------------------- 1 | // Custom Theming for Angular Material https://material.angular.io/guide/theming 2 | @import '../variables'; 3 | @import '~@angular/material/theming'; 4 | 5 | // Be sure that you only ever include this mixin once! 6 | @include mat-core(); 7 | 8 | @import './mat_theme'; 9 | 10 | // set theme vars 11 | $custom-primary: mat-palette($mat-custom-primary); 12 | $custom-accent: mat-palette($mat-custom-secondary); 13 | 14 | // The warn palette is optional (defaults to red). 15 | $custom-warn: mat-palette($mat-custom-warn); 16 | 17 | // Create the light theme object NOTE: use mat-dark-theme if you use dark mode for your app 18 | $custom-theme: mat-light-theme( 19 | ( 20 | color: ( 21 | primary: $custom-primary, 22 | accent: $custom-accent, 23 | warn: $custom-warn, 24 | ), 25 | ) 26 | ); 27 | 28 | $custom-typography: mat-typography-config( 29 | $font-family: $primary-font, 30 | ); 31 | @include angular-material-typography($custom-typography); 32 | 33 | // apply material theme 34 | @include angular-material-theme($custom-theme); 35 | -------------------------------------------------------------------------------- /src/styles/mat-theme/_mat_override.scss: -------------------------------------------------------------------------------- 1 | /* override material component's css here if globally needed */ 2 | -------------------------------------------------------------------------------- /src/styles/mat-theme/_mat_theme.scss: -------------------------------------------------------------------------------- 1 | // http://mcg.mbitson.com tool ot generate 2 | 3 | $mat-custom-primary: ( 4 | 50: #edeaf4, 5 | 100: #d1cae5, 6 | 200: #b3a7d3, 7 | 300: #9583c1, 8 | 400: #7e69b4, 9 | 500: #674ea7, 10 | 600: #5f479f, 11 | 700: #543d96, 12 | 800: #4a358c, 13 | 900: #39257c, 14 | A100: #cbbdff, 15 | A200: #a28aff, 16 | A400: #7a57ff, 17 | A700: #663dff, 18 | contrast: ( 19 | 50: #000000, 20 | 100: #000000, 21 | 200: #000000, 22 | 300: #000000, 23 | 400: #000000, 24 | 500: #000000, 25 | 600: #000000, 26 | 700: #ffffff, 27 | 800: #ffffff, 28 | 900: #ffffff, 29 | A100: #000000, 30 | A200: #000000, 31 | A400: #000000, 32 | A700: #000000, 33 | ), 34 | ); 35 | 36 | $mat-custom-secondary: ( 37 | 50: #e3e5e8, 38 | 100: #babec5, 39 | 200: #8c939f, 40 | 300: #5e6879, 41 | 400: #3c475c, 42 | 500: #19273f, 43 | 600: #162339, 44 | 700: #121d31, 45 | 800: #0e1729, 46 | 900: #080e1b, 47 | A100: #babec5, 48 | A200: #8c939f, 49 | A400: #5e6879, 50 | A700: #3c475c, 51 | contrast: ( 52 | 50: #000000, 53 | 100: #000000, 54 | 200: #000000, 55 | 300: #ffffff, 56 | 400: #ffffff, 57 | 500: #ffffff, 58 | 600: #ffffff, 59 | 700: #ffffff, 60 | 800: #ffffff, 61 | 900: #ffffff, 62 | A100: #000000, 63 | A200: #ffffff, 64 | A400: #ffffff, 65 | A700: #ffffff, 66 | ), 67 | ); 68 | 69 | $mat-custom-warn: ( 70 | 50: #ffe3e3, 71 | 100: #ffb9b9, 72 | 200: #ff8b8b, 73 | 300: #ff5d5d, 74 | 400: #ff3a3a, 75 | 500: #ff1717, 76 | 600: #ff1414, 77 | 700: #ff1111, 78 | 800: #ff0d0d, 79 | 900: #ff0707, 80 | A100: #ffffff, 81 | A200: #fff4f4, 82 | A400: #ffc1c1, 83 | A700: #ffa7a7, 84 | contrast: ( 85 | 50: #000000, 86 | 100: #000000, 87 | 200: #000000, 88 | 300: #000000, 89 | 400: #ffffff, 90 | 500: #ffffff, 91 | 600: #ffffff, 92 | 700: #ffffff, 93 | 800: #ffffff, 94 | 900: #ffffff, 95 | A100: #000000, 96 | A200: #000000, 97 | A400: #000000, 98 | A700: #000000, 99 | ), 100 | ); 101 | -------------------------------------------------------------------------------- /src/styles/shared/_animations.scss: -------------------------------------------------------------------------------- 1 | /* include all keyframe animations here if any */ 2 | -------------------------------------------------------------------------------- /src/styles/shared/_global.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | outline: none !important; 8 | } 9 | 10 | body { 11 | @apply font-primary text-sm not-italic font-light; 12 | line-height: 1.3; 13 | 14 | /* scrollbar theme overridden with below theme */ 15 | 16 | ::-webkit-scrollbar { 17 | width: 6px; 18 | height: 6px; 19 | } 20 | 21 | /* Track */ 22 | ::-webkit-scrollbar-track { 23 | @apply bg-transparent; 24 | } 25 | 26 | /* Handle, default i.e will not go below this dimensions */ 27 | ::-webkit-scrollbar-thumb { 28 | @apply bg-grey-500; 29 | width: 100px; 30 | height: 100px; 31 | } 32 | 33 | ::-webkit-scrollbar-corner { 34 | @apply bg-transparent; 35 | } 36 | 37 | /* Thumb on hover */ 38 | ::-webkit-scrollbar-thumb:hover { 39 | @apply bg-grey-500; 40 | } 41 | 42 | /* For mobile taps, remove blue tap */ 43 | -webkit-tap-highlight-color: transparent; 44 | } 45 | 46 | .ellipsis { 47 | @apply overflow-hidden whitespace-nowrap overflow-ellipsis; 48 | } 49 | -------------------------------------------------------------------------------- /src/styles/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import 'tailwindcss/base'; 3 | @import 'tailwindcss/components'; 4 | @import 'tailwindcss/utilities'; 5 | @import './shared/global'; 6 | @import './shared//animations'; 7 | @import './shared/animations'; 8 | @import './variables'; 9 | @import './mat-theme/mat_core'; 10 | @import './mat-theme/mat_override'; 11 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": ["main.ts", "polyfills.ts"], 9 | "include": ["**/*.ts", "**/*.d.ts"], 10 | "exclude": ["../node_modules", "test.ts", "**/*.spec.ts", "**/environment.*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* refer for more details https://tailwindcss.com/docs/configuration */ 2 | 3 | module.exports = { 4 | content: ['src/**/*.{html,js, jsx, tsx, ts}'], 5 | darkMode: 'media', 6 | theme: { 7 | fontFamily: { 8 | primary: ['Poppins'], 9 | secondary: ['Open Sans'], 10 | }, 11 | borderRadius: { 12 | none: '0', 13 | tiny: '5px', 14 | sm: '8px', 15 | DEFAULT: '12px', 16 | md: '14px', 17 | lg: '24px', 18 | full: '9999px', 19 | }, 20 | minWidth: { 21 | 0: '0', 22 | '1/4': '25%', 23 | '1/2': '50%', 24 | '3/4': '75%', 25 | full: '100%', 26 | }, 27 | fontSize: { 28 | tiny: '8px', 29 | xs: '12px', 30 | sm: '14px', 31 | base: '15px', 32 | lg: '16px', 33 | xl: '35px', 34 | '2xl': '45px', 35 | '3xl': '75px', 36 | }, 37 | colors: { 38 | transparent: 'transparent', 39 | primary: { 40 | 50: '#edeaf4', 41 | 100: '#d1cae5', 42 | 200: '#b3a7d3', 43 | 300: '#9583c1', 44 | 400: '#7e69b4', 45 | 500: '#674ea7', 46 | 600: '#5f479f', 47 | 700: '#543d96', 48 | 800: '#4a358c', 49 | 900: '#39257c', 50 | }, 51 | secondary: { 52 | 50: '#e3e5e8', 53 | 100: '#babec5', 54 | 200: '#8c939f', 55 | 300: '#5e6879', 56 | 400: '#3c475c', 57 | 500: '#19273f', 58 | 600: '#162339', 59 | 700: '#121d31', 60 | 800: '#0e1729', 61 | 900: '#080e1b', 62 | }, 63 | grey: { 64 | 500: '#3c3d44', 65 | }, 66 | yellow: { 67 | 500: '#f2b619', 68 | }, 69 | success: { 70 | 500: '#95c602', 71 | }, 72 | danger: { 73 | 500: '#ff1717', 74 | }, 75 | }, 76 | extend: { 77 | width: { 78 | '1/10': '10%', 79 | '1.5/10': '15%', 80 | '3/10': '30%', 81 | '3.5/10': '35%', 82 | '4.5/10': '45%', 83 | '5.5/10': '55%', 84 | '6.5/10': '65%', 85 | '7/10': '70%', 86 | '8.5/10': '85%', 87 | '9/10': '90%', 88 | '9.5/10': '95%', 89 | }, 90 | height: { 91 | '1/10': '10%', 92 | '1.5/10': '15%', 93 | '3/10': '30%', 94 | '3.5/10': '35%', 95 | '4.5/10': '45%', 96 | '5.5/10': '55%', 97 | '6.5/10': '65%', 98 | '7/10': '70%', 99 | '8.5/10': '85%', 100 | '9/10': '90%', 101 | '9.5/10': '95%', 102 | }, 103 | }, 104 | }, 105 | variants: {}, 106 | plugins: [], 107 | corePlugins: { 108 | outline: false, 109 | }, 110 | }; 111 | -------------------------------------------------------------------------------- /test-config.helper.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | type CompilerOptions = Partial<{ 4 | providers: any[]; 5 | useJit: boolean; 6 | preserveWhitespaces: boolean; 7 | }>; 8 | export type ConfigureFn = (testBed: typeof TestBed) => void; 9 | 10 | export const configureTests = (configure: ConfigureFn, compilerOptions: CompilerOptions = {}) => { 11 | const compilerConfig: CompilerOptions = { 12 | preserveWhitespaces: false, 13 | ...compilerOptions, 14 | }; 15 | 16 | const configuredTestBed = TestBed.configureCompiler(compilerConfig); 17 | 18 | configure(configuredTestBed); 19 | 20 | return configuredTestBed.compileComponents().then(() => configuredTestBed); 21 | }; 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "sourceMap": true, 12 | "declaration": false, 13 | "downlevelIteration": true, 14 | "experimentalDecorators": true, 15 | "moduleResolution": "node", 16 | "importHelpers": true, 17 | "resolveJsonModule": true, 18 | "allowSyntheticDefaultImports": true, 19 | "target": "es2017", 20 | "module": "es2020", 21 | "lib": ["es2018", "dom"], 22 | "paths": { 23 | "package.json": ["./package.json"], 24 | "@env": ["src/environments/environment"], 25 | "@shared/*": ["src/app/shared/*"], 26 | "@core/*": ["src/app/core/*"], 27 | "@features/*": ["src/app/features/*"] 28 | } 29 | }, 30 | "angularCompilerOptions": { 31 | "enableI18nLegacyMessageIdFormat": false, 32 | "strictInjectionParameters": true, 33 | "strictInputAccessModifiers": true, 34 | "strictTemplates": true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "./out-tsc/spec", 6 | "types": ["jest"], 7 | "module": "commonjs", 8 | "emitDecoratorMetadata": true, 9 | "allowJs": true 10 | }, 11 | "files": ["src/polyfills.ts"], 12 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 13 | } 14 | --------------------------------------------------------------------------------