├── .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 | [](https://github.com/sardapv/angular-material-starter-template/actions/workflows/build-deploy.yml)  
8 |
9 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------