├── .browserslistrc ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── server └── db.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ ├── app.module.ts │ ├── material.module.ts │ ├── pages │ │ ├── checkout │ │ │ ├── checkout-routing.module.ts │ │ │ ├── checkout.component.html │ │ │ ├── checkout.component.scss │ │ │ ├── checkout.component.ts │ │ │ ├── checkout.module.ts │ │ │ ├── details │ │ │ │ ├── details.component.html │ │ │ │ ├── details.component.scss │ │ │ │ ├── details.component.spec.ts │ │ │ │ └── details.component.ts │ │ │ └── thank-you-page │ │ │ │ ├── thank-you-page-routing.module.ts │ │ │ │ ├── thank-you-page.component.scss │ │ │ │ ├── thank-you-page.component.ts │ │ │ │ └── thank-you-page.module.ts │ │ └── products │ │ │ ├── interfaces │ │ │ └── product.interface.ts │ │ │ ├── product │ │ │ ├── product.component.html │ │ │ ├── product.component.scss │ │ │ └── product.component.ts │ │ │ ├── products-routing.module.ts │ │ │ ├── products.component.scss │ │ │ ├── products.component.ts │ │ │ ├── products.module.ts │ │ │ └── services │ │ │ └── products.service.ts │ └── shared │ │ ├── components │ │ ├── cart │ │ │ └── cart.component.ts │ │ └── header │ │ │ ├── header.component.scss │ │ │ └── header.component.ts │ │ ├── interfaces │ │ ├── order.interface.ts │ │ └── stores.interface.ts │ │ └── services │ │ ├── data.service.ts │ │ └── shopping-cart.service.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss └── test.ts ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # misc 34 | /.sass-cache 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | npm-debug.log 39 | yarn-error.log 40 | testem.log 41 | /typings 42 | 43 | # System Files 44 | .DS_Store 45 | Thumbs.db 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Alt text](https://i.ytimg.com/vi/i-oYrcNtc2s/hqdefault.jpg "dominicode") 2 | 3 | # Curso de Angular 12 4 | 5 | En este curso de Angular 12 aprenderás a trabajar con Angular. 6 | Empezamos desde cero explicando ¿Qué es Angular? hasta realizar una aplicación básica de Angular. 7 | Este proyecto fue creado con [Angular CLI](https://github.com/angular/angular-cli) en la versión 12.0.1. 8 | 9 | ## 🔖 Temario del curso: 10 | 11 | - 0:00:00 Intro 12 | - 0:00:20 Angular 12 : ¿Qué es Angular? 13 | - 0:01:10 Angular 12 : ¿Qué es Angular CLI? 14 | - 0:01:45 Angular 12 : Como instalar Angular CLI & prerrequisitos 15 | - 0:03:53 Angular 12 : Instalando el Angular CLI 16 | - 0:05:48 Angular 12 : Creando tu primera aplicación de Angular 17 | - 0:07:25 Angular 12 : Estructura de carpetas y ficheros de Angular 18 | - 0:13:08 Angular 12 : app.module de Angular [declaration, imports, providers, bootstrap, exports] 19 | - 0:14:53 Angular 12 : assets y enviroments 20 | - 0:15:27 Angular 12 : ¿Qué es un componente en Angular? 21 | - 0:16:32 Angular 12 : Métodos y propiedades en TypeScript 22 | - 0:16:32 Angular 12 : Interpolación, One-way binding, Two-way binding 23 | - 0:22:16 Angular 12 : Creando tu primer componente en Angular y como reutilizar componentes 24 | - 0:27:45 Angular 12 : Instalamos json-server npm i -g json-server 25 | - 0:30:00 Angular 12 : Configuramos el server en el package.json 26 | - 0:33:30 Angular 12 : Instalar Angular material 27 | - 0:36:20 Angular 12 : Agregamos el modulo para Angular material 28 | - 0:40:50 Angular 12 : Componente Inline Angular 29 | - 0:42:42 Angular 12 : Rutas en Angular, 30 | - 0:45:00 Angular 12 : Creamos un componente manual para nuestra ruta 31 | - 0:50:30 Angular 12 : Nuestra primera ruta en Angular 32 | - 0:54:10 Angular 12 : Creamos el modulo de Products 33 | - 0:58:35 Angular 12 : Reto 34 | - 0:59:53 Angular 12 : Solución al Reto crear nuevo componente Product 35 | - 1:01:01 Angular 12 : ¿Qué es una Interface en TypeScript? 36 | - 1:01:50 Angular 12 : Creamos la Interface de Product 37 | - 1:04:24 Angular 12 : Creamos el Service de Products 38 | - 1:06:33 Angular 12 : ¿Qué es un Service en Angular? 39 | - 1:08:00 Angular 12 : HttpClientModule en Angular 40 | - 1:09:40 Angular 12 : Tu primera petición Http con Angular - getProducts() 41 | - 1:13:15 Angular 12 : Recibiendo data desde la API 42 | - 1:16:05 Promo 43 | - 1:16:30 Angular 12 : Renderizamos los productos en el HTML con el pipe JSON 44 | - 1:18:35 Angular 12 : ¿Qué es un Pipe? 45 | - 1:19:34 Angular 12 : Empezamos a maquetar el listado de productos 46 | - 1:19:34 Angular 12 : mat-card de Angular Material 47 | - 1:23:44 Angular 12 : Directiva \*ngFor de Angular 48 | - 1:24:00 Angular 12 : Tipos de Directivas de Angular 49 | - 1:24:30 Angular 12 : Utilizamos \*ngFor para renderizar nuestros productos 50 | - 1:26:23 Angular 12 : mat-button de Angular Material 51 | - 1:27:44 Angular 12 : mat-icon de Angular Material 52 | - 1:30:30 Angular 12 : Maquetamos el product Card 53 | - 1:34:50 Angular 12 : Empezamos a trabajar con el componente Product 54 | - 1:35:37 Angular 12 : Comunicación entre componentes en Angular @Input 55 | - 1:39:40 Angular 12 : ¿Qué es el decorador @Output? 56 | - 1:41:10 Angular 12 : Usando el decorador @Output, comunicación entre componentes en Angular @Output 57 | - 1:48:44 Angular 12 : Resumen @Input y @output en Angular 58 | - 1:49:01 Promo 59 | - 1:49:23 Angular 12 : Creamos el ShoppingCartService 60 | - 1:59:28 Angular 12 : Utilizamos el ShoppingCartService 61 | - 2:04:30 Angular 12 : Creamos un nuevo component CartComponent 62 | - 2:14:50 Angular 12 : ¿Qué es el Change Detection en Angular? 63 | - 2:16:30 Angular 12 : Creamos el módulo de Checkout 64 | - 2:18:30 Angular 12 : Creamos el formulario con Angular Material para nuestro checkout Template driven form 65 | - 2:41:51 Angular 12 : Modificamos el header component 66 | - 2:49:30 Angular 12 : Hacemos un redirect a la página de productos 67 | - 2:51:01 Angular 12 : Trabajamos con el details de nuestro pedido, en la página de checkout 68 | - 2:59:15 Angular 12 : Utilizamos los observables del ShoppingCartService (cart$ y total$) 69 | - 3:01:30 Angular 12 : Renderizamos en el html los observables cart$ y total$ 70 | - 3:05:00 Angular 12 : Refactorizamos el ShoppingCartService usamos BehaviourSubject 71 | - 3:08:13 Angular 12 : Refactorizamos metodos de el ShoppingCartService 72 | - 3:14:50 Angular 12 : Trabajamos con el Template driven form 73 | - 3:18:11 Angular 12 : Recuperamos las tiendas 74 | - 3:21:40 Angular 12 : Creamos las interface Store 75 | - 3:23:12 Angular 12 : Trabajamos en el checkout modulo para renderizar las Stores. 76 | - 3:27:28 Angular 12 : Mostramos ocultamos recoger en tienda 77 | - 3:32:30 Angular 12 : Guardamos Formulario 78 | - 3:34:50 Angular 12 : Creamos el método saveOrder en el data service 79 | - 3:37:25 Angular 12 : Creamos el método DetailsOrder en el data service 80 | - 3:38:50 Angular 12 : Creamos las Order Interface - (Details, Order, DetailsOrder) 81 | - 3:43:25 Angular 12 : LLamamos a nuestro método del service desde el checkout 82 | - 3:51:04 Angular 12 : Creamos el método prepareDetails 83 | - 3:52:30 Angular 12 : Creamos el método getDataCart 84 | - 4:04:50 Angular 12 : Creamos thank you page module 85 | - 4:08:30 Angular 12 : Creamos ResetCart() 86 | - 4:14:38 Angular 12 : Deshabilitamos el buton add to cart si no hay stock 87 | - 4:16:29 Angular 12 : Gestionamos el stock de productos 88 | - 4:24:25 Angular 12 : Estilos y maquetación para la thank you page 89 | - 4:31:30 Angular 12 : Verificar si el carrito está vacio y redirigir a la página de productos 90 | - 4:31:30 Angular 12 : Despedida 91 | 92 | ### Empezar el Curso en Youtube 93 | 94 | - [Quiero aprender Angular 12](https://youtu.be/i-oYrcNtc2s) 95 | 96 | ### Prerrequisitos 97 | 98 | Angular requiere una [versión LTS activa](https://nodejs.org/en/about/releases/) o LTS en mantenimiento de Node.js. 99 | 100 | Te recomiendo como editor de códigos [Visual studio code](https://code.visualstudio.com/), pero puedes utilizar cualquiera. 101 | 102 | Estar familiarizado con: 103 | 104 | - [HTML](https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML) 105 | - [CSS](https://developer.mozilla.org/en-US/docs/Learn/CSS/First_steps) 106 | - [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript) 107 | - [TypeScript](https://www.typescriptlang.org/) - (no obligatorio) 108 | 109 | ## Server de desarrollo 110 | 111 | Una vez clonado este repositorio, debe instalar las dependencias. 112 | 113 | 1. `npm install` 114 | 2. `cd curso-angular-12` 115 | 116 | #### Ejecute el backend 117 | 118 | 3. `npm run serverAPI` 119 | 120 | #### Front-end 121 | 122 | 4. `ng serve` 123 | Ahora abres tu navegador y visita `http://localhost:4200/`. 124 | La aplicación se recargará automáticamente si cambia alguno de los archivos de origen. 125 | 126 | ## Build 127 | 128 | Ejecuta `ng build` para generar el proyecto. 129 | Los ficheros generados estarán en la carpeta `dist/`. 130 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "cli": { 4 | "analytics": false 5 | }, 6 | "version": 1, 7 | "newProjectRoot": "projects", 8 | "projects": { 9 | "store": { 10 | "projectType": "application", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | }, 15 | "@schematics/angular:application": { 16 | "strict": true 17 | } 18 | }, 19 | "root": "", 20 | "sourceRoot": "src", 21 | "prefix": "app", 22 | "architect": { 23 | "build": { 24 | "builder": "@angular-devkit/build-angular:browser", 25 | "options": { 26 | "outputPath": "dist/store", 27 | "index": "src/index.html", 28 | "main": "src/main.ts", 29 | "polyfills": "src/polyfills.ts", 30 | "tsConfig": "tsconfig.app.json", 31 | "inlineStyleLanguage": "scss", 32 | "assets": [ 33 | "src/favicon.ico", 34 | "src/assets" 35 | ], 36 | "styles": [ 37 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 38 | "src/styles.scss" 39 | ], 40 | "scripts": [] 41 | }, 42 | "configurations": { 43 | "production": { 44 | "budgets": [ 45 | { 46 | "type": "initial", 47 | "maximumWarning": "500kb", 48 | "maximumError": "1mb" 49 | }, 50 | { 51 | "type": "anyComponentStyle", 52 | "maximumWarning": "2kb", 53 | "maximumError": "4kb" 54 | } 55 | ], 56 | "fileReplacements": [ 57 | { 58 | "replace": "src/environments/environment.ts", 59 | "with": "src/environments/environment.prod.ts" 60 | } 61 | ], 62 | "outputHashing": "all" 63 | }, 64 | "development": { 65 | "buildOptimizer": false, 66 | "optimization": false, 67 | "vendorChunk": true, 68 | "extractLicenses": false, 69 | "sourceMap": true, 70 | "namedChunks": true 71 | } 72 | }, 73 | "defaultConfiguration": "production" 74 | }, 75 | "serve": { 76 | "builder": "@angular-devkit/build-angular:dev-server", 77 | "configurations": { 78 | "production": { 79 | "browserTarget": "store:build:production" 80 | }, 81 | "development": { 82 | "browserTarget": "store:build:development" 83 | } 84 | }, 85 | "defaultConfiguration": "development" 86 | }, 87 | "extract-i18n": { 88 | "builder": "@angular-devkit/build-angular:extract-i18n", 89 | "options": { 90 | "browserTarget": "store:build" 91 | } 92 | }, 93 | "test": { 94 | "builder": "@angular-devkit/build-angular:karma", 95 | "options": { 96 | "main": "src/test.ts", 97 | "polyfills": "src/polyfills.ts", 98 | "tsConfig": "tsconfig.spec.json", 99 | "karmaConfig": "karma.conf.js", 100 | "inlineStyleLanguage": "scss", 101 | "assets": [ 102 | "src/favicon.ico", 103 | "src/assets" 104 | ], 105 | "styles": [ 106 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 107 | "src/styles.scss" 108 | ], 109 | "scripts": [] 110 | } 111 | } 112 | } 113 | } 114 | }, 115 | "defaultProject": "store" 116 | } 117 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/store'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "store", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "serverAPI": "json-server --watch server/db.json --port 3000" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~12.0.1", 15 | "@angular/cdk": "^12.0.1", 16 | "@angular/common": "~12.0.1", 17 | "@angular/compiler": "~12.0.1", 18 | "@angular/core": "~12.0.1", 19 | "@angular/forms": "~12.0.1", 20 | "@angular/material": "^12.0.1", 21 | "@angular/platform-browser": "~12.0.1", 22 | "@angular/platform-browser-dynamic": "~12.0.1", 23 | "@angular/router": "~12.0.1", 24 | "rxjs": "~6.6.0", 25 | "tslib": "^2.1.0", 26 | "zone.js": "~0.11.4" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~12.0.1", 30 | "@angular/cli": "~12.0.1", 31 | "@angular/compiler-cli": "~12.0.1", 32 | "@types/jasmine": "~3.6.0", 33 | "@types/node": "^12.11.1", 34 | "jasmine-core": "~3.7.0", 35 | "karma": "~6.3.0", 36 | "karma-chrome-launcher": "~3.1.0", 37 | "karma-coverage": "~2.0.3", 38 | "karma-jasmine": "~4.0.0", 39 | "karma-jasmine-html-reporter": "^1.5.0", 40 | "typescript": "~4.2.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "stores": [ 3 | { 4 | "id": 1, 5 | "name": "Park Row at Beekman St", 6 | "address": "38 Park Row", 7 | "city": "New York", 8 | "openingHours": "10:00 - 14:00 and 17:00 - 20:30" 9 | }, 10 | { 11 | "id": 2, 12 | "name": "Store Alcalá", 13 | "address": "Calle de Alcalá, 21", 14 | "city": "Madrid", 15 | "openingHours": "10:00 - 14:00 and 17:00 - 20:30" 16 | }, 17 | { 18 | "id": 3, 19 | "name": "Chambers and West Broadway", 20 | "address": "125 Chambers Street", 21 | "city": "New York", 22 | "openingHours": "10:00 - 14:00 and 17:00 - 20:30" 23 | }, 24 | { 25 | "id": 4, 26 | "name": "Covent Garden - Russell St", 27 | "address": "10 Russell Street", 28 | "city": "London", 29 | "openingHours": "10:00 - 14:00 and 17:00 - 20:30" 30 | }, 31 | { 32 | "id": 5, 33 | "name": "Monmouth St", 34 | "address": "11 Monmouth Street", 35 | "city": "London", 36 | "openingHours": "10:00 - 14:00 and 17:00 - 20:30" 37 | } 38 | ], 39 | "categories": [ 40 | { 41 | "id": 1, 42 | "name": "Books" 43 | }, 44 | { 45 | "id": 2, 46 | "name": "Electronics" 47 | }, 48 | { 49 | "id": 3, 50 | "name": "Computers" 51 | } 52 | ], 53 | "products": [ 54 | { 55 | "id": 1, 56 | "name": "Essential TypeScript 4: From Beginner to Pro", 57 | "price": 45, 58 | "description": "Learn the essentials and more of TypeScript, a popular superset of the JavaScript language that adds support for static typing. TypeScript combines the typing features of C# or Java.", 59 | "categoryId": 1, 60 | "stock": 0 61 | }, 62 | { 63 | "id": 2, 64 | "name": "Hackeando el cerebro de tus compradores: PsychoGrowth", 65 | "price": 5, 66 | "description": "En Hackeando del cerebro de tus compradores, Corti nos revela cómo muchas compañías crean productos digitales o procesos de venta capaces de conectar con la psicología del comprador.", 67 | "categoryId": 1, 68 | "stock": 10 69 | }, 70 | { 71 | "id": 3, 72 | "name": "Angular Routing: Learn To Create client-side and SPA with Routing and Navigation", 73 | "price": 17, 74 | "description": "In this book, the reader will be able to focus on one concept of Angular in deep.", 75 | "categoryId": 1, 76 | "stock": 10 77 | }, 78 | { 79 | "id": 4, 80 | "name": "SanDisk 128GB Ultra MicroSDXC UHS-I Memory Card with Adapter", 81 | "price": 19, 82 | "description": "Ideal for Android smartphones and tablets, and MIL cameras. SanDisk Memory Zone app for easy file management (Download and Installation Required).", 83 | "categoryId": 2, 84 | "stock": 10 85 | }, 86 | { 87 | "id": 5, 88 | "name": "GoPro HERO9 Black - Waterproof Action Camera with Front LCD", 89 | "price": 399, 90 | "description": "5K Video - Shoot stunning video with up to 5K resolution, perfect for maintaining detail even when zooming in", 91 | "categoryId": 2, 92 | "stock": 10 93 | }, 94 | { 95 | "id": 6, 96 | "name": "CL3 Rated High-Speed 4K HDMI Cable - 6 Feet", 97 | "price": 7, 98 | "description": "HDMI A Male to A Male Cable: Supports Ethernet, 3D, 4K video and Audio Return Channel (ARC)", 99 | "categoryId": 2, 100 | "stock": 10 101 | }, 102 | { 103 | "id": 7, 104 | "name": "Logitech MK270 Wireless Keyboard and Mouse Combo", 105 | "price": 32, 106 | "description": "The USB receiver is conveniently located in the box, top flap. This wireless keyboard and mouse feature Logitech Advanced 2.4GHz technology with a range of up to 10 metres.", 107 | "categoryId": 3, 108 | "stock": 10 109 | }, 110 | { 111 | "id": 8, 112 | "name": "External CD Drive USB 3.0 Portable CD DVD +/-RW Drive DVD/CD ROM", 113 | "price": 20, 114 | "description": "Plug & play. Easy to use,powered by USB port. No external driver and Power needed. Just plug it into your USB port and the DVD driver will be detected", 115 | "categoryId": 3, 116 | "stock": 10 117 | } 118 | ], 119 | "orders": [ 120 | { 121 | "id": 1, 122 | "name": "Dominicode", 123 | "date": "01/12/1995", 124 | "shippingAddress": "Av. de la Granvia de Hospitalet, 115", 125 | "city": "Barcelona", 126 | "pickup": true 127 | } 128 | ], 129 | "detailsOrders": [ 130 | { 131 | "id": 1, 132 | "orderId": 1, 133 | "quantity": 10, 134 | "productName": "Product name" 135 | } 136 | ] 137 | } 138 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { path: '', redirectTo: '/products', pathMatch: 'full' }, 6 | { 7 | path: 'products', 8 | loadChildren: () => import('./pages/products/products.module').then(m => m.ProductsModule) 9 | }, 10 | { path: 'checkout', loadChildren: () => import('./pages/checkout/checkout.module').then(m => m.CheckoutModule) }, 11 | { path: '**', redirectTo: '', pathMatch: 'full' }, 12 | ]; 13 | 14 | @NgModule({ 15 | imports: [RouterModule.forRoot(routes)], 16 | exports: [RouterModule] 17 | }) 18 | export class AppRoutingModule { } 19 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domini-code/curso-angular-12/dd827e71b6d4427f23027996deafc303ce2588a8/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'Dominicode'; 10 | 11 | getName(): void { 12 | console.log('Hola Dominicode'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { CartComponent } from './shared/components/cart/cart.component'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | 6 | import { AppRoutingModule } from './app-routing.module'; 7 | import { AppComponent } from './app.component'; 8 | import { HeaderComponent } from './shared/components/header/header.component'; 9 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 10 | import { MaterialModule } from './material.module'; 11 | import { HttpClientModule } from '@angular/common/http'; 12 | @NgModule({ 13 | declarations: [ 14 | AppComponent, 15 | HeaderComponent, 16 | CartComponent 17 | ], 18 | imports: [ 19 | BrowserModule, 20 | AppRoutingModule, 21 | FormsModule, 22 | MaterialModule, 23 | BrowserAnimationsModule, 24 | HttpClientModule 25 | ], 26 | providers: [], 27 | bootstrap: [AppComponent] 28 | }) 29 | export class AppModule { } 30 | -------------------------------------------------------------------------------- /src/app/material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { MatToolbarModule } from '@angular/material/toolbar'; 3 | import { MatCardModule } from '@angular/material/card'; 4 | 5 | import { MatButtonModule } from '@angular/material/button'; 6 | import { MatIconModule } from '@angular/material/icon'; 7 | import { MatFormFieldModule } from '@angular/material/form-field'; 8 | import { MatRadioModule } from '@angular/material/radio'; 9 | import { MatInputModule } from '@angular/material/input'; 10 | import { MatSelectModule } from '@angular/material/select'; 11 | @NgModule({ 12 | exports: [ 13 | MatToolbarModule, 14 | MatCardModule, 15 | MatButtonModule, 16 | MatIconModule, 17 | MatFormFieldModule, 18 | MatRadioModule, 19 | MatInputModule, 20 | MatSelectModule, 21 | ] 22 | }) 23 | 24 | export class MaterialModule { } 25 | -------------------------------------------------------------------------------- /src/app/pages/checkout/checkout-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { CheckoutComponent } from './checkout.component'; 4 | 5 | const routes: Routes = [ 6 | { path: '', component: CheckoutComponent }, 7 | { 8 | path: 'thank-you-page', loadChildren: () => 9 | import('./thank-you-page/thank-you-page.module').then(m => m.ThankYouPageModule) 10 | }]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class CheckoutRoutingModule { } 17 | -------------------------------------------------------------------------------- /src/app/pages/checkout/checkout.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 12 | 13 | Name is required 14 | 15 | 16 | 17 |

Choose an option:

18 | Pickup 21 | 26 | Delivery 27 | 28 |
29 | 30 | 31 | 38 | 39 | 40 | 47 | 48 | 49 | 50 | 51 | Select me 52 | 53 | 54 | {{ tienda.name }} - {{ tienda.openingHours }} 55 | 56 | 57 | 58 | 59 |
60 |
61 | 62 |
63 | 73 |
74 | -------------------------------------------------------------------------------- /src/app/pages/checkout/checkout.component.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 6rem auto 0; 3 | width: 90%; 4 | } 5 | 6 | .form { 7 | &__element { 8 | margin-bottom: .5rem; 9 | width: 100%; 10 | } 11 | 12 | .mat-radio-group { 13 | display: block; 14 | } 15 | 16 | .mat-radio-button { 17 | margin-right: 1rem; 18 | } 19 | } 20 | 21 | .actions { 22 | margin: 4rem auto 0; 23 | width: 40%; 24 | 25 | &__btn { 26 | width: 100%; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/pages/checkout/checkout.component.ts: -------------------------------------------------------------------------------- 1 | import { ShoppingCartService } from './../../shared/services/shopping-cart.service'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Component, OnInit } from '@angular/core'; 4 | import { delay, switchMap, tap } from 'rxjs/operators'; 5 | import { Store } from 'src/app/shared/interfaces/stores.interface'; 6 | import { DataService } from 'src/app/shared/services/data.service'; 7 | import { Details, Order } from 'src/app/shared/interfaces/order.interface'; 8 | import { Product } from '../products/interfaces/product.interface'; 9 | import { Router } from '@angular/router'; 10 | import { ProductsService } from '../products/services/products.service'; 11 | 12 | @Component({ 13 | selector: 'app-checkout', 14 | templateUrl: './checkout.component.html', 15 | styleUrls: ['./checkout.component.scss'] 16 | }) 17 | export class CheckoutComponent implements OnInit { 18 | model = { 19 | name: 'Dominicode', 20 | store: '', 21 | shippingAddress: '', 22 | city: '' 23 | }; 24 | isDelivery = true; 25 | cart: Product[] = []; 26 | stores: Store[] = [] 27 | constructor( 28 | private dataSvc: DataService, 29 | private shoppingCartSvc: ShoppingCartService, 30 | private router: Router, 31 | private productsSvc: ProductsService 32 | ) { 33 | this.checkIfCartIsEmpty(); 34 | } 35 | 36 | ngOnInit(): void { 37 | this.getStores(); 38 | this.getDataCart(); 39 | this.prepareDetails(); 40 | } 41 | 42 | onPickupOrDelivery(value: boolean): void { 43 | this.isDelivery = value; 44 | } 45 | 46 | onSubmit({ value: formData }: NgForm): void { 47 | console.log('Guardar', formData); 48 | const data: Order = { 49 | ...formData, 50 | date: this.getCurrentDay(), 51 | isDelivery: this.isDelivery 52 | } 53 | this.dataSvc.saveOrder(data) 54 | .pipe( 55 | tap(res => console.log('Order ->', res)), 56 | switchMap(({ id: orderId }) => { 57 | const details = this.prepareDetails(); 58 | return this.dataSvc.saveDetailsOrder({ details, orderId }); 59 | }), 60 | tap(() => this.router.navigate(['/checkout/thank-you-page'])), 61 | delay(2000), 62 | tap(() => this.shoppingCartSvc.resetCart()) 63 | ) 64 | .subscribe(); 65 | } 66 | 67 | private getStores(): void { 68 | this.dataSvc.getStores() 69 | .pipe( 70 | tap((stores: Store[]) => this.stores = stores)) 71 | .subscribe() 72 | } 73 | 74 | private getCurrentDay(): string { 75 | return new Date().toLocaleDateString(); 76 | } 77 | 78 | private prepareDetails(): Details[] { 79 | const details: Details[] = []; 80 | this.cart.forEach((product: Product) => { 81 | const { id: productId, name: productName, qty: quantity, stock } = product; 82 | const updateStock = (stock - quantity); 83 | 84 | this.productsSvc.updateStock(productId, updateStock) 85 | .pipe( 86 | tap(() => details.push({ productId, productName, quantity })) 87 | ) 88 | .subscribe() 89 | 90 | 91 | }) 92 | return details; 93 | } 94 | 95 | private getDataCart(): void { 96 | this.shoppingCartSvc.cartAction$ 97 | .pipe( 98 | tap((products: Product[]) => this.cart = products) 99 | ) 100 | .subscribe() 101 | 102 | 103 | 104 | } 105 | 106 | private checkIfCartIsEmpty(): void { 107 | this.shoppingCartSvc.cartAction$ 108 | .pipe( 109 | tap((products: Product[]) => { 110 | if (Array.isArray(products) && !products.length) { 111 | this.router.navigate(['/products']); 112 | } 113 | }) 114 | ) 115 | .subscribe() 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /src/app/pages/checkout/checkout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { CheckoutRoutingModule } from './checkout-routing.module'; 5 | import { CheckoutComponent } from './checkout.component'; 6 | import { FormsModule } from '@angular/forms'; 7 | import { MaterialModule } from 'src/app/material.module'; 8 | import { DetailsComponent } from './details/details.component'; 9 | 10 | 11 | @NgModule({ 12 | declarations: [ 13 | CheckoutComponent, 14 | DetailsComponent 15 | ], 16 | imports: [ 17 | CommonModule, 18 | CheckoutRoutingModule, 19 | FormsModule, 20 | MaterialModule 21 | ] 22 | }) 23 | export class CheckoutModule { } 24 | -------------------------------------------------------------------------------- /src/app/pages/checkout/details/details.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
ItemQtyPrice unitSub total
{{ item.name }}{{ item.qty }}{{ item.price | currency }}{{ item.qty * item.price | currency }}
20 |

Total: {{ total$ | async | currency }}

21 |
22 | -------------------------------------------------------------------------------- /src/app/pages/checkout/details/details.component.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 2rem auto; 3 | width: 90%; 4 | } 5 | 6 | table { 7 | font-size: 1.2rem; 8 | line-height: 1.2; 9 | width: 100%; 10 | } 11 | 12 | th { 13 | background-color: #3f51b5; 14 | color: #fff; 15 | padding: 1rem; 16 | text-align: left; 17 | } 18 | 19 | td { 20 | background-color: #eee; 21 | padding: 1rem; 22 | } 23 | 24 | .total { 25 | color: #000; 26 | font-size: 1.5rem; 27 | font-weight: bold; 28 | line-height: 1.2; 29 | margin: 1rem 0 0; 30 | padding: 1rem; 31 | } 32 | -------------------------------------------------------------------------------- /src/app/pages/checkout/details/details.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DetailsComponent } from './details.component'; 4 | 5 | describe('DetailsComponent', () => { 6 | let component: DetailsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ DetailsComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DetailsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/pages/checkout/details/details.component.ts: -------------------------------------------------------------------------------- 1 | import { ShoppingCartService } from 'src/app/shared/services/shopping-cart.service'; 2 | import { Component, OnInit } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'app-details', 6 | templateUrl: './details.component.html', 7 | styleUrls: ['./details.component.scss'] 8 | }) 9 | export class DetailsComponent implements OnInit { 10 | total$ = this.shoppingCartSvc.totalAction$; 11 | cart$ = this.shoppingCartSvc.cartAction$; 12 | 13 | constructor(private shoppingCartSvc: ShoppingCartService) { } 14 | 15 | ngOnInit(): void { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/app/pages/checkout/thank-you-page/thank-you-page-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { ThankYouPageComponent } from './thank-you-page.component'; 4 | 5 | const routes: Routes = [{ path: '', component: ThankYouPageComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class ThankYouPageRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/pages/checkout/thank-you-page/thank-you-page.component.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 6rem auto 0; 3 | width: 90%; 4 | } 5 | 6 | .title { 7 | font-size: 5rem; 8 | line-height: 1.2; 9 | } 10 | 11 | .content { 12 | font-size: 1.5rem; 13 | line-height: 1.2; 14 | } 15 | -------------------------------------------------------------------------------- /src/app/pages/checkout/thank-you-page/thank-you-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-thank-you-page', 5 | template: ` 6 |
7 |

Thank you!

8 |

Your order is on the way!

9 | 10 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Quaerat quo autem 11 | ut facilis velit, laboriosam nihil, voluptates cum neque asperiores commodi 12 | sequi vel eveniet iure dicta suscipit sit inventore molestiae. 13 | 14 |
`, 15 | styleUrls: ['./thank-you-page.component.scss'] 16 | }) 17 | export class ThankYouPageComponent { 18 | } 19 | -------------------------------------------------------------------------------- /src/app/pages/checkout/thank-you-page/thank-you-page.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { ThankYouPageRoutingModule } from './thank-you-page-routing.module'; 5 | import { ThankYouPageComponent } from './thank-you-page.component'; 6 | 7 | 8 | @NgModule({ 9 | declarations: [ 10 | ThankYouPageComponent 11 | ], 12 | imports: [ 13 | CommonModule, 14 | ThankYouPageRoutingModule 15 | ] 16 | }) 17 | export class ThankYouPageModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/products/interfaces/product.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Product { 2 | id: number; 3 | name: string; 4 | price: number; 5 | description: string; 6 | categoryId: number; 7 | stock: number; 8 | qty: number; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/pages/products/product/product.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ product.name }} 3 | {{ product.description }} 4 | 5 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/pages/products/product/product.component.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | display: flex; 3 | flex-direction: column; 4 | margin: 1rem; 5 | max-width: 25rem; 6 | min-height: 10rem; 7 | min-width: 25rem; 8 | } 9 | 10 | .mat-card-title { 11 | line-height: 1.2; 12 | } 13 | 14 | .mat-card-actions { 15 | margin: 0; 16 | } 17 | 18 | .mat-card > .mat-card-actions:last-child { 19 | margin-bottom: 0; 20 | margin-top: auto; 21 | } 22 | 23 | button { 24 | width: 100%; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/pages/products/product/product.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Product } from '../interfaces/product.interface'; 3 | 4 | @Component({ 5 | selector: 'app-product', 6 | templateUrl: './product.component.html', 7 | styleUrls: ['./product.component.scss'], 8 | changeDetection: ChangeDetectionStrategy.OnPush 9 | }) 10 | export class ProductComponent { 11 | @Input() product!: Product; 12 | @Output() addToCartClick = new EventEmitter(); 13 | 14 | 15 | onClick(): void { 16 | this.addToCartClick.emit(this.product); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/app/pages/products/products-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { ProductsComponent } from './products.component'; 4 | 5 | const routes: Routes = [{ path: '', component: ProductsComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class ProductsRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/pages/products/products.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .products { 3 | align-items: center; 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: center; 7 | margin-top: 6rem; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/app/pages/products/products.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ProductsService } from './services/products.service'; 3 | import { tap } from 'rxjs/operators'; 4 | import { Product } from './interfaces/product.interface'; 5 | import { ShoppingCartService } from 'src/app/shared/services/shopping-cart.service'; 6 | 7 | @Component({ 8 | selector: 'app-products', 9 | template: ` 10 |
11 | 16 |
17 | `, 18 | styleUrls: ['./products.component.scss'] 19 | }) 20 | export class ProductsComponent implements OnInit { 21 | products!: Product[]; 22 | 23 | constructor(private productSvc: ProductsService, private shoppingCartSvc: ShoppingCartService) { } 24 | 25 | ngOnInit(): void { 26 | this.productSvc.getProducts() 27 | .pipe( 28 | tap((products: Product[]) => this.products = products) 29 | ) 30 | .subscribe(); 31 | } 32 | 33 | addToCart(product: Product): void { 34 | console.log('Add to cart', product); 35 | this.shoppingCartSvc.updateCart(product); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/app/pages/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { ProductsRoutingModule } from './products-routing.module'; 5 | import { ProductsComponent } from './products.component'; 6 | import { ProductComponent } from './product/product.component'; 7 | 8 | import { MaterialModule } from './../../material.module'; 9 | 10 | 11 | @NgModule({ 12 | declarations: [ 13 | ProductsComponent, 14 | ProductComponent 15 | ], 16 | imports: [ 17 | CommonModule, 18 | ProductsRoutingModule, 19 | MaterialModule 20 | ] 21 | }) 22 | export class ProductsModule { } 23 | -------------------------------------------------------------------------------- /src/app/pages/products/services/products.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { Product } from '../interfaces/product.interface'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class ProductsService { 10 | private apiURl = 'http://localhost:3000/products'; 11 | constructor(private http: HttpClient) { } 12 | 13 | getProducts(): Observable { 14 | return this.http.get(this.apiURl); 15 | } 16 | 17 | updateStock(productId: number, stock: number): Observable { 18 | const body = { "stock": stock }; 19 | return this.http.patch(`${this.apiURl}/${productId}`, body); 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/shared/components/cart/cart.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { ShoppingCartService } from 'src/app/shared/services/shopping-cart.service'; 3 | @Component({ 4 | selector: 'app-cart', 5 | template: ` 6 | 7 | 8 | add_shopping_cart 9 | {{dataCart.total | currency}} 10 | ({{dataCart.quantity}}) 11 | 12 | 13 | ` 14 | }) 15 | export class CartComponent { 16 | 17 | quantity$ = this.shoppingCartSvc.quantityAction$; 18 | total$ = this.shoppingCartSvc.totalAction$; 19 | constructor(private shoppingCartSvc: ShoppingCartService) { } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/shared/components/header/header.component.scss: -------------------------------------------------------------------------------- 1 | .main-header { 2 | display: flex; 3 | justify-content: space-between; 4 | left: 0; 5 | position: fixed; 6 | top: 0; 7 | z-index: 1; 8 | } 9 | 10 | a { 11 | color: #fff; 12 | text-decoration: none; 13 | } 14 | 15 | .shopping-cart { 16 | align-items: center; 17 | border-radius: .2rem; 18 | cursor: pointer; 19 | display: flex; 20 | padding: .1rem .5rem; 21 | transition: background-color .25s ease-in-out; 22 | 23 | &:hover { 24 | background-color: rgb(4,172, 194); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/shared/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Component } from '@angular/core'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-header', 7 | template: ` 8 | 9 | My Store 10 | 11 | `, 12 | styleUrls: ['./header.component.scss'] 13 | }) 14 | export class HeaderComponent { 15 | constructor(private router: Router) { } 16 | 17 | goToCheckout(): void { 18 | this.router.navigate(['/checkout']); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/shared/interfaces/order.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Details { 2 | productId: number; 3 | productName: string; 4 | quantity: number; 5 | } 6 | 7 | export interface Order { 8 | name: string; 9 | shippingAddress: string; 10 | city: string; 11 | date: string; 12 | isDelivery: boolean; 13 | id: number; 14 | } 15 | 16 | export interface DetailsOrder { 17 | details: Details[]; 18 | orderId: number; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/shared/interfaces/stores.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Store { 2 | id: number; 3 | name: string; 4 | address: string; 5 | city: string; 6 | openingHours: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/shared/services/data.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { Order, DetailsOrder } from '../interfaces/order.interface'; 5 | import { Store } from '../interfaces/stores.interface'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | 11 | export class DataService { 12 | private apiURL = 'http://localhost:3000'; 13 | constructor(private http: HttpClient) { } 14 | 15 | getStores(): Observable { 16 | return this.http.get(`${this.apiURL}/stores`) 17 | } 18 | 19 | saveOrder(order: Order): Observable { 20 | return this.http.post(`${this.apiURL}/orders`, order); 21 | } 22 | 23 | saveDetailsOrder(details: DetailsOrder): Observable { 24 | return this.http.post(`${this.apiURL}/detailsOrders`, details); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/app/shared/services/shopping-cart.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, BehaviorSubject } from 'rxjs'; 3 | import { Product } from 'src/app/pages/products/interfaces/product.interface'; 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class ShoppingCartService { 8 | products: Product[] = []; 9 | 10 | private cartSubject = new BehaviorSubject([]); 11 | private totalSubject = new BehaviorSubject(0); 12 | private quantitySubject = new BehaviorSubject(0); 13 | 14 | 15 | get totalAction$(): Observable { 16 | return this.totalSubject.asObservable(); 17 | } 18 | get quantityAction$(): Observable { 19 | return this.quantitySubject.asObservable(); 20 | } 21 | get cartAction$(): Observable { 22 | return this.cartSubject.asObservable(); 23 | } 24 | 25 | 26 | updateCart(product: Product): void { 27 | this.addToCart(product); 28 | this.quantityProducts(); 29 | this.calcTotal(); 30 | } 31 | 32 | resetCart(): void { 33 | this.cartSubject.next([]); 34 | this.totalSubject.next(0); 35 | this.quantitySubject.next(0); 36 | this.products = []; 37 | } 38 | 39 | private addToCart(product: Product): void { 40 | const isProductInCart = this.products.find(({ id }) => id === product.id) 41 | 42 | if (isProductInCart) { 43 | isProductInCart.qty += 1; 44 | } else { 45 | this.products.push({ ...product, qty: 1 }) 46 | } 47 | 48 | this.cartSubject.next(this.products); 49 | } 50 | 51 | private quantityProducts(): void { 52 | const quantity = this.products.reduce((acc, prod) => acc += prod.qty, 0); 53 | this.quantitySubject.next(quantity); 54 | } 55 | 56 | private calcTotal(): void { 57 | const total = this.products.reduce((acc, prod) => acc += (prod.price * prod.qty), 0); 58 | this.totalSubject.next(total); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domini-code/curso-angular-12/dd827e71b6d4427f23027996deafc303ce2588a8/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 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/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domini-code/curso-angular-12/dd827e71b6d4427f23027996deafc303ce2588a8/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Store 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /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().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js'; // Included with Angular CLI. 61 | 62 | 63 | /*************************************************************************************************** 64 | * APPLICATION IMPORTS 65 | */ 66 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "sourceMap": true, 12 | "declaration": false, 13 | "downlevelIteration": true, 14 | "experimentalDecorators": true, 15 | "moduleResolution": "node", 16 | "importHelpers": true, 17 | "target": "es2017", 18 | "module": "es2020", 19 | "lib": [ 20 | "es2018", 21 | "dom" 22 | ] 23 | }, 24 | "angularCompilerOptions": { 25 | "enableI18nLegacyMessageIdFormat": false, 26 | "strictInjectionParameters": true, 27 | "strictInputAccessModifiers": true, 28 | "strictTemplates": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------