├── .editorconfig ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── README.md ├── angular.json ├── apps ├── .gitkeep ├── api │ ├── jest.config.js │ ├── src │ │ ├── app │ │ │ ├── .gitkeep │ │ │ ├── app.controller.spec.ts │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── cart.service.spec.ts │ │ │ ├── cart.service.ts │ │ │ ├── mocks │ │ │ │ ├── products.ts │ │ │ │ └── shipping-methods.ts │ │ │ ├── product.service.spec.ts │ │ │ ├── product.service.ts │ │ │ ├── shipping.service.spec.ts │ │ │ └── shipping.service.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── tslint.json ├── ngrx-workshop-app-e2e │ ├── cypress.json │ ├── src │ │ ├── fixtures │ │ │ └── example.json │ │ ├── integration │ │ │ └── app.spec.ts │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── index.ts │ ├── tsconfig.e2e.json │ └── tsconfig.json └── ngrx-workshop-app │ ├── browserslist │ ├── jest.config.js │ ├── proxy.conf.json │ ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── cart │ │ │ ├── cart-routing.module.ts │ │ │ ├── cart.module.ts │ │ │ ├── cart │ │ │ │ ├── cart.component.css │ │ │ │ ├── cart.component.html │ │ │ │ └── cart.component.ts │ │ │ └── shipping-method-selection-dialog │ │ │ │ ├── shipping-method-selection-dialog.component.css │ │ │ │ ├── shipping-method-selection-dialog.component.html │ │ │ │ ├── shipping-method-selection-dialog.component.spec.ts │ │ │ │ └── shipping-method-selection-dialog.component.ts │ │ ├── layout │ │ │ ├── layout.module.ts │ │ │ └── top-bar │ │ │ │ ├── top-bar.component.css │ │ │ │ ├── top-bar.component.html │ │ │ │ └── top-bar.component.ts │ │ ├── products │ │ │ ├── product-details │ │ │ │ ├── product-details.component.css │ │ │ │ ├── product-details.component.html │ │ │ │ └── product-details.component.ts │ │ │ ├── product-list │ │ │ │ ├── product-list.component.css │ │ │ │ ├── product-list.component.html │ │ │ │ └── product-list.component.ts │ │ │ ├── products-routing.module.ts │ │ │ └── products.module.ts │ │ └── shared │ │ │ └── state │ │ │ ├── app │ │ │ ├── app.actions.ts │ │ │ └── index.ts │ │ │ ├── cart │ │ │ ├── cart.actions.ts │ │ │ ├── cart.effects.spec.ts │ │ │ ├── cart.effects.ts │ │ │ ├── cart.module.ts │ │ │ ├── cart.reducer.spec.ts │ │ │ ├── cart.reducer.ts │ │ │ ├── cart.selectors.spec.ts │ │ │ ├── cart.selectors.ts │ │ │ └── index.ts │ │ │ ├── products │ │ │ ├── index.ts │ │ │ ├── products.actions.ts │ │ │ ├── products.effects.spec.ts │ │ │ ├── products.effects.ts │ │ │ ├── products.module.ts │ │ │ ├── products.reducer.spec.ts │ │ │ ├── products.reducer.ts │ │ │ ├── products.selectors.spec.ts │ │ │ └── products.selectors.ts │ │ │ └── shipping │ │ │ ├── index.ts │ │ │ ├── shipping.actions.ts │ │ │ ├── shipping.effects.ts │ │ │ ├── shipping.module.ts │ │ │ ├── shipping.reducer.ts │ │ │ └── shipping.selectors.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── tslint.json ├── jest.config.js ├── libs ├── .gitkeep ├── api-interface │ ├── README.md │ ├── jest.config.js │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ └── interfaces.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── cart-data-access │ ├── README.md │ ├── jest.config.js │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── cart-data-access.module.spec.ts │ │ │ ├── cart-data-access.module.ts │ │ │ └── cart.service.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── product-data-access │ ├── README.md │ ├── jest.config.js │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── product-data-access.module.spec.ts │ │ │ ├── product-data-access.module.ts │ │ │ └── product.service.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json └── shipping-data-access │ ├── README.md │ ├── jest.config.js │ ├── src │ ├── index.ts │ ├── lib │ │ ├── shipping-data-access.module.spec.ts │ │ ├── shipping-data-access.module.ts │ │ └── shipping.service.ts │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── nx.json ├── package.json ├── tools ├── schematics │ └── .gitkeep └── tsconfig.tools.json ├── tsconfig.json ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "angular.ng-template", 5 | "ms-vscode.vscode-typescript-tslint-plugin", 6 | "esbenp.prettier-vscode" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NgrxWorkshopApp 2 | 3 | This project was generated using [Nx](https://nx.dev). 4 | 5 |

6 | 7 | 🔎 **Nx is a set of Angular CLI power-ups for modern development.** 8 | 9 | ## Quick Start & Documentation 10 | 11 | [Nx Documentation](https://nx.dev) 12 | 13 | [30-minute video showing all Nx features](https://nx.dev/getting-started/what-is-nx) 14 | 15 | [Interactive Tutorial](https://nx.dev/tutorial/01-create-application) 16 | 17 | ## Adding capabilities to your workspace 18 | 19 | Nx supports many plugins which add capabilities for developing different types of applications and different tools. 20 | 21 | These capabilities include generating applications, libraries, .etc as well as the devtools to test, and build projects as well. 22 | 23 | Below are some plugins which you can add to your workspace: 24 | 25 | - [Angular](https://angular.io) 26 | - `ng add @nrwl/angular` 27 | - [React](https://reactjs.org) 28 | - `ng add @nrwl/react` 29 | - Web (no framework frontends) 30 | - `ng add @nrwl/web` 31 | - [Nest](https://nestjs.com) 32 | - `ng add @nrwl/nest` 33 | - [Express](https://expressjs.com) 34 | - `ng add @nrwl/express` 35 | - [Node](https://nodejs.org) 36 | - `ng add @nrwl/node` 37 | 38 | ## Generate an application 39 | 40 | Run `ng g @nrwl/angular:app my-app` to generate an application. 41 | 42 | > You can use any of the plugins above to generate applications as well. 43 | 44 | When using Nx, you can create multiple applications and libraries in the same workspace. 45 | 46 | ## Generate a library 47 | 48 | Run `ng g @nrwl/angular:lib my-lib` to generate a library. 49 | 50 | > You can also use any of the plugins above to generate libraries as well. 51 | 52 | Libraries are sharable across libraries and applications. They can be imported from `@ngrx-workshop-app/mylib`. 53 | 54 | ## Development server 55 | 56 | Run `ng serve my-app` for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files. 57 | 58 | ## Code scaffolding 59 | 60 | Run `ng g component my-component --project=my-app` to generate a new component. 61 | 62 | ## Build 63 | 64 | Run `ng build my-app` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 65 | 66 | ## Running unit tests 67 | 68 | Run `ng test my-app` to execute the unit tests via [Jest](https://jestjs.io). 69 | 70 | Run `npm run affected:test` to execute the unit tests affected by a change. 71 | 72 | ## Running end-to-end tests 73 | 74 | Run `ng e2e my-app` to execute the end-to-end tests via [Cypress](https://www.cypress.io). 75 | 76 | Run `npm run affected:e2e` to execute the end-to-end tests affected by a change. 77 | 78 | ## Understand your workspace 79 | 80 | Run `npm run dep-graph` to see a diagram of the dependencies of your projects. 81 | 82 | ## Further help 83 | 84 | Visit the [Nx Documentation](https://nx.dev) to learn more. 85 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "", 5 | "projects": { 6 | "ngrx-workshop-app": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "apps/ngrx-workshop-app", 10 | "sourceRoot": "apps/ngrx-workshop-app/src", 11 | "prefix": "ngrx-workshop-app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/apps/ngrx-workshop-app", 17 | "index": "apps/ngrx-workshop-app/src/index.html", 18 | "main": "apps/ngrx-workshop-app/src/main.ts", 19 | "polyfills": "apps/ngrx-workshop-app/src/polyfills.ts", 20 | "tsConfig": "apps/ngrx-workshop-app/tsconfig.app.json", 21 | "assets": [ 22 | "apps/ngrx-workshop-app/src/favicon.ico", 23 | "apps/ngrx-workshop-app/src/assets" 24 | ], 25 | "styles": ["apps/ngrx-workshop-app/src/styles.css"], 26 | "scripts": [] 27 | }, 28 | "configurations": { 29 | "production": { 30 | "fileReplacements": [ 31 | { 32 | "replace": "apps/ngrx-workshop-app/src/environments/environment.ts", 33 | "with": "apps/ngrx-workshop-app/src/environments/environment.prod.ts" 34 | } 35 | ], 36 | "optimization": true, 37 | "outputHashing": "all", 38 | "sourceMap": false, 39 | "extractCss": true, 40 | "namedChunks": false, 41 | "aot": true, 42 | "extractLicenses": true, 43 | "vendorChunk": false, 44 | "buildOptimizer": true, 45 | "budgets": [ 46 | { 47 | "type": "initial", 48 | "maximumWarning": "2mb", 49 | "maximumError": "5mb" 50 | } 51 | ] 52 | } 53 | } 54 | }, 55 | "serve": { 56 | "builder": "@angular-devkit/build-angular:dev-server", 57 | "options": { 58 | "browserTarget": "ngrx-workshop-app:build", 59 | "proxyConfig": "apps/ngrx-workshop-app/proxy.conf.json" 60 | }, 61 | "configurations": { 62 | "production": { 63 | "browserTarget": "ngrx-workshop-app:build:production" 64 | } 65 | } 66 | }, 67 | "serve-with-api": { 68 | "builder": "@angular-devkit/architect:allOf", 69 | "options": { 70 | "targets": [ 71 | { 72 | "target": "ngrx-workshop-app:serve" 73 | }, 74 | { 75 | "target": "api:serve" 76 | } 77 | ] 78 | } 79 | }, 80 | "extract-i18n": { 81 | "builder": "@angular-devkit/build-angular:extract-i18n", 82 | "options": { 83 | "browserTarget": "ngrx-workshop-app:build" 84 | } 85 | }, 86 | "lint": { 87 | "builder": "@angular-devkit/build-angular:tslint", 88 | "options": { 89 | "tsConfig": [ 90 | "apps/ngrx-workshop-app/tsconfig.app.json", 91 | "apps/ngrx-workshop-app/tsconfig.spec.json" 92 | ], 93 | "exclude": ["**/node_modules/**", "!apps/ngrx-workshop-app/**"] 94 | } 95 | }, 96 | "test": { 97 | "builder": "@nrwl/jest:jest", 98 | "options": { 99 | "jestConfig": "apps/ngrx-workshop-app/jest.config.js", 100 | "tsConfig": "apps/ngrx-workshop-app/tsconfig.spec.json", 101 | "setupFile": "apps/ngrx-workshop-app/src/test-setup.ts" 102 | } 103 | } 104 | } 105 | }, 106 | "ngrx-workshop-app-e2e": { 107 | "root": "apps/ngrx-workshop-app-e2e", 108 | "sourceRoot": "apps/ngrx-workshop-app-e2e/src", 109 | "projectType": "application", 110 | "architect": { 111 | "e2e": { 112 | "builder": "@nrwl/cypress:cypress", 113 | "options": { 114 | "cypressConfig": "apps/ngrx-workshop-app-e2e/cypress.json", 115 | "tsConfig": "apps/ngrx-workshop-app-e2e/tsconfig.e2e.json", 116 | "devServerTarget": "ngrx-workshop-app:serve" 117 | }, 118 | "configurations": { 119 | "production": { 120 | "devServerTarget": "ngrx-workshop-app:serve:production" 121 | } 122 | } 123 | }, 124 | "lint": { 125 | "builder": "@angular-devkit/build-angular:tslint", 126 | "options": { 127 | "tsConfig": "apps/ngrx-workshop-app-e2e/tsconfig.e2e.json", 128 | "exclude": ["**/node_modules/**", "!apps/ngrx-workshop-app-e2e/**"] 129 | } 130 | } 131 | } 132 | }, 133 | "api": { 134 | "root": "apps/api", 135 | "sourceRoot": "apps/api/src", 136 | "projectType": "application", 137 | "prefix": "api", 138 | "schematics": {}, 139 | "architect": { 140 | "build": { 141 | "builder": "@nrwl/node:build", 142 | "options": { 143 | "outputPath": "dist/apps/api", 144 | "main": "apps/api/src/main.ts", 145 | "tsConfig": "apps/api/tsconfig.app.json", 146 | "assets": ["apps/api/src/assets"] 147 | }, 148 | "configurations": { 149 | "production": { 150 | "optimization": true, 151 | "extractLicenses": true, 152 | "inspect": false, 153 | "fileReplacements": [ 154 | { 155 | "replace": "apps/api/src/environments/environment.ts", 156 | "with": "apps/api/src/environments/environment.prod.ts" 157 | } 158 | ] 159 | } 160 | } 161 | }, 162 | "serve": { 163 | "builder": "@nrwl/node:execute", 164 | "options": { 165 | "buildTarget": "api:build" 166 | } 167 | }, 168 | "lint": { 169 | "builder": "@angular-devkit/build-angular:tslint", 170 | "options": { 171 | "tsConfig": [ 172 | "apps/api/tsconfig.app.json", 173 | "apps/api/tsconfig.spec.json" 174 | ], 175 | "exclude": ["**/node_modules/**", "!apps/api/**"] 176 | } 177 | }, 178 | "test": { 179 | "builder": "@nrwl/jest:jest", 180 | "options": { 181 | "jestConfig": "apps/api/jest.config.js", 182 | "tsConfig": "apps/api/tsconfig.spec.json" 183 | } 184 | } 185 | } 186 | }, 187 | "api-interface": { 188 | "root": "libs/api-interface", 189 | "sourceRoot": "libs/api-interface/src", 190 | "projectType": "library", 191 | "schematics": {}, 192 | "architect": { 193 | "lint": { 194 | "builder": "@angular-devkit/build-angular:tslint", 195 | "options": { 196 | "tsConfig": [ 197 | "libs/api-interface/tsconfig.lib.json", 198 | "libs/api-interface/tsconfig.spec.json" 199 | ], 200 | "exclude": ["**/node_modules/**", "!libs/api-interface/**"] 201 | } 202 | }, 203 | "test": { 204 | "builder": "@nrwl/jest:jest", 205 | "options": { 206 | "jestConfig": "libs/api-interface/jest.config.js", 207 | "tsConfig": "libs/api-interface/tsconfig.spec.json" 208 | } 209 | } 210 | } 211 | }, 212 | "cart-data-access": { 213 | "projectType": "library", 214 | "root": "libs/cart-data-access", 215 | "sourceRoot": "libs/cart-data-access/src", 216 | "prefix": "ngrx-workshop-app", 217 | "architect": { 218 | "lint": { 219 | "builder": "@angular-devkit/build-angular:tslint", 220 | "options": { 221 | "tsConfig": [ 222 | "libs/cart-data-access/tsconfig.lib.json", 223 | "libs/cart-data-access/tsconfig.spec.json" 224 | ], 225 | "exclude": ["**/node_modules/**", "!libs/cart-data-access/**"] 226 | } 227 | }, 228 | "test": { 229 | "builder": "@nrwl/jest:jest", 230 | "options": { 231 | "jestConfig": "libs/cart-data-access/jest.config.js", 232 | "tsConfig": "libs/cart-data-access/tsconfig.spec.json", 233 | "setupFile": "libs/cart-data-access/src/test-setup.ts" 234 | } 235 | } 236 | }, 237 | "schematics": {} 238 | }, 239 | "product-data-access": { 240 | "projectType": "library", 241 | "root": "libs/product-data-access", 242 | "sourceRoot": "libs/product-data-access/src", 243 | "prefix": "ngrx-workshop-app", 244 | "architect": { 245 | "lint": { 246 | "builder": "@angular-devkit/build-angular:tslint", 247 | "options": { 248 | "tsConfig": [ 249 | "libs/product-data-access/tsconfig.lib.json", 250 | "libs/product-data-access/tsconfig.spec.json" 251 | ], 252 | "exclude": ["**/node_modules/**", "!libs/product-data-access/**"] 253 | } 254 | }, 255 | "test": { 256 | "builder": "@nrwl/jest:jest", 257 | "options": { 258 | "jestConfig": "libs/product-data-access/jest.config.js", 259 | "tsConfig": "libs/product-data-access/tsconfig.spec.json", 260 | "setupFile": "libs/product-data-access/src/test-setup.ts" 261 | } 262 | } 263 | }, 264 | "schematics": {} 265 | }, 266 | "shipping-data-access": { 267 | "projectType": "library", 268 | "root": "libs/shipping-data-access", 269 | "sourceRoot": "libs/shipping-data-access/src", 270 | "prefix": "ngrx-workshop-app", 271 | "architect": { 272 | "lint": { 273 | "builder": "@angular-devkit/build-angular:tslint", 274 | "options": { 275 | "tsConfig": [ 276 | "libs/shipping-data-access/tsconfig.lib.json", 277 | "libs/shipping-data-access/tsconfig.spec.json" 278 | ], 279 | "exclude": ["**/node_modules/**", "!libs/shipping-data-access/**"] 280 | } 281 | }, 282 | "test": { 283 | "builder": "@nrwl/jest:jest", 284 | "options": { 285 | "jestConfig": "libs/shipping-data-access/jest.config.js", 286 | "tsConfig": "libs/shipping-data-access/tsconfig.spec.json", 287 | "setupFile": "libs/shipping-data-access/src/test-setup.ts" 288 | } 289 | } 290 | }, 291 | "schematics": {} 292 | } 293 | }, 294 | "cli": { 295 | "warnings": { 296 | "typescriptMismatch": false, 297 | "versionMismatch": false 298 | }, 299 | "defaultCollection": "@nrwl/angular" 300 | }, 301 | "schematics": { 302 | "@nrwl/angular:application": { 303 | "unitTestRunner": "jest", 304 | "e2eTestRunner": "cypress" 305 | }, 306 | "@nrwl/angular:library": { 307 | "unitTestRunner": "jest" 308 | } 309 | }, 310 | "defaultProject": "ngrx-workshop-app" 311 | } 312 | -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /apps/api/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api', 3 | preset: '../../jest.config.js', 4 | coverageDirectory: '../../coverage/apps/api' 5 | }; 6 | -------------------------------------------------------------------------------- /apps/api/src/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/apps/api/src/app/.gitkeep -------------------------------------------------------------------------------- /apps/api/src/app/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { CartService } from './cart.service'; 4 | import { ProductService } from './product.service'; 5 | import { ShippingService } from './shipping.service'; 6 | 7 | describe('AppController', () => { 8 | let app: TestingModule; 9 | 10 | beforeAll(async () => { 11 | app = await Test.createTestingModule({ 12 | controllers: [AppController], 13 | providers: [CartService, ProductService, ShippingService] 14 | }).compile(); 15 | }); 16 | 17 | describe('getShippingMethods', () => { 18 | it('should return expected list of methods', () => {}); 19 | }); 20 | 21 | describe('getProducts', () => {}); 22 | 23 | describe('getProduct', () => {}); 24 | 25 | describe('getCartItems', () => {}); 26 | 27 | describe('addToCart', () => {}); 28 | 29 | describe('checkout', () => {}); 30 | 31 | // describe('getData', () => { 32 | // it('should return "Welcome to api!"', () => { 33 | // const appController = app.get(AppController); 34 | // expect(appController.getData()).toEqual({ message: 'Welcome to api!' }); 35 | // }); 36 | // }); 37 | }); 38 | -------------------------------------------------------------------------------- /apps/api/src/app/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; 2 | import { 3 | Product, 4 | ShippingMethod, 5 | Item 6 | } from '@ngrx-workshop-app/api-interface'; 7 | import { CartService } from './cart.service'; 8 | import { ProductService } from './product.service'; 9 | import { ShippingService } from './shipping.service'; 10 | 11 | @Controller() 12 | export class AppController { 13 | constructor( 14 | private readonly cartService: CartService, 15 | private readonly productService: ProductService, 16 | private readonly shippingService: ShippingService 17 | ) {} 18 | 19 | @Get('shipping') 20 | getShippingMethods(): ShippingMethod[] { 21 | return this.shippingService.getShippingMethods(); 22 | } 23 | 24 | @Get('products') 25 | getProducts(): Product[] { 26 | return this.productService.getProducts(); 27 | } 28 | 29 | @Get('products/:productId') 30 | getProduct(@Param('productId') productId: string): Product { 31 | return this.productService.getProduct(+productId); 32 | } 33 | 34 | @Get('cart') 35 | getCartItems(): Item[] { 36 | return this.cartService.getItems(); 37 | } 38 | 39 | @Post('cart') 40 | addToCart(@Body() { productId }): Item[] { 41 | return this.cartService.addItem(productId); 42 | } 43 | 44 | @Delete('cart') 45 | checkout(): Item[] { 46 | return this.cartService.checkout(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /apps/api/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { CartService } from './cart.service'; 4 | import { ProductService } from './product.service'; 5 | import { ShippingService } from './shipping.service'; 6 | 7 | @Module({ 8 | imports: [], 9 | controllers: [AppController], 10 | providers: [CartService, ProductService, ShippingService] 11 | }) 12 | export class AppModule {} 13 | -------------------------------------------------------------------------------- /apps/api/src/app/cart.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import { CartService } from './cart.service'; 3 | 4 | describe('CartService', () => { 5 | let service: CartService; 6 | 7 | beforeAll(async () => { 8 | const app = await Test.createTestingModule({ 9 | providers: [CartService] 10 | }).compile(); 11 | 12 | service = app.get(CartService); 13 | }); 14 | 15 | describe('getItems', () => { 16 | it('should return expected items', () => {}); 17 | }); 18 | 19 | describe('addItem', () => { 20 | it('should add the item and return all items in cart', () => {}); 21 | }); 22 | 23 | describe('checkout', () => { 24 | it('should add remove all items from the cart and return an empty array', () => {}); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /apps/api/src/app/cart.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Item } from '@ngrx-workshop-app/api-interface'; 3 | 4 | @Injectable() 5 | export class CartService { 6 | private itemCount = 0; 7 | private items: Item[] = []; 8 | 9 | constructor() {} 10 | 11 | getItems(): Item[] { 12 | return this.items; 13 | } 14 | 15 | addItem(productId: string): Item[] { 16 | this.items.push({ 17 | itemId: `${this.itemCount++}`, 18 | productId: productId 19 | }); 20 | return this.items; 21 | } 22 | 23 | checkout(): Item[] { 24 | this.items = []; 25 | return this.items; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/api/src/app/mocks/products.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '@ngrx-workshop-app/api-interface'; 2 | 3 | export const mockProducts: Product[] = [ 4 | { 5 | productId: '1', 6 | name: 'Phone XL', 7 | price: 799, 8 | description: 'A large phone with one of the best screens' 9 | }, 10 | { 11 | productId: '2', 12 | name: 'Phone Mini', 13 | price: 699, 14 | description: 'A great phone with one of the best cameras' 15 | }, 16 | { 17 | productId: '3', 18 | name: 'Phone Standard', 19 | price: 299, 20 | description: '' 21 | } 22 | ]; 23 | -------------------------------------------------------------------------------- /apps/api/src/app/mocks/shipping-methods.ts: -------------------------------------------------------------------------------- 1 | import { ShippingMethod } from '@ngrx-workshop-app/api-interface'; 2 | 3 | export const mockShippingMethods: ShippingMethod[] = [ 4 | { 5 | type: 'Overnight', 6 | price: 25.99 7 | }, 8 | { 9 | type: '2-Day', 10 | price: 9.99 11 | }, 12 | { 13 | type: 'Postal', 14 | price: 2.99 15 | } 16 | ]; 17 | -------------------------------------------------------------------------------- /apps/api/src/app/product.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import { Product } from '@ngrx-workshop-app/api-interface'; 3 | import { ProductService } from './product.service'; 4 | 5 | describe('ProductService', () => { 6 | let service: ProductService; 7 | let mockProducts: Product[]; 8 | 9 | beforeAll(async () => { 10 | const app = await Test.createTestingModule({ 11 | providers: [ProductService] 12 | }).compile(); 13 | 14 | service = app.get(ProductService); 15 | 16 | mockProducts = [ 17 | { 18 | productId: '1', 19 | name: 'Phone XL', 20 | price: 799, 21 | description: 'A large phone with one of the best screens' 22 | }, 23 | { 24 | productId: '2', 25 | name: 'Phone Mini', 26 | price: 699, 27 | description: 'A great phone with one of the best cameras' 28 | }, 29 | { 30 | productId: '3', 31 | name: 'Phone Standard', 32 | price: 299, 33 | description: '' 34 | } 35 | ]; 36 | }); 37 | 38 | describe('getProducts', () => { 39 | it('should return mock products', () => { 40 | expect(service.getProducts()).toEqual(mockProducts); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /apps/api/src/app/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Product } from '@ngrx-workshop-app/api-interface'; 3 | import { mockProducts } from './mocks/products'; 4 | 5 | @Injectable() 6 | export class ProductService { 7 | private products: Product[]; 8 | 9 | constructor() { 10 | this.products = mockProducts; 11 | } 12 | 13 | getProducts(): Product[] { 14 | return this.products; 15 | } 16 | 17 | getProduct(productId: number): Product { 18 | return this.products.find(product => +product.productId === productId); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/api/src/app/shipping.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import { ShippingMethod } from '@ngrx-workshop-app/api-interface'; 3 | import { ShippingService } from './shipping.service'; 4 | 5 | describe('ShippingService', () => { 6 | let service: ShippingService; 7 | let mockShippingMethods: ShippingMethod[]; 8 | 9 | beforeAll(async () => { 10 | const app = await Test.createTestingModule({ 11 | providers: [ShippingService] 12 | }).compile(); 13 | 14 | service = app.get(ShippingService); 15 | 16 | mockShippingMethods = [ 17 | { 18 | type: 'Overnight', 19 | price: 25.99 20 | }, 21 | { 22 | type: '2-Day', 23 | price: 9.99 24 | }, 25 | { 26 | type: 'Postal', 27 | price: 2.99 28 | } 29 | ]; 30 | }); 31 | 32 | describe('getShippingMethods', () => { 33 | it('should return mock shipping methods', () => { 34 | expect(service.getShippingMethods()).toEqual(mockShippingMethods); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /apps/api/src/app/shipping.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ShippingMethod } from '@ngrx-workshop-app/api-interface'; 3 | import { mockShippingMethods } from './mocks/shipping-methods'; 4 | 5 | @Injectable() 6 | export class ShippingService { 7 | private shippingMethods: ShippingMethod[]; 8 | 9 | constructor() { 10 | this.shippingMethods = mockShippingMethods; 11 | } 12 | 13 | getShippingMethods(): ShippingMethod[] { 14 | return this.shippingMethods; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/api/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/apps/api/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/api/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /apps/api/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` 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 | }; 8 | -------------------------------------------------------------------------------- /apps/api/src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is not a production server yet! 3 | * This is only a minimal backend to get started. 4 | **/ 5 | 6 | import { NestFactory } from '@nestjs/core'; 7 | 8 | import { AppModule } from './app/app.module'; 9 | 10 | async function bootstrap() { 11 | const app = await NestFactory.create(AppModule); 12 | const globalPrefix = 'api'; 13 | app.setGlobalPrefix(globalPrefix); 14 | const port = process.env.port || 3333; 15 | await app.listen(port, () => { 16 | console.log('Listening at http://localhost:' + port + '/' + globalPrefix); 17 | }); 18 | } 19 | 20 | bootstrap(); 21 | -------------------------------------------------------------------------------- /apps/api/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "exclude": ["**/*.spec.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/api/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/api/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": {} 4 | } 5 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "pluginsFile": "./src/plugins/index", 6 | "supportFile": false, 7 | "video": true, 8 | "videosFolder": "../../dist/cypress/apps/ngrx-workshop-app-e2e/videos", 9 | "screenshotsFolder": "../../dist/cypress/apps/ngrx-workshop-app-e2e/screenshots", 10 | "chromeWebSecurity": false 11 | } 12 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('ngrx-workshop-app', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | getGreeting().contains('Welcome to ngrx-workshop-app!'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app-e2e/src/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); 15 | 16 | module.exports = (on, config) => { 17 | // `on` is used to hook into various events Cypress emits 18 | // `config` is the resolved Cypress config 19 | 20 | // Preprocess Typescript 21 | on('file:preprocessor', preprocessTypescript(config)); 22 | }; 23 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app-e2e/src/support/index.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 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app-e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc" 6 | }, 7 | "include": ["src/**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["cypress", "node"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/browserslist: -------------------------------------------------------------------------------- 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 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'ngrx-workshop-app', 3 | preset: '../../jest.config.js', 4 | coverageDirectory: '../../coverage/apps/ngrx-workshop-app', 5 | snapshotSerializers: [ 6 | 'jest-preset-angular/AngularSnapshotSerializer.js', 7 | 'jest-preset-angular/HTMLCommentSerializer.js' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:3333", 4 | "secure": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | const routes: Routes = []; 5 | 6 | @NgModule({ 7 | imports: [RouterModule.forRoot(routes)], 8 | exports: [RouterModule] 9 | }) 10 | export class AppRoutingModule {} 11 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/apps/ngrx-workshop-app/src/app/app.component.css -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { async, TestBed } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { provideMockStore } from '@ngrx/store/testing'; 5 | import { AppComponent } from './app.component'; 6 | import { TopBarComponent } from './layout/top-bar/top-bar.component'; 7 | import { MatDialog } from '@angular/material/dialog'; 8 | 9 | describe('AppComponent', () => { 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [AppComponent, TopBarComponent], 13 | imports: [HttpClientModule, RouterTestingModule], 14 | providers: [ 15 | provideMockStore({ initialState: {} }), 16 | { provide: MatDialog, useValue: { open: () => {} } } 17 | ] 18 | }).compileComponents(); 19 | })); 20 | 21 | it('should create the app', () => { 22 | const fixture = TestBed.createComponent(AppComponent); 23 | const app = fixture.debugElement.componentInstance; 24 | expect(app).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ngrx-workshop-app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent {} 9 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { NgModule } from '@angular/core'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | 7 | import { EffectsModule } from '@ngrx/effects'; 8 | import { StoreRouterConnectingModule, RouterState } from '@ngrx/router-store'; 9 | import { StoreModule } from '@ngrx/store'; 10 | import { StoreDevtoolsModule } from '@ngrx/store-devtools'; 11 | 12 | import { AppComponent } from './app.component'; 13 | import { AppRoutingModule } from './app-routing.module'; 14 | import { CartModule } from './cart/cart.module'; 15 | import { environment } from '../environments/environment'; 16 | import { LayoutModule } from './layout/layout.module'; 17 | import { ProductsModule } from './products/products.module'; 18 | 19 | @NgModule({ 20 | declarations: [AppComponent], 21 | imports: [ 22 | BrowserModule, 23 | BrowserAnimationsModule, 24 | HttpClientModule, 25 | ReactiveFormsModule, 26 | AppRoutingModule, 27 | ProductsModule, 28 | LayoutModule, 29 | CartModule, 30 | StoreModule.forRoot( 31 | {}, 32 | { 33 | metaReducers: !environment.production ? [] : [], 34 | runtimeChecks: { 35 | strictStateImmutability: true, 36 | strictActionImmutability: true 37 | } 38 | } 39 | ), 40 | EffectsModule.forRoot([]), 41 | !environment.production ? StoreDevtoolsModule.instrument() : [], 42 | StoreRouterConnectingModule.forRoot({ 43 | routerState: RouterState.Minimal 44 | }) 45 | ], 46 | bootstrap: [AppComponent] 47 | }) 48 | export class AppModule {} 49 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/cart/cart-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { CartComponent } from './cart/cart.component'; 5 | 6 | const routes: Routes = [{ path: 'cart', component: CartComponent }]; 7 | 8 | @NgModule({ 9 | imports: [RouterModule.forChild(routes)], 10 | exports: [RouterModule] 11 | }) 12 | export class CartRoutingModule {} 13 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/cart/cart.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { MatDialogModule } from '@angular/material/dialog'; 6 | import { MatButtonModule } from '@angular/material/button'; 7 | 8 | import { CartDataAccessModule } from '@ngrx-workshop-app/cart-data-access'; 9 | import { ShippingDataAccessModule } from '@ngrx-workshop-app/shipping-data-access'; 10 | import { CartStateModule } from '@ngrx-workshop-app/shared/state/cart'; 11 | import { ShippingStateModule } from '@ngrx-workshop-app/shared/state/shipping'; 12 | 13 | import { CartComponent } from './cart/cart.component'; 14 | import { CartRoutingModule } from './cart-routing.module'; 15 | import { ShippingMethodSelectionDialogComponent } from './shipping-method-selection-dialog/shipping-method-selection-dialog.component'; 16 | 17 | @NgModule({ 18 | declarations: [CartComponent, ShippingMethodSelectionDialogComponent], 19 | imports: [ 20 | CommonModule, 21 | ReactiveFormsModule, 22 | MatDialogModule, 23 | MatButtonModule, 24 | CartRoutingModule, 25 | CartDataAccessModule, 26 | CartStateModule, 27 | ShippingDataAccessModule, 28 | ShippingStateModule 29 | ], 30 | entryComponents: [ShippingMethodSelectionDialogComponent] 31 | }) 32 | export class CartModule {} 33 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/cart/cart/cart.component.css: -------------------------------------------------------------------------------- 1 | .side-by-side { 2 | display: grid; 3 | grid-template-columns: 1fr 1fr; 4 | grid-template-rows: 1fr; 5 | grid-column-gap: 20px; 6 | grid-row-gap: 0px; 7 | } 8 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/cart/cart/cart.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Cart - Subtotal: {{ cartSubtotal | currency }}

4 | 5 |
6 | 7 | {{ item.product.name }} 8 | {{ item.product.price | currency }} 9 | 10 |
11 |
12 | 13 |
14 |

20 | Shipping Options - Subtotal: {{ shippingSubtotal | currency }} 21 |

22 | 23 | 24 |

Shipping Options - SHIPPING SELECTION REQUIRED

25 |
26 | 27 | 28 |
34 | {{ shipping.type }} 35 | {{ shipping.price | currency }} 36 |
37 |
38 |
39 |
40 | 41 |

Shipping Info

42 | 43 |
44 |
45 | 46 | 47 |
48 | 49 |
50 | 51 | 52 |
53 | 54 | 57 | 58 | 59 |

Errors

60 | 61 |
    62 |
  • Shipping Selection Required
  • 63 |
  • Cart is Empty
  • 64 |
  • Name & Address Required
  • 65 |
66 |
67 |
68 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/cart/cart/cart.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { ItemWithProduct } from '@ngrx-workshop-app/api-interface'; 4 | import * as fromShipping from '@ngrx-workshop-app/shared/state/shipping'; 5 | import { select, Store } from '@ngrx/store'; 6 | import { combineLatest, Observable } from 'rxjs'; 7 | import { map, startWith } from 'rxjs/operators'; 8 | 9 | @Component({ 10 | selector: 'app-cart', 11 | templateUrl: './cart.component.html', 12 | styleUrls: ['./cart.component.css'] 13 | }) 14 | export class CartComponent implements OnInit { 15 | checkoutForm = new FormGroup({ 16 | name: new FormControl('', [Validators.required]), 17 | address: new FormControl('', [Validators.required]) 18 | }); 19 | 20 | items = new Array(); 21 | shippingOptions$ = this.store.pipe( 22 | select(fromShipping.selectAllShippingOptions) 23 | ); 24 | selectedMethod$ = this.store.pipe( 25 | select(fromShipping.selectSelectedShippingOption) 26 | ); 27 | cartSubtotal = 0; 28 | shippingSubtotal$ = this.store.pipe(select(fromShipping.selectShippingCost)); 29 | grandTotal = 0; 30 | shippingInvalid$ = this.store.pipe(select(fromShipping.getShippingInvalid)); 31 | cartInvalid = false; 32 | 33 | shippingInfoInvalid$ = this.checkoutForm.statusChanges.pipe( 34 | map(x => x !== 'VALID'), 35 | startWith(true) 36 | ); 37 | 38 | checkoutDisabled$: Observable = combineLatest([ 39 | this.shippingInvalid$, 40 | this.shippingInfoInvalid$ 41 | ]).pipe(map(arr => arr.some(x => x === true))); 42 | 43 | constructor(private store: Store<{}>) {} 44 | 45 | ngOnInit() {} 46 | 47 | optionSelected(shippingMethod: string) {} 48 | 49 | onSubmit() { 50 | this.checkoutForm.reset(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/cart/shipping-method-selection-dialog/shipping-method-selection-dialog.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/apps/ngrx-workshop-app/src/app/cart/shipping-method-selection-dialog/shipping-method-selection-dialog.component.css -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/cart/shipping-method-selection-dialog/shipping-method-selection-dialog.component.html: -------------------------------------------------------------------------------- 1 | 2 |

Select a Shipping Method:

3 |
4 |
10 | {{ shipping.type }} 11 | {{ shipping.price | currency }} 12 |
13 |
14 |
15 | 18 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/cart/shipping-method-selection-dialog/shipping-method-selection-dialog.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ShippingMethodSelectionDialogComponent } from './shipping-method-selection-dialog.component'; 4 | import { provideMockStore } from '@ngrx/store/testing'; 5 | import { MatDialogRef } from '@angular/material/dialog'; 6 | 7 | describe('ShippingMethodSelectionDialogComponent', () => { 8 | let component: ShippingMethodSelectionDialogComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ShippingMethodSelectionDialogComponent], 14 | providers: [ 15 | provideMockStore(), 16 | { provide: MatDialogRef, useValue: { close: () => {} } } 17 | ] 18 | }).compileComponents(); 19 | })); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(ShippingMethodSelectionDialogComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/cart/shipping-method-selection-dialog/shipping-method-selection-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { MatDialogRef } from '@angular/material/dialog'; 3 | import { select, Store } from '@ngrx/store'; 4 | import { combineLatest, merge, Subject } from 'rxjs'; 5 | import { map, publishReplay, refCount, startWith, take } from 'rxjs/operators'; 6 | 7 | import * as fromShipping from '@ngrx-workshop-app/shared/state/shipping'; 8 | import { ShippingActions } from '@ngrx-workshop-app/shared/state/shipping'; 9 | 10 | @Component({ 11 | selector: 'ngrx-workshop-app-shipping-method-selection-dialog', 12 | templateUrl: './shipping-method-selection-dialog.component.html', 13 | styleUrls: ['./shipping-method-selection-dialog.component.css'] 14 | }) 15 | export class ShippingMethodSelectionDialogComponent implements OnInit { 16 | private _userSelections$ = new Subject(); 17 | private _originallySelectedOption$ = this.store.pipe( 18 | select(fromShipping.selectSelectedShippingOption), 19 | take(1), 20 | publishReplay(1), 21 | refCount() 22 | ); 23 | 24 | shippingOptions$ = this.store.pipe( 25 | select(fromShipping.selectAllShippingOptions) 26 | ); 27 | 28 | currentlySelected$ = merge( 29 | this._originallySelectedOption$, 30 | this._userSelections$ 31 | ).pipe( 32 | publishReplay(1), 33 | refCount() 34 | ); 35 | 36 | selectDisabled$ = combineLatest([ 37 | this.currentlySelected$.pipe( 38 | startWith(fromShipping.NO_SHIPPING_METHOD_SELECTED_TOKEN) 39 | ), 40 | this._originallySelectedOption$ 41 | ]).pipe( 42 | map(([current, original]) => 43 | current === fromShipping.NO_SHIPPING_METHOD_SELECTED_TOKEN 44 | ? true 45 | : current === original 46 | ) 47 | ); 48 | 49 | constructor( 50 | private store: Store<{}>, 51 | private dialogRef: MatDialogRef 52 | ) {} 53 | 54 | ngOnInit() {} 55 | 56 | optionSelected(option: string) { 57 | this._userSelections$.next(option); 58 | } 59 | 60 | select(shippingMethod: string) { 61 | this.store.dispatch( 62 | ShippingActions.shippingDialogSelectShippingMethod({ shippingMethod }) 63 | ); 64 | this.dialogRef.close(); 65 | } 66 | 67 | cancel() { 68 | this.dialogRef.close(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/layout/layout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { TopBarComponent } from './top-bar/top-bar.component'; 6 | 7 | @NgModule({ 8 | declarations: [TopBarComponent], 9 | imports: [CommonModule, RouterModule], 10 | exports: [TopBarComponent] 11 | }) 12 | export class LayoutModule {} 13 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/layout/top-bar/top-bar.component.css: -------------------------------------------------------------------------------- 1 | .status-buttons button, 2 | .status-buttons a { 3 | margin: 4px; 4 | } 5 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/layout/top-bar/top-bar.component.html: -------------------------------------------------------------------------------- 1 | 2 |

My Store

3 |
4 |
5 | 35 | 36 | 37 | shopping_cartCheckout 38 | {{ total | currency }} 39 | 40 |
41 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/layout/top-bar/top-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { MatDialog } from '@angular/material/dialog'; 3 | import { ShippingMethodSelectionDialogComponent } from '@ngrx-workshop-app/cart/shipping-method-selection-dialog/shipping-method-selection-dialog.component'; 4 | import * as fromShipping from '@ngrx-workshop-app/shared/state/shipping'; 5 | import { select, Store } from '@ngrx/store'; 6 | 7 | @Component({ 8 | selector: 'ngrx-workshop-app-top-bar', 9 | templateUrl: './top-bar.component.html', 10 | styleUrls: ['./top-bar.component.css'] 11 | }) 12 | export class TopBarComponent { 13 | total = 0; 14 | shippingMethod$ = this.store.pipe( 15 | select(fromShipping.selectSelectedShippingOption) 16 | ); 17 | NO_SHIPPING_METHOD_SELECTED_TOKEN = 18 | fromShipping.NO_SHIPPING_METHOD_SELECTED_TOKEN; 19 | 20 | constructor(private dialog: MatDialog, private store: Store<{}>) {} 21 | 22 | openShippingOptions() { 23 | this.dialog.open(ShippingMethodSelectionDialogComponent, { 24 | width: '450px' 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/products/product-details/product-details.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/apps/ngrx-workshop-app/src/app/products/product-details/product-details.component.css -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/products/product-details/product-details.component.html: -------------------------------------------------------------------------------- 1 |

Product Details

2 | 3 |
4 |

{{ product.name }}

5 |

{{ product.price | currency }}

6 |

{{ product.description }}

7 | 8 | 9 |
10 | 11 | 12 | Loading ... 13 | 14 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/products/product-details/product-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { MatSnackBar } from '@angular/material'; 3 | import { ActivatedRoute } from '@angular/router'; 4 | import { Product } from '@ngrx-workshop-app/api-interface'; 5 | import * as fromProducts from '@ngrx-workshop-app/shared/state/products'; 6 | import { ProductActions } from '@ngrx-workshop-app/shared/state/products'; 7 | import { select, Store } from '@ngrx/store'; 8 | import { map } from 'rxjs/operators'; 9 | 10 | @Component({ 11 | selector: 'app-product-details', 12 | templateUrl: './product-details.component.html', 13 | styleUrls: ['./product-details.component.css'] 14 | }) 15 | export class ProductDetailsComponent implements OnInit { 16 | product$ = this.store.pipe(select(fromProducts.getSelectedProduct)); 17 | 18 | constructor( 19 | private store: Store<{}>, 20 | private route: ActivatedRoute, 21 | private snack: MatSnackBar 22 | ) {} 23 | 24 | ngOnInit() { 25 | this.route.paramMap 26 | .pipe( 27 | map(params => 28 | ProductActions.enterProductDetailsPage({ id: params.get('productId') }) 29 | ) 30 | ) 31 | .subscribe(this.store); 32 | } 33 | 34 | addToCart(product: Product) { 35 | this.snack.open('Product added to cart successfully!', null, { 36 | duration: 1000 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/products/product-list/product-list.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/apps/ngrx-workshop-app/src/app/products/product-list/product-list.component.css -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/products/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 |

Products

2 | 3 |
4 |

5 | 9 | {{ product.name }} 10 | 11 |

12 |

Description: {{ product.description }}

13 | 14 | 17 |
18 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/products/product-list/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store, select } from '@ngrx/store'; 3 | import * as fromProducts from '@ngrx-workshop-app/shared/state/products'; 4 | import { MatSnackBar } from '@angular/material'; 5 | import { ProductActions } from '@ngrx-workshop-app/shared/state/products'; 6 | 7 | @Component({ 8 | selector: 'app-product-list', 9 | templateUrl: './product-list.component.html', 10 | styleUrls: ['./product-list.component.css'] 11 | }) 12 | export class ProductListComponent implements OnInit { 13 | products$ = this.store.pipe(select(fromProducts.getAllProducts)); 14 | 15 | constructor(private store: Store<{}>, private snackBar: MatSnackBar) {} 16 | 17 | ngOnInit() { 18 | this.store.dispatch(ProductActions.enterProductsPage()); 19 | } 20 | 21 | share() { 22 | this.snackBar.open('The product has been shared!', undefined, { 23 | duration: 1000 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/products/products-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { ProductDetailsComponent } from './product-details/product-details.component'; 4 | import { ProductListComponent } from './product-list/product-list.component'; 5 | 6 | const routes: Routes = [ 7 | { path: '', component: ProductListComponent }, 8 | { path: 'products/:productId', component: ProductDetailsComponent } 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [RouterModule.forChild(routes)], 13 | exports: [RouterModule] 14 | }) 15 | export class ProductsRoutingModule {} 16 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ProductDataAccessModule } from '@ngrx-workshop-app/product-data-access'; 4 | 5 | import { ProductsStateModule } from '@ngrx-workshop-app/shared/state/products'; 6 | 7 | import { ProductDetailsComponent } from './product-details/product-details.component'; 8 | import { ProductListComponent } from './product-list/product-list.component'; 9 | import { ProductsRoutingModule } from './products-routing.module'; 10 | import { MatSnackBarModule } from '@angular/material'; 11 | 12 | @NgModule({ 13 | declarations: [ProductListComponent, ProductDetailsComponent], 14 | imports: [ 15 | CommonModule, 16 | ProductDataAccessModule, 17 | ProductsStateModule, 18 | ProductsRoutingModule, 19 | MatSnackBarModule 20 | ] 21 | }) 22 | export class ProductsModule {} 23 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/app/app.actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@ngrx/store'; 2 | 3 | export const init = createAction('[App] Init'); 4 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/app/index.ts: -------------------------------------------------------------------------------- 1 | import * as AppActions from './app.actions'; 2 | 3 | export { AppActions }; 4 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/cart/cart.actions.ts: -------------------------------------------------------------------------------- 1 | // TODO: Implement Cart Actions 2 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/cart/cart.effects.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { MatSnackBarModule } from '@angular/material'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | import { CartService } from '@ngrx-workshop-app/cart-data-access'; 6 | import { provideMockActions } from '@ngrx/effects/testing'; 7 | import { Observable } from 'rxjs'; 8 | import { CartEffects } from './cart.effects'; 9 | 10 | describe('CartEffects', () => { 11 | let actions$: Observable; 12 | let effects: CartEffects; 13 | 14 | beforeEach(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [ 17 | HttpClientTestingModule, 18 | RouterTestingModule, 19 | MatSnackBarModule 20 | ], 21 | providers: [CartEffects, provideMockActions(() => actions$), CartService] 22 | }); 23 | 24 | effects = TestBed.get(CartEffects); 25 | }); 26 | 27 | it('should be created', () => { 28 | expect(effects).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/cart/cart.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { MatSnackBar } from '@angular/material'; 3 | import { Router } from '@angular/router'; 4 | import { CartService } from '@ngrx-workshop-app/cart-data-access'; 5 | import { Actions } from '@ngrx/effects'; 6 | 7 | @Injectable() 8 | export class CartEffects { 9 | // TODO: Implement Cart Effects 10 | 11 | constructor( 12 | private actions$: Actions, 13 | private cartService: CartService, 14 | private router: Router, 15 | private snackBar: MatSnackBar 16 | ) {} 17 | } 18 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/cart/cart.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { EffectsModule } from '@ngrx/effects'; 4 | import { StoreModule } from '@ngrx/store'; 5 | 6 | import { CartEffects } from './cart.effects'; 7 | import * as fromCart from './cart.reducer'; 8 | import { MatSnackBarModule } from '@angular/material'; 9 | 10 | @NgModule({ 11 | declarations: [], 12 | imports: [ 13 | StoreModule.forFeature(fromCart.cartFeatureKey, fromCart.reducer), 14 | EffectsModule.forFeature([CartEffects]), 15 | MatSnackBarModule 16 | ] 17 | }) 18 | export class CartStateModule {} 19 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/cart/cart.reducer.spec.ts: -------------------------------------------------------------------------------- 1 | import { initialState, reducer } from './cart.reducer'; 2 | 3 | describe('Products Reducer', () => { 4 | describe('an unknown action', () => { 5 | it('should return the previous state', () => { 6 | const action = {} as any; 7 | 8 | const result = reducer(initialState, action); 9 | 10 | expect(result).toBe(initialState); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/cart/cart.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action, createReducer } from '@ngrx/store'; 2 | 3 | // TOOD: Implement Cart State Interface extending from EntityState 4 | export interface State {} 5 | 6 | export const cartFeatureKey = 'cart'; 7 | 8 | // TODO: Implement Cart State Entity Adapter 9 | 10 | // TODO: Implement Cart Initial State 11 | export const initialState: State = {}; 12 | 13 | // TODO: Implement On handlers for each action 14 | const cartReducer = createReducer(initialState); 15 | 16 | export function reducer(state: State | undefined, action: Action) { 17 | return cartReducer(state, action); 18 | } 19 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/cart/cart.selectors.spec.ts: -------------------------------------------------------------------------------- 1 | import { Item, Product } from '@ngrx-workshop-app/api-interface'; 2 | import * as fromProducts from '../products'; 3 | import * as fromCart from './cart.reducer'; 4 | 5 | describe('Cart Selectors', () => { 6 | const ERROR_MSG = 'No Error Available'; 7 | 8 | let storeState; 9 | 10 | beforeEach(() => { 11 | const createItem = (id: string): Item => ({ 12 | itemId: `item-${id}`, 13 | productId: id 14 | }); 15 | const createProducts = (id: string, name = ''): Product => ({ 16 | productId: id, 17 | name: name || `name-${id}`, 18 | description: '', 19 | price: 4.99 20 | }); 21 | storeState = { 22 | cart: fromCart.initialState, 23 | products: fromProducts.adapter.addAll( 24 | [ 25 | createProducts('PRODUCT-AAA'), 26 | createProducts('PRODUCT-BBB'), 27 | createProducts('PRODUCT-CCC') 28 | ], 29 | fromProducts.initialState 30 | ) 31 | }; 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/cart/cart.selectors.ts: -------------------------------------------------------------------------------- 1 | // TODO: Implement Cart Selectors 2 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/cart/index.ts: -------------------------------------------------------------------------------- 1 | // import * as CartActions from './cart.actions'; 2 | 3 | export * from './cart.module'; 4 | export * from './cart.reducer'; 5 | // export * from './cart.selectors'; 6 | 7 | // export { CartActions }; 8 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/products/index.ts: -------------------------------------------------------------------------------- 1 | import * as ProductActions from './products.actions'; 2 | export * from './products.module'; 3 | export * from './products.reducer'; 4 | export * from './products.selectors'; 5 | export { ProductActions }; -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/products/products.actions.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '@ngrx-workshop-app/api-interface'; 2 | import { createAction, props } from '@ngrx/store'; 3 | 4 | export const enterProductsPage = createAction('[Products Page] Enter'); 5 | 6 | export const enterProductDetailsPage = createAction( 7 | '[Products Details Page] Enter', 8 | props<{ id: string }>() 9 | ); 10 | 11 | // TODO: Implement addToCart action 12 | 13 | export const loadProductsSuccess = createAction( 14 | '[Products API] Load Products Success', 15 | props<{ products: Product[] }>() 16 | ); 17 | 18 | export const loadProductsFailure = createAction( 19 | '[Products API] Load Products Failure', 20 | props<{ error: any }>() 21 | ); 22 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/products/products.effects.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { ProductService } from '@ngrx-workshop-app/product-data-access'; 4 | import { provideMockActions } from '@ngrx/effects/testing'; 5 | import { Observable } from 'rxjs'; 6 | import { ProductsEffects } from './products.effects'; 7 | 8 | describe('ProductsEffects', () => { 9 | let actions$: Observable; 10 | let effects: ProductsEffects; 11 | 12 | beforeEach(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [HttpClientTestingModule], 15 | providers: [ 16 | ProductsEffects, 17 | provideMockActions(() => actions$), 18 | ProductService 19 | ] 20 | }); 21 | 22 | effects = TestBed.get(ProductsEffects); 23 | }); 24 | 25 | it('should be created', () => { 26 | expect(effects).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/products/products.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ProductService } from '@ngrx-workshop-app/product-data-access'; 3 | import { Actions, createEffect, ofType } from '@ngrx/effects'; 4 | import { of } from 'rxjs'; 5 | import { catchError, exhaustMap, map } from 'rxjs/operators'; 6 | 7 | import * as ProductsActions from './products.actions'; 8 | import * as AppActions from '../app/app.actions'; 9 | 10 | @Injectable() 11 | export class ProductsEffects { 12 | loadProducts$ = createEffect(() => 13 | this.actions$.pipe( 14 | ofType( 15 | ProductsActions.enterProductsPage, 16 | ProductsActions.enterProductDetailsPage, 17 | AppActions.init 18 | ), 19 | exhaustMap(() => 20 | this.productsService.getProducts().pipe( 21 | map(products => ProductsActions.loadProductsSuccess({ products })), 22 | catchError(() => 23 | of( 24 | ProductsActions.loadProductsFailure({ 25 | error: 'Unable to load products' 26 | }) 27 | ) 28 | ) 29 | ) 30 | ) 31 | ) 32 | ); 33 | 34 | constructor( 35 | private actions$: Actions, 36 | private productsService: ProductService 37 | ) {} 38 | } 39 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { StoreModule } from '@ngrx/store'; 4 | import { EffectsModule } from '@ngrx/effects'; 5 | 6 | import * as fromProducts from './products.reducer'; 7 | import { ProductsEffects } from './products.effects'; 8 | 9 | @NgModule({ 10 | declarations: [], 11 | imports: [ 12 | CommonModule, 13 | StoreModule.forFeature( 14 | fromProducts.productsFeatureKey, 15 | fromProducts.reducer 16 | ), 17 | EffectsModule.forFeature([ProductsEffects]) 18 | ] 19 | }) 20 | export class ProductsStateModule {} 21 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/products/products.reducer.spec.ts: -------------------------------------------------------------------------------- 1 | import { reducer, initialState } from './products.reducer'; 2 | 3 | describe('Products Reducer', () => { 4 | describe('an unknown action', () => { 5 | it('should return the previous state', () => { 6 | const action = {} as any; 7 | 8 | const result = reducer(initialState, action); 9 | 10 | expect(result).toBe(initialState); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/products/products.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action, createReducer, on } from '@ngrx/store'; 2 | import { EntityState, createEntityAdapter } from '@ngrx/entity'; 3 | import { Product } from '@ngrx-workshop-app/api-interface'; 4 | 5 | import * as ProductsActions from './products.actions'; 6 | 7 | export interface State extends EntityState { 8 | selectedId: string | null; 9 | loaded: boolean; 10 | error: string | null; 11 | } 12 | 13 | export const productsFeatureKey = 'products'; 14 | 15 | export const adapter = createEntityAdapter({ 16 | selectId: (model: Product) => model.productId 17 | }); 18 | 19 | export const initialState: State = adapter.getInitialState({ 20 | selectedId: null, 21 | loaded: false, 22 | error: null 23 | }); 24 | 25 | const productsReducer = createReducer( 26 | initialState, 27 | on(ProductsActions.enterProductsPage, state => ({ 28 | ...state, 29 | loaded: false, 30 | error: null 31 | })), 32 | on(ProductsActions.enterProductDetailsPage, (state, { id }) => ({ 33 | ...state, 34 | selectedId: id 35 | })), 36 | on(ProductsActions.loadProductsSuccess, (state, { products }) => 37 | adapter.addAll(products, { ...state, loaded: true, error: null }) 38 | ), 39 | on(ProductsActions.loadProductsFailure, (state, { error }) => ({ 40 | ...state, 41 | error 42 | })) 43 | ); 44 | 45 | export function reducer(state: State | undefined, action: Action) { 46 | return productsReducer(state, action); 47 | } 48 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/products/products.selectors.spec.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '@ngrx-workshop-app/api-interface'; 2 | import * as ProductsSelectors from './products.selectors'; 3 | 4 | describe('Products Selectors', () => { 5 | const ERROR_MSG = 'No Error Available'; 6 | const getProductsId = it => it['productId']; 7 | 8 | let storeState; 9 | 10 | beforeEach(() => { 11 | const createProducts = (id: string, name = ''): Product => ({ 12 | productId: id, 13 | name: name || `name-${id}`, 14 | description: '', 15 | price: 4.99 16 | }); 17 | storeState = { 18 | products: { 19 | ids: ['PRODUCT-AAA', 'PRODUCT-BBB', 'PRODUCT-CCC'], 20 | entities: { 21 | ['PRODUCT-AAA']: createProducts('PRODUCT-AAA'), 22 | ['PRODUCT-BBB']: createProducts('PRODUCT-BBB'), 23 | ['PRODUCT-CCC']: createProducts('PRODUCT-CCC') 24 | }, 25 | selectedId: 'PRODUCT-BBB', 26 | error: ERROR_MSG, 27 | loaded: true 28 | } 29 | }; 30 | }); 31 | 32 | describe('Products Selectors', () => { 33 | it('getAllProducts() should return the list of Products', () => { 34 | const results = ProductsSelectors.getAllProducts(storeState); 35 | const selId = getProductsId(results[1]); 36 | 37 | expect(results.length).toBe(3); 38 | expect(selId).toBe('PRODUCT-BBB'); 39 | }); 40 | 41 | it('getSelectedProducts() should return the selected Product', () => { 42 | const result = ProductsSelectors.getSelectedProduct(storeState); 43 | const selId = getProductsId(result); 44 | 45 | expect(selId).toBe('PRODUCT-BBB'); 46 | }); 47 | 48 | it("getLoaded() should return the current 'loaded' status", () => { 49 | const result = ProductsSelectors.getProductsLoaded(storeState); 50 | 51 | expect(result).toBe(true); 52 | }); 53 | 54 | it("getError() should return the current 'error' storeState", () => { 55 | const result = ProductsSelectors.getError(storeState); 56 | 57 | expect(result).toBe(ERROR_MSG); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/products/products.selectors.ts: -------------------------------------------------------------------------------- 1 | import { createFeatureSelector, createSelector } from '@ngrx/store'; 2 | import * as fromProducts from './products.reducer'; 3 | 4 | const { selectAll, selectEntities } = fromProducts.adapter.getSelectors(); 5 | 6 | // Lookup the 'Products' feature state managed by NgRx 7 | const getProductsState = createFeatureSelector( 8 | fromProducts.productsFeatureKey 9 | ); 10 | 11 | export const getProductsLoaded = createSelector( 12 | getProductsState, 13 | state => state.loaded 14 | ); 15 | export const getError = createSelector( 16 | getProductsState, 17 | state => state.error 18 | ); 19 | 20 | export const getAllProducts = createSelector( 21 | getProductsState, 22 | selectAll 23 | ); 24 | 25 | export const getProductsEntities = createSelector( 26 | getProductsState, 27 | selectEntities 28 | ); 29 | 30 | export const getSelectedProductId = createSelector( 31 | getProductsState, 32 | state => state.selectedId 33 | ); 34 | 35 | export const getSelectedProduct = createSelector( 36 | getProductsEntities, 37 | getSelectedProductId, 38 | (entities, selectedId) => selectedId && entities[selectedId] 39 | ); 40 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/shipping/index.ts: -------------------------------------------------------------------------------- 1 | import * as ShippingActions from './shipping.actions'; 2 | export * from './shipping.module'; 3 | export * from './shipping.reducer'; 4 | export * from './shipping.selectors'; 5 | 6 | export { ShippingActions }; -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/shipping/shipping.actions.ts: -------------------------------------------------------------------------------- 1 | import { ShippingMethod } from '@ngrx-workshop-app/api-interface'; 2 | import { createAction, props } from '@ngrx/store'; 3 | 4 | export const shippingApiOptionsLoadedSuccess = createAction( 5 | '[Shipping API] Options Loaded Success', 6 | props<{ shippingMethods: ShippingMethod[] }>() 7 | ); 8 | 9 | export const shippingApiOptionsLoadFailure = createAction( 10 | '[Shipping API] Options Load Failure', 11 | props<{ error: string }>() 12 | ); 13 | 14 | export const shippingDialogSelectShippingMethod = createAction( 15 | '[Shipping Dialog] Select Shipping Method', 16 | props<{ shippingMethod: string }>() 17 | ); 18 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/shipping/shipping.effects.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ShippingService } from '@ngrx-workshop-app/shipping-data-access'; 3 | import { createEffect, Actions, ofType } from '@ngrx/effects'; 4 | import { switchMap, map, catchError } from 'rxjs/operators'; 5 | import { of } from 'rxjs'; 6 | 7 | import * as AppActions from '../app/app.actions'; 8 | import * as ShippingActions from './shipping.actions'; 9 | 10 | @Injectable() 11 | export class ShippingEffects { 12 | getShippingOptions$ = createEffect(() => 13 | this.actions.pipe( 14 | ofType(AppActions.init), 15 | switchMap(() => 16 | this.shippingService.getShippingPrices().pipe( 17 | map(shippingMethods => 18 | ShippingActions.shippingApiOptionsLoadedSuccess({ shippingMethods }) 19 | ), 20 | catchError((error: Error) => 21 | of( 22 | ShippingActions.shippingApiOptionsLoadFailure({ 23 | error: error.message 24 | }) 25 | ) 26 | ) 27 | ) 28 | ) 29 | ) 30 | ); 31 | 32 | dispatchAppInit$ = createEffect(() => of(AppActions.init())); 33 | 34 | constructor( 35 | private actions: Actions, 36 | private shippingService: ShippingService 37 | ) {} 38 | } 39 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/shipping/shipping.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { EffectsModule } from '@ngrx/effects'; 3 | import { StoreModule } from '@ngrx/store'; 4 | import { ShippingEffects } from './shipping.effects'; 5 | import { reducer, shippingFeatureKey } from './shipping.reducer'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | StoreModule.forFeature(shippingFeatureKey, reducer), 10 | EffectsModule.forFeature([ShippingEffects]) 11 | ] 12 | }) 13 | export class ShippingStateModule {} 14 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/shipping/shipping.reducer.ts: -------------------------------------------------------------------------------- 1 | import { ShippingMethod } from '@ngrx-workshop-app/api-interface'; 2 | import { createEntityAdapter, EntityState } from '@ngrx/entity'; 3 | import { Action, createReducer, on } from '@ngrx/store'; 4 | import * as AppActions from '../app/app.actions'; 5 | import * as ShippingActions from './shipping.actions'; 6 | 7 | export interface ShippingState extends EntityState { 8 | selectedMethod: string | null; 9 | loading: boolean; 10 | error: string | null; 11 | } 12 | 13 | export const shippingFeatureKey = 'shipping'; 14 | 15 | export const shippingAdapter = createEntityAdapter({ 16 | selectId: shippingMethod => shippingMethod.type 17 | }); 18 | 19 | export const initialState: ShippingState = shippingAdapter.getInitialState({ 20 | selectedMethod: null, 21 | loading: false, 22 | error: null 23 | }); 24 | 25 | const shippingReducer = createReducer( 26 | initialState, 27 | on(AppActions.init, state => ({ ...state, loading: true })), 28 | on( 29 | ShippingActions.shippingApiOptionsLoadedSuccess, 30 | (state, { shippingMethods }) => ({ 31 | ...shippingAdapter.addAll(shippingMethods, state), 32 | loading: false 33 | }) 34 | ), 35 | on(ShippingActions.shippingApiOptionsLoadFailure, (state, { error }) => ({ 36 | ...state, 37 | error 38 | })), 39 | on( 40 | ShippingActions.shippingDialogSelectShippingMethod, 41 | (state, { shippingMethod }) => ({ 42 | ...state, 43 | selectedMethod: shippingMethod 44 | }) 45 | ) 46 | ); 47 | 48 | export function reducer(state: ShippingState | undefined, action: Action) { 49 | return shippingReducer(state, action); 50 | } 51 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/app/shared/state/shipping/shipping.selectors.ts: -------------------------------------------------------------------------------- 1 | import { createFeatureSelector, createSelector } from '@ngrx/store'; 2 | import { shippingAdapter, ShippingState } from './shipping.reducer'; 3 | 4 | export const NO_SHIPPING_METHOD_SELECTED_TOKEN = {}; 5 | Object.freeze(NO_SHIPPING_METHOD_SELECTED_TOKEN); 6 | 7 | const { selectAll, selectEntities } = shippingAdapter.getSelectors(); 8 | 9 | export const selectShippingFeatureSelector = createFeatureSelector< 10 | ShippingState 11 | >('shipping'); 12 | 13 | export const selectAllShippingOptions = createSelector( 14 | selectShippingFeatureSelector, 15 | selectAll 16 | ); 17 | 18 | export const selectSelectedShippingOption = createSelector( 19 | selectShippingFeatureSelector, 20 | state => state.selectedMethod || NO_SHIPPING_METHOD_SELECTED_TOKEN 21 | ); 22 | 23 | export const getShippingInvalid = createSelector( 24 | selectSelectedShippingOption, 25 | selected => selected === NO_SHIPPING_METHOD_SELECTED_TOKEN 26 | ); 27 | 28 | export const selectShippingEntities = createSelector( 29 | selectShippingFeatureSelector, 30 | selectEntities 31 | ); 32 | 33 | export const selectShippingCost = createSelector( 34 | selectShippingEntities, 35 | selectSelectedShippingOption, 36 | (entities, selected) => 37 | selected === NO_SHIPPING_METHOD_SELECTED_TOKEN 38 | ? 0 39 | : entities[selected as string].price 40 | ); 41 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/apps/ngrx-workshop-app/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` 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 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/apps/ngrx-workshop-app/src/favicon.ico -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NgrxWorkshopApp 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/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 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/styles.css: -------------------------------------------------------------------------------- 1 | @import '~@angular/material/prebuilt-themes/indigo-pink.css'; 2 | 3 | /* Global Styles */ 4 | 5 | * { 6 | font-family: 'Roboto', Arial, sans-serif; 7 | color: #616161; 8 | box-sizing: border-box; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | } 16 | 17 | .container { 18 | display: flex; 19 | flex-direction: row; 20 | } 21 | 22 | router-outlet + * { 23 | padding: 0 16px; 24 | } 25 | 26 | /* Text */ 27 | 28 | h1 { 29 | font-size: 32px; 30 | } 31 | 32 | h2 { 33 | font-size: 20px; 34 | } 35 | 36 | h1, 37 | h2 { 38 | font-weight: lighter; 39 | } 40 | 41 | p { 42 | font-size: 14px; 43 | } 44 | 45 | /* Hyperlink */ 46 | 47 | a { 48 | cursor: pointer; 49 | color: #1976d2; 50 | text-decoration: none; 51 | } 52 | 53 | a:hover { 54 | opacity: 0.8; 55 | } 56 | 57 | /* Input */ 58 | 59 | input { 60 | font-size: 14px; 61 | border-radius: 2px; 62 | padding: 8px; 63 | margin-bottom: 16px; 64 | border: 1px solid #bdbdbd; 65 | } 66 | 67 | label { 68 | font-size: 12px; 69 | font-weight: bold; 70 | margin-bottom: 4px; 71 | display: block; 72 | text-transform: uppercase; 73 | } 74 | 75 | /* Button */ 76 | .button, 77 | button { 78 | display: inline-flex; 79 | align-items: center; 80 | padding: 8px 16px; 81 | border-radius: 2px; 82 | font-size: 14px; 83 | cursor: pointer; 84 | background-color: #1976d2; 85 | color: white; 86 | border: none; 87 | } 88 | 89 | .button:hover, 90 | button:hover { 91 | opacity: 0.8; 92 | font-weight: normal; 93 | } 94 | 95 | .button:disabled, 96 | button:disabled { 97 | opacity: 0.5; 98 | cursor: auto; 99 | } 100 | 101 | /* Fancy Button */ 102 | 103 | .fancy-button { 104 | background-color: white; 105 | color: #1976d2; 106 | } 107 | 108 | .fancy-button i.material-icons { 109 | color: #1976d2; 110 | padding-right: 4px; 111 | } 112 | 113 | .fancy-button svg { 114 | padding-right: 4px; 115 | } 116 | 117 | .fancy-button svg path { 118 | stroke: #1976d2; 119 | fill: #1976d2; 120 | } 121 | 122 | .fancy-button.invalid { 123 | color: rgb(255, 55, 55); 124 | } 125 | 126 | .fancy-button.invalid svg path { 127 | stroke: rgb(255, 55, 55); 128 | fill: rgb(255, 55, 55); 129 | } 130 | 131 | /* Top Bar */ 132 | 133 | ngrx-workshop-app-top-bar { 134 | width: 100%; 135 | height: 68px; 136 | background-color: #1976d2; 137 | padding: 16px; 138 | display: flex; 139 | flex-direction: row; 140 | justify-content: space-between; 141 | align-items: center; 142 | color: white; 143 | margin: 0; 144 | } 145 | 146 | ngrx-workshop-app-top-bar h1 { 147 | color: white; 148 | margin: 0; 149 | } 150 | 151 | /* Checkout Cart, Shipping Prices */ 152 | 153 | .cart-item, 154 | .shipping-item { 155 | width: 100%; 156 | min-width: 400px; 157 | max-width: 450px; 158 | display: flex; 159 | flex-direction: row; 160 | justify-content: space-between; 161 | padding: 16px 32px; 162 | margin-bottom: 8px; 163 | border-radius: 2px; 164 | background-color: #eeeeee; 165 | } 166 | 167 | .shipping-item { 168 | cursor: pointer; 169 | } 170 | 171 | .shipping-item.selected { 172 | background-color: #1976d2; 173 | } 174 | 175 | .shipping-item.selected span { 176 | color: white; 177 | } 178 | 179 | simple-snack-bar span { 180 | color: white; 181 | } 182 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "include": ["**/*.ts"], 8 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 9 | "angularCompilerOptions": { 10 | "enableIvy": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/ngrx-workshop-app/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "ngrxWorkshopApp", "camelCase"], 5 | "component-selector": [true, "element", "ngrx-workshop-app", "kebab-case"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], 3 | transform: { 4 | '^.+\\.(ts|js|html)$': 'ts-jest' 5 | }, 6 | resolver: '@nrwl/jest/plugins/resolver', 7 | moduleFileExtensions: ['ts', 'js', 'html'], 8 | collectCoverage: true, 9 | coverageReporters: ['html'] 10 | }; 11 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/libs/.gitkeep -------------------------------------------------------------------------------- /libs/api-interface/README.md: -------------------------------------------------------------------------------- 1 | # api-interface 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test api-interface` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/api-interface/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'api-interface', 3 | preset: '../../jest.config.js', 4 | transform: { 5 | '^.+\\.[tj]sx?$': 'ts-jest' 6 | }, 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], 8 | coverageDirectory: '../../coverage/libs/api-interface' 9 | }; 10 | -------------------------------------------------------------------------------- /libs/api-interface/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/interfaces'; 2 | -------------------------------------------------------------------------------- /libs/api-interface/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Message { 2 | message: string; 3 | } 4 | 5 | export interface ShippingMethod { 6 | type: string; 7 | price: number; 8 | } 9 | 10 | export interface Product { 11 | productId: string; 12 | name: string; 13 | price: number; 14 | description: string; 15 | } 16 | 17 | export interface Item { 18 | productId: string; 19 | itemId: string; 20 | } 21 | 22 | export type ItemWithProduct = Item & { product: Product }; 23 | -------------------------------------------------------------------------------- /libs/api-interface/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /libs/api-interface/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "exclude": ["**/*.spec.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/api-interface/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.spec.tsx", 11 | "**/*.spec.js", 12 | "**/*.spec.jsx", 13 | "**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /libs/api-interface/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": [] 4 | } 5 | -------------------------------------------------------------------------------- /libs/cart-data-access/README.md: -------------------------------------------------------------------------------- 1 | # cart-data-access 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test cart-data-access` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/cart-data-access/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'cart-data-access', 3 | preset: '../../jest.config.js', 4 | coverageDirectory: '../../coverage/libs/cart-data-access', 5 | snapshotSerializers: [ 6 | 'jest-preset-angular/AngularSnapshotSerializer.js', 7 | 'jest-preset-angular/HTMLCommentSerializer.js' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /libs/cart-data-access/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/cart-data-access.module'; 2 | export { CartService } from './lib/cart.service'; 3 | -------------------------------------------------------------------------------- /libs/cart-data-access/src/lib/cart-data-access.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, TestBed } from '@angular/core/testing'; 2 | import { CartDataAccessModule } from './cart-data-access.module'; 3 | 4 | describe('CartDataAccessModule', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | imports: [CartDataAccessModule] 8 | }).compileComponents(); 9 | })); 10 | 11 | it('should create', () => { 12 | expect(CartDataAccessModule).toBeDefined(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /libs/cart-data-access/src/lib/cart-data-access.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [CommonModule] 6 | }) 7 | export class CartDataAccessModule {} 8 | -------------------------------------------------------------------------------- /libs/cart-data-access/src/lib/cart.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Product, Item } from '@ngrx-workshop-app/api-interface'; 4 | import { Observable } from 'rxjs'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class CartService { 10 | constructor(private http: HttpClient) {} 11 | 12 | getItems(): Observable { 13 | return this.http.get('api/cart'); 14 | } 15 | 16 | addToCart(productId: string): Observable { 17 | return this.http.post('api/cart', { productId }); 18 | } 19 | 20 | checkout(): Observable { 21 | return this.http.delete('api/cart'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libs/cart-data-access/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/cart-data-access/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /libs/cart-data-access/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "annotateForClosureCompiler": true, 13 | "skipTemplateCodegen": true, 14 | "strictMetadataEmit": true, 15 | "fullTemplateTypeCheck": true, 16 | "strictInjectionParameters": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": ["src/test.ts", "**/*.spec.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /libs/cart-data-access/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/cart-data-access/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "ngrxWorkshopApp", "camelCase"], 5 | "component-selector": [true, "element", "ngrx-workshop-app", "kebab-case"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/product-data-access/README.md: -------------------------------------------------------------------------------- 1 | # product-data-access 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test product-data-access` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/product-data-access/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'product-data-access', 3 | preset: '../../jest.config.js', 4 | coverageDirectory: '../../coverage/libs/product-data-access', 5 | snapshotSerializers: [ 6 | 'jest-preset-angular/AngularSnapshotSerializer.js', 7 | 'jest-preset-angular/HTMLCommentSerializer.js' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /libs/product-data-access/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/product-data-access.module'; 2 | export { ProductService } from './lib/product.service'; 3 | -------------------------------------------------------------------------------- /libs/product-data-access/src/lib/product-data-access.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, TestBed } from '@angular/core/testing'; 2 | import { ProductDataAccessModule } from './product-data-access.module'; 3 | 4 | describe('ProductDataAccessModule', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | imports: [ProductDataAccessModule] 8 | }).compileComponents(); 9 | })); 10 | 11 | it('should create', () => { 12 | expect(ProductDataAccessModule).toBeDefined(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /libs/product-data-access/src/lib/product-data-access.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [CommonModule] 6 | }) 7 | export class ProductDataAccessModule {} 8 | -------------------------------------------------------------------------------- /libs/product-data-access/src/lib/product.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Product } from '@ngrx-workshop-app/api-interface'; 4 | import { Observable } from 'rxjs'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class ProductService { 10 | constructor(private http: HttpClient) {} 11 | 12 | getProducts(): Observable { 13 | return this.http.get('api/products'); 14 | } 15 | 16 | getProduct(productId: number): Observable { 17 | return this.http.get(`api/products/${productId}`); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libs/product-data-access/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/product-data-access/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /libs/product-data-access/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "annotateForClosureCompiler": true, 13 | "skipTemplateCodegen": true, 14 | "strictMetadataEmit": true, 15 | "fullTemplateTypeCheck": true, 16 | "strictInjectionParameters": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": ["src/test.ts", "**/*.spec.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /libs/product-data-access/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/product-data-access/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "ngrxWorkshopApp", "camelCase"], 5 | "component-selector": [true, "element", "ngrx-workshop-app", "kebab-case"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/shipping-data-access/README.md: -------------------------------------------------------------------------------- 1 | # shipping-data-access 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test shipping-data-access` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/shipping-data-access/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'shipping-data-access', 3 | preset: '../../jest.config.js', 4 | coverageDirectory: '../../coverage/libs/shipping-data-access', 5 | snapshotSerializers: [ 6 | 'jest-preset-angular/AngularSnapshotSerializer.js', 7 | 'jest-preset-angular/HTMLCommentSerializer.js' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /libs/shipping-data-access/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/shipping-data-access.module'; 2 | export { ShippingService } from './lib/shipping.service'; 3 | -------------------------------------------------------------------------------- /libs/shipping-data-access/src/lib/shipping-data-access.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, TestBed } from '@angular/core/testing'; 2 | import { ShippingDataAccessModule } from './shipping-data-access.module'; 3 | 4 | describe('ShippingDataAccessModule', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | imports: [ShippingDataAccessModule] 8 | }).compileComponents(); 9 | })); 10 | 11 | it('should create', () => { 12 | expect(ShippingDataAccessModule).toBeDefined(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /libs/shipping-data-access/src/lib/shipping-data-access.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [CommonModule] 6 | }) 7 | export class ShippingDataAccessModule {} 8 | -------------------------------------------------------------------------------- /libs/shipping-data-access/src/lib/shipping.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { ShippingMethod } from '@ngrx-workshop-app/api-interface'; 4 | import { Observable } from 'rxjs'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class ShippingService { 10 | constructor(private http: HttpClient) {} 11 | 12 | getShippingPrices(): Observable { 13 | return this.http.get('api/shipping'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /libs/shipping-data-access/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/shipping-data-access/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["node", "jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /libs/shipping-data-access/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "annotateForClosureCompiler": true, 13 | "skipTemplateCodegen": true, 14 | "strictMetadataEmit": true, 15 | "fullTemplateTypeCheck": true, 16 | "strictInjectionParameters": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": ["src/test.ts", "**/*.spec.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /libs/shipping-data-access/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/shipping-data-access/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "ngrxWorkshopApp", "camelCase"], 5 | "component-selector": [true, "element", "ngrx-workshop-app", "kebab-case"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmScope": "ngrx-workshop-app", 3 | "implicitDependencies": { 4 | "angular.json": "*", 5 | "package.json": "*", 6 | "tsconfig.json": "*", 7 | "tslint.json": "*", 8 | "nx.json": "*" 9 | }, 10 | "projects": { 11 | "ngrx-workshop-app-e2e": { 12 | "tags": [] 13 | }, 14 | "ngrx-workshop-app": { 15 | "tags": [] 16 | }, 17 | "api": { 18 | "tags": [] 19 | }, 20 | "api-interface": { 21 | "tags": [] 22 | }, 23 | "cart-data-access": { 24 | "tags": [] 25 | }, 26 | "product-data-access": { 27 | "tags": [] 28 | }, 29 | "shipping-data-access": { 30 | "tags": [] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngrx-workshop-app", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng run ngrx-workshop-app:serve-with-api", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "nx lint && ng lint", 11 | "e2e": "ng e2e", 12 | "affected:apps": "nx affected:apps", 13 | "affected:libs": "nx affected:libs", 14 | "affected:build": "nx affected:build", 15 | "affected:e2e": "nx affected:e2e", 16 | "affected:test": "nx affected:test", 17 | "affected:lint": "nx affected:lint", 18 | "affected:dep-graph": "nx affected:dep-graph", 19 | "affected": "nx affected", 20 | "format": "nx format:write", 21 | "format:write": "nx format:write", 22 | "format:check": "nx format:check", 23 | "update": "ng update @nrwl/workspace", 24 | "update:check": "ng update", 25 | "workspace-schematic": "nx workspace-schematic", 26 | "dep-graph": "nx dep-graph", 27 | "help": "nx help" 28 | }, 29 | "private": true, 30 | "dependencies": { 31 | "@angular/animations": "^8.0.0", 32 | "@angular/cdk": "^8.1.1", 33 | "@angular/common": "^8.0.0", 34 | "@angular/compiler": "^8.0.0", 35 | "@angular/core": "^8.0.0", 36 | "@angular/forms": "^8.0.0", 37 | "@angular/material": "^8.1.1", 38 | "@angular/platform-browser": "^8.0.0", 39 | "@angular/platform-browser-dynamic": "^8.0.0", 40 | "@angular/router": "^8.0.0", 41 | "@nestjs/common": "^6.2.4", 42 | "@nestjs/core": "^6.2.4", 43 | "@nestjs/platform-express": "^6.2.4", 44 | "@ngrx/effects": "8.1.0", 45 | "@ngrx/entity": "8.1.0", 46 | "@ngrx/router-store": "8.1.0", 47 | "@ngrx/store": "8.1.0", 48 | "@nrwl/angular": "8.2.0", 49 | "core-js": "^2.5.4", 50 | "reflect-metadata": "^0.1.12", 51 | "rxjs": "~6.5.2", 52 | "zone.js": "^0.9.1" 53 | }, 54 | "devDependencies": { 55 | "@angular-devkit/build-angular": "^0.800.0", 56 | "@angular/cli": "8.0.0", 57 | "@angular/compiler-cli": "^8.0.0", 58 | "@angular/language-service": "^8.0.0", 59 | "rxjs": "~6.5.2", 60 | "zone.js": "^0.9.1", 61 | "@nestjs/schematics": "^6.3.0", 62 | "@nestjs/testing": "^6.2.4", 63 | "@ngrx/schematics": "^8.1.0", 64 | "@ngrx/store-devtools": "8.1.0", 65 | "@nrwl/angular": "8.2.0", 66 | "@nrwl/cypress": "8.2.0", 67 | "@nrwl/jest": "8.2.0", 68 | "@nrwl/nest": "8.2.0", 69 | "@nrwl/node": "8.2.0", 70 | "@nrwl/workspace": "8.2.0", 71 | "@types/jest": "24.0.9", 72 | "@types/node": "~8.9.4", 73 | "codelyzer": "~5.0.1", 74 | "cypress": "~3.3.1", 75 | "dotenv": "6.2.0", 76 | "jest": "24.1.0", 77 | "jest-preset-angular": "7.0.0", 78 | "prettier": "1.16.4", 79 | "ts-jest": "24.0.0", 80 | "ts-node": "~7.0.0", 81 | "tslint": "~5.11.0", 82 | "typescript": "~3.4.5" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tools/schematics/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrwl/ngrx-workshop-app/a30affbee50c5d0959b8dda56ec74106ad6d67ee/tools/schematics/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"] 9 | }, 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "typeRoots": ["node_modules/@types"], 14 | "lib": ["es2017", "dom"], 15 | "skipLibCheck": true, 16 | "skipDefaultLibCheck": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@ngrx-workshop-app/api-interface": ["libs/api-interface/src/index.ts"], 20 | "@ngrx-workshop-app/cart-data-access": [ 21 | "libs/cart-data-access/src/index.ts" 22 | ], 23 | "@ngrx-workshop-app/product-data-access": [ 24 | "libs/product-data-access/src/index.ts" 25 | ], 26 | "@ngrx-workshop-app/shipping-data-access": [ 27 | "libs/shipping-data-access/src/index.ts" 28 | ], 29 | "@ngrx-workshop-app/*": ["apps/ngrx-workshop-app/src/app/*"] 30 | } 31 | }, 32 | "exclude": ["node_modules", "tmp"] 33 | } 34 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/@nrwl/workspace/src/tslint", 4 | "node_modules/codelyzer" 5 | ], 6 | "rules": { 7 | "arrow-return-shorthand": true, 8 | "callable-types": true, 9 | "class-name": true, 10 | "deprecation": { 11 | "severity": "warn" 12 | }, 13 | "forin": true, 14 | "import-blacklist": [true, "rxjs/Rx"], 15 | "interface-over-type-literal": true, 16 | "member-access": false, 17 | "member-ordering": [ 18 | true, 19 | { 20 | "order": [ 21 | "static-field", 22 | "instance-field", 23 | "static-method", 24 | "instance-method" 25 | ] 26 | } 27 | ], 28 | "no-arg": true, 29 | "no-bitwise": true, 30 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 31 | "no-construct": true, 32 | "no-debugger": true, 33 | "no-duplicate-super": true, 34 | "no-empty": false, 35 | "no-empty-interface": true, 36 | "no-eval": true, 37 | "no-inferrable-types": [true, "ignore-params"], 38 | "no-misused-new": true, 39 | "no-non-null-assertion": true, 40 | "no-shadowed-variable": true, 41 | "no-string-literal": false, 42 | "no-string-throw": true, 43 | "no-switch-case-fall-through": true, 44 | "no-unnecessary-initializer": true, 45 | "no-unused-expression": true, 46 | "no-var-keyword": true, 47 | "object-literal-sort-keys": false, 48 | "prefer-const": true, 49 | "radix": true, 50 | "triple-equals": [true, "allow-null-check"], 51 | "unified-signatures": true, 52 | "variable-name": false, 53 | "nx-enforce-module-boundaries": [ 54 | true, 55 | { 56 | "allow": [], 57 | "depConstraints": [ 58 | { 59 | "sourceTag": "*", 60 | "onlyDependOnLibsWithTags": ["*"] 61 | } 62 | ] 63 | } 64 | ], 65 | "directive-selector": [true, "attribute", "app", "camelCase"], 66 | "component-selector": [true, "element", "app", "kebab-case"], 67 | "no-conflicting-lifecycle": true, 68 | "no-host-metadata-property": true, 69 | "no-input-rename": true, 70 | "no-inputs-metadata-property": true, 71 | "no-output-native": true, 72 | "no-output-on-prefix": true, 73 | "no-output-rename": true, 74 | "no-outputs-metadata-property": true, 75 | "template-banana-in-box": true, 76 | "template-no-negated-async": true, 77 | "use-lifecycle-interface": true, 78 | "use-pipe-transform-interface": true 79 | } 80 | } 81 | --------------------------------------------------------------------------------