├── .editorconfig ├── .gitignore ├── .vscode └── settings.json ├── INSTALL.md ├── README.md ├── angular.json ├── browserslist ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── admin │ │ ├── admin-routing.module.ts │ │ ├── admin.module.ts │ │ └── components │ │ │ ├── dashboard │ │ │ ├── dashboard.component.html │ │ │ ├── dashboard.component.scss │ │ │ ├── dashboard.component.spec.ts │ │ │ └── dashboard.component.ts │ │ │ ├── form-product │ │ │ ├── form-product.component.html │ │ │ ├── form-product.component.scss │ │ │ ├── form-product.component.spec.ts │ │ │ └── form-product.component.ts │ │ │ ├── list-products │ │ │ ├── list-products-datasource.ts │ │ │ ├── list-products.component.html │ │ │ ├── list-products.component.scss │ │ │ ├── list-products.component.spec.ts │ │ │ └── list-products.component.ts │ │ │ ├── nav │ │ │ ├── nav.component.html │ │ │ ├── nav.component.scss │ │ │ ├── nav.component.spec.ts │ │ │ └── nav.component.ts │ │ │ ├── product-edit │ │ │ ├── product-edit.component.html │ │ │ ├── product-edit.component.scss │ │ │ ├── product-edit.component.spec.ts │ │ │ └── product-edit.component.ts │ │ │ ├── product-form │ │ │ ├── product-form.component.html │ │ │ ├── product-form.component.scss │ │ │ ├── product-form.component.spec.ts │ │ │ └── product-form.component.ts │ │ │ └── products-list │ │ │ ├── products-list.component.html │ │ │ ├── products-list.component.scss │ │ │ ├── products-list.component.spec.ts │ │ │ └── products-list.component.ts │ ├── app-routing.module.ts │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── components │ │ ├── cart │ │ │ ├── cart.component.html │ │ │ ├── cart.component.scss │ │ │ ├── cart.component.spec.ts │ │ │ └── cart.component.ts │ │ └── not-found │ │ │ ├── not-found.component.html │ │ │ ├── not-found.component.scss │ │ │ ├── not-found.component.spec.ts │ │ │ └── not-found.component.ts │ ├── contact │ │ ├── components │ │ │ ├── contact.component.html │ │ │ ├── contact.component.scss │ │ │ ├── contact.component.spec.ts │ │ │ └── contact.component.ts │ │ ├── contact-routing.module.ts │ │ └── contact.module.ts │ ├── core │ │ ├── core.module.ts │ │ └── services │ │ │ ├── cart.service.spec.ts │ │ │ ├── cart.service.ts │ │ │ └── products │ │ │ ├── products.service.spec.ts │ │ │ └── products.service.ts │ ├── guardians │ │ ├── admin.guard.spec.ts │ │ └── admin.guard.ts │ ├── home │ │ ├── components │ │ │ ├── banner │ │ │ │ ├── banner.component.html │ │ │ │ ├── banner.component.scss │ │ │ │ ├── banner.component.spec.ts │ │ │ │ └── banner.component.ts │ │ │ └── home │ │ │ │ ├── home.component.html │ │ │ │ ├── home.component.scss │ │ │ │ ├── home.component.spec.ts │ │ │ │ └── home.component.ts │ │ ├── home-routing.module.ts │ │ └── home.module.ts │ ├── layout │ │ ├── layout.component.html │ │ ├── layout.component.scss │ │ ├── layout.component.spec.ts │ │ └── layout.component.ts │ ├── material │ │ └── material.module.ts │ ├── order │ │ ├── components │ │ │ └── order │ │ │ │ ├── order.component.html │ │ │ │ ├── order.component.scss │ │ │ │ ├── order.component.spec.ts │ │ │ │ └── order.component.ts │ │ ├── order-routing.module.ts │ │ └── order.module.ts │ ├── product.model.ts │ ├── products │ │ ├── components │ │ │ ├── product-detail │ │ │ │ ├── product-detail.component.html │ │ │ │ ├── product-detail.component.scss │ │ │ │ ├── product-detail.component.spec.ts │ │ │ │ └── product-detail.component.ts │ │ │ ├── product │ │ │ │ ├── product.component.html │ │ │ │ ├── product.component.scss │ │ │ │ └── product.component.ts │ │ │ └── products │ │ │ │ ├── products.component.html │ │ │ │ ├── products.component.scss │ │ │ │ ├── products.component.spec.ts │ │ │ │ └── products.component.ts │ │ ├── product-routing.module.ts │ │ └── products.module.ts │ ├── shared │ │ ├── cart │ │ │ ├── cart.component.html │ │ │ ├── cart.component.scss │ │ │ ├── cart.component.spec.ts │ │ │ └── cart.component.ts │ │ ├── components │ │ │ ├── footer │ │ │ │ ├── footer.component.html │ │ │ │ ├── footer.component.scss │ │ │ │ ├── footer.component.spec.ts │ │ │ │ └── footer.component.ts │ │ │ └── header │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.scss │ │ │ │ ├── header.component.spec.ts │ │ │ │ └── header.component.ts │ │ ├── directives │ │ │ └── highlight │ │ │ │ ├── highlight.directive.spec.ts │ │ │ │ └── highlight.directive.ts │ │ ├── pipes │ │ │ └── exponential │ │ │ │ ├── exponential.pipe.spec.ts │ │ │ │ └── exponential.pipe.ts │ │ └── shared.module.ts │ ├── test │ │ ├── test.component.html │ │ ├── test.component.scss │ │ ├── test.component.spec.ts │ │ └── test.component.ts │ └── utils │ │ └── validators.ts ├── assets │ ├── .gitkeep │ └── images │ │ ├── banner-1.jpg │ │ ├── banner-2.jpg │ │ ├── banner-3.jpg │ │ ├── camiseta.png │ │ ├── hoodie.png │ │ ├── mug.png │ │ ├── pin.png │ │ ├── stickers1.png │ │ └── stickers2.png ├── environments │ ├── environment.prod.ts │ ├── environment.stag.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.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 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Swiper", 4 | "flexboxgrid", 5 | "sidenav" 6 | ] 7 | } -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # PlatziStore 4 | 5 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.0. 6 | 7 | ## Development server 8 | 9 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 10 | 11 | ## Code scaffolding 12 | 13 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 14 | 15 | ## Build 16 | 17 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 18 | 19 | ## Running unit tests 20 | 21 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 22 | 23 | ## Running end-to-end tests 24 | 25 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 26 | 27 | ## Further help 28 | 29 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular 2 | 3 | # Comandos bash 4 | 5 | ## `ng serve` -> 6 | 7 | para levantar el servidor (puedes enviarle mas datos como puerto o dominio donde quieres que corra, normalmente usa el puerto 4200) 8 | 9 | Para cambiar la ruta en la cual correrá el proyecto: 10 | 11 | ```bash 12 | ng serve --o --host 192.168.1.26 --port {PORT} // cambiar la ruta en la cual correrá el proyecto 13 | ``` 14 | 15 | ## `ng build --prod` -> 16 | 17 | similar a serve, se usa para alistar la aplicación para producción, te genera una capeta en tu proyecto llamada “dist” donde crea lo necesario para montar en producción. 18 | 19 | ## `ng --version` -> 20 | 21 | Versiones que tienes en tu proyecto 22 | 23 | ## `ng g`-> 24 | 25 | Genera automáticamente el componente en una carpeta con el mismo nombre que se le dio. Ademas lo importa automáticamente en al *app.module.ts* 26 | 27 | ```shell 28 | ng g c components/cart 29 | ``` 30 | 31 | - g = generate 32 | - c = component 33 | 34 | ### Flags 35 | 36 | #### --inline-style 37 | 38 | Evita que se cree el archivo de estilos 39 | 40 | ```shell 41 | ng g c componente --inline-style 42 | ``` 43 | 44 | #### --inline-template 45 | 46 | Este evita que se cree el html 47 | 48 | ```shell 49 | ng g c componente --inline-template 50 | ``` 51 | 52 | ## `ng lint`-> 53 | 54 | ``` 55 | ng lint 56 | ``` 57 | 58 | Revisa el código del proyecto para ver si se están cumpliendo las reglas definidas en el archivo tslint.json 59 | 60 | ``` 61 | ng lint --format json 62 | ``` 63 | 64 | Nos permite visualizar el resultado de la revisión con un formato json. 65 | 66 | ``` 67 | ng lint --fix 68 | ``` 69 | 70 | Corrige automáticamente, dentro de lo posible, los errores encontrados. 71 | 72 | # [Directivas](https://angular.io/api/common#directives) 73 | 74 | ## `ngIf` 75 | 76 | Muestra el componente sólo si cumple con la directiva 77 | 78 | ```html 79 |
80 | Súper div 81 |
82 | ``` 83 | 84 | ## `ng-model` 85 | 86 | Permite hacer data binding, osea conectar entre un dato y un componente 87 | 88 | ```html 89 | 90 | ``` 91 | 92 | 93 | 94 | En proyectos reales puedes que te encuentres con dos tipos de NgModel 95 | 96 | 1. [(ngModel)]=“nombre” 97 | 2. [ngModel]=“apellido” 98 | 99 | El primero hace un enlace de doble sentido, es decir, si se actualiza la vista se actualiza el valor y si se actualiza el valor se actualiza la vista. 100 | 101 | El segundo hace un enlace de un sólo sentido, este sentido va desde el modelo a la vista, pero la vista NO puede actualizar al modelo. Pueden copiar y pegar este código para comprobarlo. 102 | 103 | # String interpolation 104 | 105 | La interpolación es la forma de mostrar datos del **Componente** al **DOM** (esa representación que hace el browser del **HTML** con forma de objetos). Su notación es en forma de doble brackets `{{}}` () y lo que está dentro de esos brackets es lo que se quiere mostrar en pantalla «procesado». 106 | 107 | Los templates de Angular utilizan html como lenguaje de maquetación donde se puede ejecutar interpolación de strings mediante una implementación de moustache donde dentro de los corchetes puedes hacer una sentencia que se resolverá como un string para ser renderizado en el DOM. 108 | 109 | Nuestros templates tienen acceso a las variables exportadas en el arc`.ts` con el mismo nombre, mismas que podemos renderizar mediante los string interpolation, por ejemplo: 110 | 111 | ```html 112 |

{{ title }}

113 |

114 | Las suma de 2 + 2 = {{ 2 + 2 }} 115 |

116 | ``` 117 | 118 | Angular divide la lógica de la vista, pero puedes hacer uso de los elementos de uno o del otro en las 2 partes. 119 | 120 | ## `{{ template-expression }}` 121 | 122 | Un template expresion es la expresión que se va a evaluar para renderizarse en el DOM. 123 | 124 | Los tipos de **Template Expresion** válidos pueden ser: 125 | 126 | - **{{ `1 + 1` }}**: la cual devuelve 2 127 | - **{{ `miVariable` }}**: donde miVariable es una variable definida en el **Component** 128 | - **{{ `miMetodo()` }}** : donde miMetodo() es un método definido en el **Component** 129 | 130 | También se le puede asignar a un atributo **HTML** (que espere un string) el valor de una **Template Expresion**, por ejemplo 131 | 132 | `

` ← se asigna un valor “ya procesado” a un **Tag HTML** 133 | 134 | # Directivas Propias 135 | 136 | Para crear una directiva 137 | 138 | ```typescript 139 | ng generate 140 | ng g 141 | ``` 142 | 143 | Las directivas tienen un selector que es como su identificador el cual se debe colocar en las etiquetas como atributos. 144 | 145 | Recuerda que las directivas modifican dinamicamente los elementos desde el DOM, esto no es buena practica aplicarlo para todo ya que existen alternativas más usables como el data-binding 146 | 147 | ## Ejemplo: 148 | 149 | **selector = ‘[appTabRequired]’** 150 | 151 | En el constructor tendremos que hacer la inyeccion de dependencia **ElementRef** 152 | 153 | ### constructor 154 | 155 | ```typescript 156 | constructor( element: ElementRef){ 157 | element.nativeElement.style.color = 'red'; 158 | } 159 | ``` 160 | 161 | ### hightlight.directive.ts 162 | 163 | ```typescript 164 | import { Directive, ElementRef } from '@angular/core'; 165 | 166 | @Directive({ 167 | selector: '[appHighlight]' 168 | }) 169 | export class HighlightDirective { 170 | constructor( 171 | element: ElementRef 172 | ) { 173 | element.nativeElement.style.backgroundColor = 'blue'; 174 | } 175 | } 176 | 177 | ``` 178 | 179 | ### Implementación en el template: 180 | 181 | ```html 182 |
183 | Contenido 184 |
185 | ``` 186 | 187 | 188 | 189 | # COMPONENTES EN ANGULAR - Sintaxis General 190 | 191 | Es muy bueno saber como construir un componente de manera manual, pero existe un comando para generar nuevos componentes de forma automática. 192 | 193 | ```bash 194 | ng g c nombredelcomponente 195 | ``` 196 | 197 | Nomenclatura : **name.component.ts** 198 | 199 | ## `Decoradores`: 200 | 201 | Le dan un contexto a los artefactos de angular. Y le asigna el tipo de rol que cumplirá. 202 | 203 | Un decorador angular es una clase especial de declaración que puede acoplarse a una clase, método, propiedad o parámetro. Los decoradores se declaran de la siguiente manera `@expression`. Gracias al parámetro `@` podemos reconocer fácilmente que estamos hablando de un decorador. 204 | 205 | Generalmente usamos los decoradores para extender servicios de terceros, de esta manera evitamos modificar el código original del módulo y en tiempo de ejecución agregamos el funcionamiento que necesitamos. 206 | 207 | ```javascript 208 | import { Component, OnInit } from '@angular/core'; 209 | 210 | @Component({ 211 | selector: 'selector-name', 212 | templateUrl: './product.component.html' 213 | }) 214 | 215 | export class ProductComponent { 216 | constructor() { } 217 | } 218 | ``` 219 | 220 | ## `selector:` 221 | 222 | Se refiere al selector con el que vas a llamar a tu componente desde el html (``) 223 | 224 | ## `templateURL` 225 | 226 | El templateURL: es el archivo html. 227 | 228 | ## `class`: 229 | 230 | Para que la clase sea utilizada por cualquiera se debe hacer "publica" 231 | 232 | `export class ProductComponent` 233 | 234 | Además se debe importar el componente en el archivo `app.module.ts`, 235 | 236 | ```javascript 237 | import { Component } from 'component-route' 238 | ... 239 | 240 | @NgModule({ 241 | declarations: [ 242 | ... 243 | Component 244 | ], 245 | ... 246 | }) 247 | export class AppModule { } 248 | 249 | ``` 250 | 251 | # Uso de Inputs y Outputs 252 | 253 | Un componente sólo debe de tener una sóla responsabilidad (principio SOLID) 254 | 255 | Lo que seria props en React 256 | 257 | La [documentación](https://angular.io/guide/component-interaction#parent-listens-for-child-event) de Angular respecto a la comunicación entre componentes. 258 | 259 | [Artículo](https://samueleresca.net/2016/08/solid-principles-using-typescript/) que explica la aplicabilidad de los principios SOLID en Angular. 260 | 261 | ## `Input` 262 | 263 | Para comunicar de un componente padre a un componente hijo usamos **Input** 264 | 265 | ```typescript 266 | export class ProductComponent { 267 | @Input() product: Product; // Equivalente a prop, dónde le vamos a pasar la data al componente 268 | } 269 | ``` 270 | 271 | Para pasar argumentos, desde el template padre podemos utilizar los corchetes cuadrados con el Input para pasar el argumento 272 | 273 | ```html 274 | 275 | ``` 276 | 277 | ```html 278 |
279 | 280 |
281 | ``` 282 | 283 | ## `Output` 284 | 285 | Para comunicar de un componente hijo a un componente padre usamos **Output**. 286 | 287 | Los `Output` son eventos que podemos cachar desde nuestro componente hijo por parte del padre. 288 | 289 | Para declararlos en nuestro componente tenemos que usar el decorador `@Output` sobre un `EventEmitter` 290 | 291 | ```typescript 292 | export class ProductComponent { 293 | @Output() clickAddToCart = new EventEmitter(); // (clickAddToCart)= eventHandler($event):function 294 | ... 295 | } 296 | ``` 297 | 298 | Los `EventEmmiter`'s pueden emitir un evento con un argumento que será recibido por el padre. 299 | 300 | ```typescript 301 | export class ProductComponent { 302 | @Output() clickAddToCart = new EventEmitter(); // (clickAddToCart)= eventHandler($event):function 303 | addToCart(){ 304 | this.clickAddToCart.emit(this.product.id) 305 | } 306 | } 307 | ``` 308 | 309 | Para hacer uso del output podemos llamar el evento desde paréntesis y asignarle un evento `(clickAddToCart)="handler($event)"`: 310 | 311 | ```typescript 312 | // Componente padre 313 | export class AppComponent { 314 | ... 315 | handleProductAddToCart(id: number) { 316 | console.log('product -> id', id); 317 | } 318 | } 319 | 320 | // Template del componente padre 321 | 324 | 325 | ``` 326 | 327 | El event es recibido desde el `emit` del `EventEmmiter` : 328 | 329 | `this.clickAddToCart.emit(this.product.id)` 330 | 331 | ## `Observables` 332 | 333 | ### Referencias: 334 | 335 | - https://desarrolloweb.com/articulos/introduccion-teorica-observables-angular.html 336 | - https://platzi.com/clases/1071-angular2/6433-que-es-un-observable/ 337 | 338 | Observable es un patrón de diseño de software, donde básicamente tienes algo que observar (Observable) pueden ser eventos de un formulario, un llamada Htttp, etc, nosotros podemos suscribirnos a esos eventos. Otro componente importante es el que observa (Observer) este es el que se suscribe a los eventos y por medio de callbacks captura los eventos que emite el observable, por último tenemos el subject o sujeto que es el que hace que el observable lance los eventos para ser capturados. 339 | 340 | Un Observer crea un espacio de ejecución independiente para cada suscriptor que este tenga. 341 | 342 | # Ciclo de vida del componente 343 | 344 | `ngOnChanges` y `ngDoCheck` tienen un error de colisión, ya que los dos pueden cumplir la tarea de escuchar por cambios del componente. El primero es la forma nativa de Angular, el segundo es una forma customizada para ello. 345 | 346 | `ngOnDestroy` nos ayudará remover las suscripciones a datos que inicializamos en algún momento, así podemos evitar bucles y fugas de memoria en la aplicación, es decir, limpiamos procesos de memoria siguiendo buenas prácticas. 347 | 348 | ![](https://static.platzi.com/media/user_upload/Ciclo-97b9ac82-5217-4dd1-ae4d-3f30c08cfa9b.jpg) 349 | 350 | # [Pipes](https://angular.io/api/common/DatePipe) | tuberías o transformaciones 351 | 352 | Los `pipes` en Angular son transformaciones que se le puede hacer a un dato dentro de una **template-expression** sirve para formatear data como se desee. 353 | 354 | Los pipes pueden ser anidados, por lo que puede ser bastante poderoso. 355 | 356 | La sintaxis es `{{ variableName | pipeName:argumentos }}` 357 | 358 | Nosotros podemos crear nuestros pipes, pero Angular por defecto provee muchos pipes que podemos implementar sin muchas complicaciones, mismos que pueden ser configurables en el `app.module.ts` éstos son los comunes: 359 | 360 | - [ AsyncPipe](https://angular.io/api/common/AsyncPipe) 361 | - [ CurrencyPipe](https://angular.io/api/common/CurrencyPipe) 362 | - [ DatePipe](https://angular.io/api/common/DatePipe) 363 | - [ DecimalPipe](https://angular.io/api/common/DecimalPipe) 364 | - [ I18nPluralPipe](https://angular.io/api/common/I18nPluralPipe) 365 | - [ I18nSelectPipe](https://angular.io/api/common/I18nSelectPipe) 366 | - [ JsonPipe](https://angular.io/api/common/JsonPipe) 367 | - [ KeyValuePipe](https://angular.io/api/common/KeyValuePipe) 368 | - [ LowerCasePipe](https://angular.io/api/common/LowerCasePipe) 369 | - [ PercentPipe](https://angular.io/api/common/PercentPipe) 370 | - [ SlicePipe](https://angular.io/api/common/SlicePipe) 371 | - [ TitleCasePipe](https://angular.io/api/common/TitleCasePipe) 372 | - [ UpperCasePipe](https://angular.io/api/common/UpperCasePipe) 373 | 374 | [Pueden ver unos ejemplos acá](https://bit.ly/2oRRj0Z) 375 | 376 | Para agregar localismos a nuestros pipes (en este caso mexicanos) tendríamos que agregar el idioma a un `provider` dentro de `app.module.ts` de la siguiente forma: 377 | 378 | ```typescript 379 | ... 380 | import { LOCALE_ID } from '@angular/core'; 381 | import localeEs from '@angular/common/locales/es'; 382 | import { registerLocaleData } from '@angular/common'; 383 | 384 | registerLocaleData(localeEs); 385 | ... 386 | 387 | @NgModule({ 388 | ... 389 | providers: [ { provide: LOCALE_ID, useValue: 'es-mx' } ], 390 | ... 391 | }) 392 | export class AppModule { } 393 | ``` 394 | 395 | ## Pipes custom 396 | 397 | Los pipes en angular tienen muy buen rendimiento por lo que se recomiendan envés de las funciones para estos casos 398 | 399 | Los **pipes** nos sirven para transformar datos. 400 | 401 | Un dato puede ingresar y puede ser convertido segun el pipe usado 402 | 403 | Recuerda que si quieres generar tus propios pipes puedes usar 404 | 405 | ```bash 406 | $ ng generate pipe 407 | $ ng g pipe 408 | ``` 409 | 410 | ### Ejemplo de pipe custom 411 | 412 | ```typescript 413 | import { Pipe, PipeTransform } from '@angular/core'; 414 | 415 | @Pipe({ 416 | name: 'exponential' 417 | }) 418 | export class ExponentialPipe implements PipeTransform { 419 | transform(value: number, exp:number=2): number { 420 | return Math.pow(value, exp); 421 | } 422 | } 423 | ``` 424 | 425 | # ngModule 426 | 427 | Los módulos en angular sirven para resumir o adjuntar varios artefactos de Angular como servicios, componentes y directivas, dividiendo y abstrayendo el dominio de la aplicación. 428 | 429 | De esta manera 430 | 431 | Los componentes que hacen parte de una página en particular se pueden encapsular en un mismo módulo. 432 | 433 | Los módulos especiales son core y shared. 434 | 435 | - **core**: Guarda toda la funcionalidad de la aplicación que va a ser compartida, es decir que genera una sóla referencia de la funcionalidad, siguiendo el principio Singleton de SOLID, facilitando así el traslado de funcionalidad entre aplicaciones. guarda todos los servicios y componentes que usaremos a lo largo de todos los otros módulos. Este sera usado en **todos ** los otros componentes y/o modulos respetando los principios SOLID (SINGLETON) 436 | - **shared**: Encapsula los componentes y servicios compartidos para la aplicación. podemos almacenar componentes y servicios compartidos. En este podemos tener componentes servicios y/o compartidos. 437 | 438 | ## RUTAS EN ANGULAR 439 | 440 | En el archivo **app-routing-module.ts** se encuentra un objeto **Route** el cual sirve para incrustar las rutas del proyecto. 441 | 442 | La sintaxis dentro del router (`app-routing-module.ts`): 443 | 444 | ```json 445 | import { componentName } from './url/to/componentName.component'; 446 | 447 | const routes: Routes = [ 448 | { 449 | path = ‘routeName’, 450 | component = componentName 451 | }, 452 | ] 453 | 454 | ``` 455 | 456 | Donde: 457 | 458 | - `path` = ruta relativa al home ('/') de nuestra app 459 | - `component` = componente importado desde `componentName.component.ts` 460 | 461 | La forma de implementar el **router** en un **`template.html`** es con el componente `router-outlet`: 462 | 463 | ```html 464 | 465 | 466 | 467 | ``` 468 | 469 | Donde el `router-outlet` se reemplazará por el contenido del componente según la URL en la que estés 470 | 471 | ### Páginas no encontradas (not-found) 472 | 473 | Para definir una página no encontrada podemos utilizar la ruta `'**'` que simboliza cualquier ruta diferente a las anteriormente definidas, el componente también deberá de ser el objeto del componente importado. 474 | 475 | ```js 476 | { 477 | path: '**', 478 | component: NotFoundComponent 479 | } 480 | ``` 481 | 482 | > Nota: El orden de las rutas **es importante**, si algo matchea una ruta, no se segirá a la siguiente, por lo que el not-found, debería de ser la última ruta, siempre 483 | 484 | ### Redirecciones 485 | 486 | Para las redirecciones tenemos que usar las propiedades `redirectTo:'route'` y `pathMatch:'full'` 487 | 488 | ```typescript 489 | { 490 | path: 'from', 491 | redirectTo: 'to', 492 | pathMatch: 'full' 493 | }, 494 | ``` 495 | 496 | Donde: 497 | 498 | - `path`: Página actual 499 | - `redirectTo`: página a la que se redireccionará 500 | - `pathMatch`: tipo de match, en el caso de 'full' será con una relación exacta 501 | 502 | ### Redirecciones sin recargar `routerLink` 503 | 504 | Para poder movernos entre rutas sin recargar nuestra página (tipo single page application) debemos agregar a nuestras anclas '``' la directiva **`routerLink`** envés del atributo **`href`** para que Angular determine que no haga una recarga de la página. 505 | 506 | ```html 507 | 510 | ``` 511 | 512 | ### Ancla activa `routerLinkActive` 513 | 514 | Puedes definir una clase para cuando una ruta matchee completamente al agregar la directiva `routerLinkActive`, misma que agregará una clase al elemento que contenga un `routerLink`, de esta manera en los estilos podrás acceder al elemento seleccionado. 515 | 516 | La recomendación es llamar "`active`" al routerLinkActive (`routerLinkActive="active">`), de esta manera podrás acceder desde el css mediante la clase `.active` 517 | 518 | **html** 519 | 520 | ```html 521 | 526 | ``` 527 | 528 | **scss** 529 | 530 | ```css 531 | nav a { 532 | padding: 5px; 533 | text-decoration: none; 534 | &.active { 535 | background-color: papayawhip; 536 | } 537 | } 538 | ``` 539 | 540 | Así se vería un `app-routing.module.ts` completo: 541 | 542 | ```typescript 543 | ... // importaciones 544 | const routes: Routes = [ 545 | { 546 | path: 'home', 547 | redirectTo: '', 548 | pathMatch: 'full' 549 | }, 550 | { 551 | path: '', 552 | component: HomeComponent 553 | }, 554 | { 555 | path: '**', 556 | component: NotFoundComponent 557 | }, 558 | ]; 559 | 560 | @NgModule({ 561 | imports: [RouterModule.forRoot(routes)], 562 | exports: [RouterModule] 563 | }) 564 | export class AppRoutingModule { } 565 | 566 | ``` 567 | 568 | ### Vistas Anidadas 569 | 570 | A veces existen componentes que se utilizan en varios otros componentes, para esto existe la técnica de vistas anidadas, la cual consiste en crear un componente que albergue los elementos que se repiten y los otros componentes que los utilizan serán rutas hijas del componente creado anteriormente. 571 | 572 | Primero creamos el componente que almacena los elementos con el comando ‘ng g c nombreComponente’ 573 | 574 | Luego en los archivos del componente se almacenan los elementos que se utilizaran repetitivamente en otros componentes. En el archivo html se utiliza la etiqueta router-oulet para renderizar los componentes que se quieren cargar, y los elementos repetitivos se especifican. 575 | 576 | ```html 577 | 578 | 579 | 580 | ``` 581 | 582 | En el archivo routing se incorpora el componente creado anteriormente en la variable de routes, pero esta vez todos los componentes que se basen en este deberán estar anidadas. Para anidarlos el objeto del componente creado tendrá que tener una nueva característica ‘children’, el cual tendrá los ‘path’ y ‘component’ de las rutas que se basen en la anterior. 583 | 584 | ```typescript 585 | constroutes: Routes = [ 586 | { 587 | path: '', 588 | component: LayoutComponent, 589 | children: [ 590 | { 591 | path: '', 592 | redirectTo: '/home', 593 | pathMatch: 'full', 594 | }, 595 | { 596 | path: 'home', 597 | component: HomeComponent 598 | }, 599 | ... 600 | ] 601 | }, 602 | { 603 | path: 'demo', 604 | component: DemoComponent 605 | }, 606 | { 607 | path: '**', 608 | component: PageNotFoundComponent 609 | } 610 | ]; 611 | ``` 612 | 613 | 614 | 615 | # Servicios 616 | 617 | Los servicios proveen datos esencialmente. La forma de crear un servicio es en la terminal con los comandos ‘ng g s nombreServicio’. 618 | Por lo general tenemos 2 métodos esenciales en los servicios, uno para obtener todos los objetos guardados en una variable, y otro para obtener 1 objeto especifico. 619 | 620 | ```typescript 621 | export class someService { 622 | 623 | store = [ 624 | { 625 | id: '1', 626 | title: 'title', 627 | price: 10, 628 | }, 629 | { 630 | id: '2', 631 | title: 'title', 632 | price: 20, 633 | } 634 | ]; 635 | 636 | constructor() { } 637 | 638 | getAllStored() { 639 | returnthis.store; 640 | } 641 | 642 | getStored(id: string) { 643 | returnthis.store.find(item => id === item.id); 644 | } 645 | } 646 | ``` 647 | 648 | - El método getAllStored() devuelve todos los objetos almacenados en la variable. 649 | - El método getStored() devuelve 1 objeto especifico almacenado en la variable, en este caso se utiliza la variable id para buscar este objeto. 650 | 651 | **Componente para objetos** 652 | A veces es necesario crear componentes para desplegar la información de un solo objeto que provee un servicio. Para eso creamos un componente en la terminal con el comando ‘ng g c nombreComponente’ 653 | 654 | Luego de esto debemos asignarle una ruta en el archivo de routing, pero en esta ocasión tendrá un parámetro dinámico que se enviará. 655 | 656 | ```typescript 657 | const routes: Routes = [ 658 | { 659 | path: 'home', 660 | component: HomeComponent 661 | }, 662 | { 663 | path: 'product', 664 | component: ProductComponent 665 | } 666 | { 667 | path: 'product/:id', 668 | component: ProductDetailComponent 669 | } 670 | 671 | ]; 672 | ``` 673 | 674 | En el componente creado debemos realizar 2 importaciones de dependencias, estos son ‘ActivatedRoute’ y ‘Params’ de ‘@angular/router’. *Nota: no olvidar que las inyecciones de dependencia deben ingresarse como parámetro en el constructor.* 675 | 676 | ```typescript 677 | import { Component, OnInit } from '@angular/core'; 678 | import { ActivatedRoute, Params } from '@angular/router'; 679 | 680 | @Component({ 681 | selector: 'app-product-detail', 682 | templateUrl: './product-detail.component.html', 683 | styleUrls: ['./product-detail.component.scss'] 684 | }) 685 | export class ProductDetailComponent implements OnInit { 686 | 687 | constructor( 688 | private route: ActivatedRoute 689 | ) { } 690 | 691 | ngOnInit() {}; 692 | 693 | } 694 | ``` 695 | 696 | Desde el ngOnInit() es la forma adecuada de recibir el parámetro definido anteriormente en la ruta, y se suscribe a el por si existen cambios, esto para ejecutar los cambios en la pagina por si cambia la ruta. La variable definida en la suscripción es de tipo Params que fue importado anteriormente. 697 | 698 | ```typescript 699 | ngOnInit() { 700 | this.route.params.subscribe((params: Params) => { 701 | const id = params.id; 702 | console.log(id); 703 | }); 704 | } 705 | ``` 706 | 707 | En el método anterior capturamos el valor de id en params. *Es importante aclarar que params es un json.* 708 | 709 | Ahora para consultar los objetos de un servicio debemos importar el servicio en sí. Con ello tendremos acceso a sus datos y métodos. *Los servicios son inyecciones de dependencia, por lo que hay que ingresarlos en el constructor.* 710 | 711 | ```typescript 712 | import { ProductsService } from'./../products.service'; 713 | constructor( 714 | private route: ActivatedRoute, 715 | private productsService: ProductsService 716 | ){ } 717 | ``` 718 | 719 | El servicio importado en este caso tiene un método que devuelve un objeto especifico en formato json que se busca a través de su id, por ello se solicita una variable de entrada que se utiliza para buscar dicho objeto. En el siguiente caso se guarda el objeto en una variable y se imprime por consola. 720 | 721 | ```typescript 722 | ngOnInit() { 723 | this.route.params.subscribe((params: Params) => { 724 | const id = params.id; 725 | const product = this.productsService.getProduct(id); 726 | console.log(product); 727 | }); 728 | } 729 | ``` 730 | 731 | # **Modularizacion** 732 | 733 | Un modulo encapsula varios elementos de una aplicación. Por lo general se modulariza cada vista de nuestra aplicación. Para crear un modulo se utiliza el comando ‘ng g m nombreModulo’ 734 | 735 | Es buena practica crear un modulo por vista, y dentro este una carpeta ‘components’ con los componentes que utilizara. 736 | 737 | En los módulos existe un archivo ‘nombreModulo.module.ts’ el cual tendrá que importar los componentes que utilizara. En el ‘@ngModule’ se debe declarar los componentes que utilizara, y exportar los que se utilizaran en otros módulos. 738 | 739 | ```typescript 740 | import { NgModule } from '@angular/core'; 741 | 742 | import { BannerComponent } from '../components/banner/banner.component'; 743 | import { HomeComponent } from'./home.component'; 744 | 745 | @NgModule({ 746 | declarations: [ 747 | BannerComponent, 748 | HomeComponent 749 | ], 750 | }) 751 | 752 | export class HomeModule { 753 | 754 | } 755 | ``` 756 | 757 | También los módulos tienen un archivo routing ‘nombreModulo-routing.module.ts’ en el cual se deben definir las rutas del modulo. 758 | 759 | ```typescript 760 | import { NgModule } from '@angular/core'; 761 | import { Routes, RouterModule } from '@angular/router'; 762 | 763 | constroutes: Routes = []; 764 | 765 | @NgModule({ 766 | imports: [ 767 | RouterModule.forChild(routes), 768 | ], 769 | exports: [ 770 | RouterModule 771 | ] 772 | }) 773 | export class HomeRoutingModule {} 774 | ``` 775 | 776 | Luego de terminar con el archivo routing, este debe importarse al archivo ‘nombreModulo.module.ts’ para ser utilizado. 777 | 778 | ```typescript 779 | import { NgModule } from'@angular/core'; 780 | 781 | import { BannerComponent } from'./components/banner/banner.component'; 782 | import { HomeComponent } from'./home.component'; 783 | 784 | import { HomeRoutingModule } from'./home-routing.module'; 785 | 786 | @NgModule({ 787 | declarations: [ 788 | BannerComponent, 789 | HomeComponent 790 | ], 791 | imports: [ 792 | HomeRoutingModule 793 | ] 794 | }) 795 | exportclassHomeModule { 796 | 797 | } 798 | ``` 799 | 800 | ## Lazy 801 | 802 | Es una técnica que ayuda a reducir el peso de las aplicaciones, de esta forma carga mas rápido el proyecto. La carga inicial de una pagina no debería ser Lazy ya que necesitamos que se cargue de una, como por ejemplo el Home. 803 | 804 | La forma en que funciona es que el navegador realiza un request del html, el cual contiene las instrucciones de carga (archivos css, js e imágenes) y cuando identifica los archivos que necesita para cargar el modulo, realiza otro request para pedir dichos archivos. Para aplicar esta técnica es necesario modularizar la aplicación. 805 | 806 | Cuando ya esta modularizado, hay que definir una ruta inicial en el archivo routing del modulo. 807 | 808 | ```typescript 809 | import { HomeComponent } from './components/home/home.component'; 810 | 811 | constroutes: Routes = [ 812 | { 813 | path: '', 814 | component: HomeComponent 815 | } 816 | ]; 817 | ``` 818 | 819 | Ahora en el app-routing cargamos la ruta del modulo con **loadChildren**, de esta forma se carga dinámicamente. De esta forma resolvemos todo un modulo (no un componente únicamente). 820 | 821 | ```typescript 822 | constroutes: Routes = [ 823 | { 824 | path: '', 825 | component: LayoutComponent, 826 | children: [ 827 | { 828 | path: '', 829 | redirectTo: '/home', 830 | pathMatch: 'full', 831 | }, 832 | { 833 | path: 'home', 834 | loadChildren: () => import('./home/home.module').then(m => m.HomeModule) 835 | } 836 | ] 837 | }, 838 | { 839 | path: '**', 840 | component: PageNotFoundComponent 841 | } 842 | ]; 843 | ``` 844 | 845 | Ahora tenemos que hacer que la aplicación realice una precarga de los otros módulos de las otras vistas cuando la vista que estamos consultando ya termine. Para realizarlo en el archivo ‘app-routing’ debemos cambiar de estrategia de carga, precargando los archivos con **‘PreloadAllModules’**. *No olvidar importar ‘PreloadAllModules’* 846 | 847 | ```typescript 848 | import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; 849 | 850 | @NgModule({ 851 | imports: [RouterModule.forRoot(routes, { 852 | preloadingStrategy: PreloadAllModules 853 | })], 854 | exports: [RouterModule] 855 | }) 856 | ``` 857 | 858 | # [Creando vistas con Angular schematic](https://material.angular.io/guide/schematics) 859 | 860 | *Schematics* es una forma de crear archivos o reglas desde el CLI (command line interfaces), material ya tiene incluídos algunos schematics muy útiles para poder crear componentes de uso regular. 861 | 862 | Para poder crear un schematic tienes que tener instalado `'angular/cdk'`, mismo que viene por defecto al instalar Angular desde el CLI, pero que se tiene que instalar si no llegasen a funcionar los comandos. 863 | 864 | Los schematics generan código, así que [busca en la documentación que quieres crear](https://material.angular.io/guide/schematics) y pégalo en la línea de comandos, por ejemplo: 865 | 866 | ``` 867 | ng generate @angular/material:dashboard admin/components/dashboard 868 | 869 | ng generate @angular/material:table admin/components/list-products 870 | 871 | ng generate @angular/material:navigation admin/components/nav 872 | 873 | ng generate @angular/material:address-form admin/components/product-form 874 | ``` 875 | 876 | # [`HtttpClient`](https://angular.io/guide/http) 877 | 878 | La mayoría de las aplicaciones front-end se comunican con los servicios de back-end a través del protocolo HTTP. Los navegadores modernos admiten dos API diferentes para realizar solicitudes HTTP: la interfaz XMLHttpRequest y la API fetch(), pero Angular implementa su propia interfaz basada en XMLHttpRequest para facilitar el fetching de datos, api expuesta por los navegadores. 879 | 880 | `HttpClient` viene desde `@angular/common/http` y ofrece una interfaz API HTTP de cliente simplificada para aplicaciones Angular. 881 | 882 | Los beneficios adicionales de **HttpClient** incluyen: 883 | 884 | - Suite de **pruebas** simplificadas 885 | - Requests y responses **tipados** 886 | - **Intercepción** de requests y responses 887 | - **Observables** 888 | - Manejo de **errores** optimizado (mediante streams) 889 | 890 | . 891 | 892 | Para trabajar con HttpClient tenemos que crear un objeto de tipo HttpClient: 893 | 894 | ```typescript 895 | import { HttpClient } from '@angular/common/http'; 896 | export class ProductsService { 897 | constructor( private http: HttpClient ) {} 898 | } 899 | ``` 900 | 901 | HttpClient provee métodos http basados en los verbos de protocolos http, por lo tanto podemos facilitar una capa de servicios que implemente los métodos específicos de cada acción http: 902 | 903 | ```typescript 904 | export class ProductsService { 905 | ... 906 | getAllProducts(): Observable { 907 | return this.http.get(`${environment.url_api}/products`); 908 | } 909 | ... 910 | } 911 | ``` 912 | 913 | Algunos de los métodos que podríamos utilizar son: 914 | 915 | - `http.get` 916 | - `http.post` 917 | - `http.put` 918 | - `http.patch` 919 | - `http.put` 920 | 921 | Por lo tanto así se vería una capa de servicios en Angular basados en `HttpClient` 922 | 923 | ```typescript 924 | @Injectable({ providedIn: 'root' }) 925 | export class ProductsService { 926 | constructor( private http: HttpClient ) {} 927 | getAllProducts(): Observable { 928 | return this.http.get(`${environment.url_api}/products`); 929 | } 930 | getProduct(id: string): Observable { 931 | return this.http.get(`${environment.url_api}/products/${id}`); 932 | } 933 | createProduct(product: Product): any { 934 | return this.http.post(`${environment.url_api}/products`, product); 935 | } 936 | updateProduct(id: string, changes: Partial): any { 937 | return this.http.put(`${environment.url_api}/products/${id}`, changes); 938 | } 939 | deleteProduct(id: string): any { 940 | return this.http.delete(`${environment.url_api}/products/${id}`); 941 | } 942 | } 943 | ``` 944 | 945 | # Ambientes en Angular 946 | 947 | Un entorno de aplicación en Angular (environment) es información de configuración JSON que le dice al sistema de compilación qué archivos cambiar cuando usa ng build y ng serve. 948 | 949 | La recomendación es hacer ambientes dentro del directorio `environments/environment.[nombre].ts`, y para registrarlo necesitas modificar el archivo `angular.json` 950 | 951 | Para agregar un nuevo ambiente al `angular.json` se necesitan duplicar el environment de **`build`** y de **`serve`** dentro de **projects..architect.build.configurations.nameOfNewEnvironment** y de **projects..architect.serve.configurations.production** y cambiar production por el nombre que quieras que reciba tu environment, como staging o local, etc. 952 | 953 | Recuerda que es muy delicado este archivo y que lo tienes que hacer a conciencia, además de que tienes que colocar la ruta de tu archivo de environments en `fileReplacements`, porque lo que hace este archivo es reemplazar las ocurrencias de importación de `src/environments/environment.ts` por el archivo de ambiente que le indiques. 954 | 955 | ```json 956 | { 957 | projects: { 958 | nameOfProject: { 959 | ... 960 | architect: { 961 | build: { 962 | ... 963 | production: {} <-- Duplicar este objeto 964 | }, 965 | serve: { 966 | configurations: { 967 | ... 968 | production: {} <-- Duplicar este objeto 969 | } 970 | } 971 | } 972 | } 973 | } 974 | } 975 | ``` 976 | 977 | # Formularios Reactivos 978 | 979 | Los formularios reactivos ayudan a manejar entradas de formulario cuyos valores cambian con un enfoque explícito e inmutable para administrar el estado de un formulario en un momento dado. 980 | 981 | Los formularios reactivos difieren de los [formularios basadas en plantillas](https://angular.io/guide/forms) en los siguientes puntos. 982 | 983 | - Las formas reactivas son predecibles al ser síncronas con el modelo de datos 984 | - Son inmutables, por lo tanto cada cambio en el estado del formulario devuelve un nuevo estado, 985 | - seguimiento de cambios a través de streams observables. 986 | 987 | - Proporcionan una ruta directa a las pruebas porque tiene la seguridad de que sus datos son consistentes y predecibles cuando se solicitan. 988 | - Cualquier consumidor de los streams tiene acceso para manipular estos datos de manera segura 989 | - Se construyen alrededor de [observable](https://angular.io/guide/glossary#observable) streams 990 | 991 | Además proveen métodos más sencillos para: 992 | 993 | - La validación de datos 994 | - Realización de pruebas unitarias: porque tienes la seguridad de que sus datos son consistentes y predecibles al momento de solicitarlos. 995 | - Tener lógicas más complejas 996 | 997 | ## `FormControl` 998 | 999 | Un input de tipo FormControl permite la anidación de validadores (ValidatorFn) con los que podemos de manera sencilla validar la fuente de datos. 1000 | 1001 | ```typescript 1002 | FormControl( 1003 | formState?: any, 1004 | validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions, 1005 | asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] 1006 | ): FormControl 1007 | ``` 1008 | 1009 | Se definen detro de una variable de tipo `FormControl()` y se pueden llamar en el template: 1010 | 1011 | ```typescript 1012 | // component.component.ts 1013 | export class Component implements OnInit { 1014 | emailField: FormControl; 1015 | constructor() { 1016 | this.emailField = new FormControl('', [ 1017 | Validators.required, 1018 | Validators.maxLength(50), 1019 | Validators.minLength(4), 1020 | Validators.email, 1021 | Validators.pattern(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/) 1022 | ]); 1023 | } 1024 | } 1025 | ``` 1026 | 1027 | ```html 1028 | 1029 | 1030 | {{ emailField.valid }} 1031 | ``` 1032 | 1033 | 1034 | 1035 | # Reactive programming 1036 | 1037 | - [Rxjs](https://rxjs-dev.firebaseapp.com/guide/) 1038 | - [Operators](https://rxjs-dev.firebaseapp.com/guide/operators) 1039 | 1040 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "platzi-store": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/platzi-store", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": true, 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 32 | "./node_modules/flexboxgrid/css/flexboxgrid.min.css", 33 | "./node_modules/swiper/css/swiper.css", 34 | "src/styles.scss" 35 | ], 36 | "scripts": [] 37 | }, 38 | "configurations": { 39 | "production": { 40 | "fileReplacements": [ 41 | { 42 | "replace": "src/environments/environment.ts", 43 | "with": "src/environments/environment.prod.ts" 44 | } 45 | ], 46 | "optimization": true, 47 | "outputHashing": "all", 48 | "sourceMap": false, 49 | "extractCss": true, 50 | "namedChunks": false, 51 | "extractLicenses": true, 52 | "vendorChunk": false, 53 | "buildOptimizer": true, 54 | "budgets": [ 55 | { 56 | "type": "initial", 57 | "maximumWarning": "2mb", 58 | "maximumError": "5mb" 59 | }, 60 | { 61 | "type": "anyComponentStyle", 62 | "maximumWarning": "6kb", 63 | "maximumError": "10kb" 64 | } 65 | ] 66 | }, 67 | "stag": { 68 | "fileReplacements": [ 69 | { 70 | "replace": "src/environments/environment.ts", 71 | "with": "src/environments/environment.stag.ts" 72 | } 73 | ], 74 | "optimization": true, 75 | "aot": false, 76 | "outputHashing": "all", 77 | "sourceMap": false, 78 | "extractCss": true, 79 | "namedChunks": false, 80 | "extractLicenses": true, 81 | "vendorChunk": false, 82 | "buildOptimizer": false, 83 | "budgets": [ 84 | { 85 | "type": "initial", 86 | "maximumWarning": "2mb", 87 | "maximumError": "5mb" 88 | }, 89 | { 90 | "type": "anyComponentStyle", 91 | "maximumWarning": "6kb", 92 | "maximumError": "10kb" 93 | } 94 | ] 95 | } 96 | } 97 | }, 98 | "serve": { 99 | "builder": "@angular-devkit/build-angular:dev-server", 100 | "options": { 101 | "browserTarget": "platzi-store:build" 102 | }, 103 | "configurations": { 104 | "production": { 105 | "browserTarget": "platzi-store:build:production" 106 | }, 107 | "stag": { 108 | "browserTarget": "platzi-store:build:stag" 109 | } 110 | } 111 | }, 112 | "extract-i18n": { 113 | "builder": "@angular-devkit/build-angular:extract-i18n", 114 | "options": { 115 | "browserTarget": "platzi-store:build" 116 | } 117 | }, 118 | "test": { 119 | "builder": "@angular-devkit/build-angular:karma", 120 | "options": { 121 | "main": "src/test.ts", 122 | "polyfills": "src/polyfills.ts", 123 | "tsConfig": "tsconfig.spec.json", 124 | "karmaConfig": "karma.conf.js", 125 | "assets": [ 126 | "src/favicon.ico", 127 | "src/assets" 128 | ], 129 | "styles": [ 130 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 131 | "src/styles.scss" 132 | ], 133 | "scripts": [] 134 | } 135 | }, 136 | "lint": { 137 | "builder": "@angular-devkit/build-angular:tslint", 138 | "options": { 139 | "tsConfig": [ 140 | "tsconfig.app.json", 141 | "tsconfig.spec.json", 142 | "e2e/tsconfig.json" 143 | ], 144 | "exclude": [ 145 | "**/node_modules/**" 146 | ] 147 | } 148 | }, 149 | "e2e": { 150 | "builder": "@angular-devkit/build-angular:protractor", 151 | "options": { 152 | "protractorConfig": "e2e/protractor.conf.js", 153 | "devServerTarget": "platzi-store:serve" 154 | }, 155 | "configurations": { 156 | "production": { 157 | "devServerTarget": "platzi-store:serve:production" 158 | } 159 | } 160 | } 161 | } 162 | } 163 | }, 164 | "defaultProject": "platzi-store" 165 | } 166 | -------------------------------------------------------------------------------- /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'. -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('platzi-store app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/platzi-store'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "platzi-store", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "serve dist/platzi-store", 7 | "dev": "ng serve", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "~9.1.0", 16 | "@angular/cdk": "~9.2.0-next.0", 17 | "@angular/common": "~9.1.0", 18 | "@angular/compiler": "~9.1.0", 19 | "@angular/core": "~9.1.0", 20 | "@angular/forms": "~9.1.0", 21 | "@angular/material": "~9.2.0-next.0", 22 | "@angular/platform-browser": "~9.1.0", 23 | "@angular/platform-browser-dynamic": "~9.1.0", 24 | "@angular/router": "~9.1.0", 25 | "flexboxgrid": "^6.3.1", 26 | "rxjs": "~6.5.4", 27 | "serve": "^11.3.0", 28 | "swiper": "^5.3.7", 29 | "tslib": "^1.10.0", 30 | "zone.js": "~0.10.2" 31 | }, 32 | "devDependencies": { 33 | "@angular-devkit/build-angular": "~0.901.0", 34 | "@angular/cli": "~9.1.0", 35 | "@angular/compiler-cli": "~9.1.0", 36 | "@angular/language-service": "~9.1.0", 37 | "@types/node": "^12.11.1", 38 | "@types/jasmine": "~3.5.0", 39 | "@types/jasminewd2": "~2.0.3", 40 | "codelyzer": "^5.1.2", 41 | "jasmine-core": "~3.5.0", 42 | "jasmine-spec-reporter": "~4.2.1", 43 | "karma": "~4.4.1", 44 | "karma-chrome-launcher": "~3.1.0", 45 | "karma-coverage-istanbul-reporter": "~2.1.0", 46 | "karma-jasmine": "~3.0.1", 47 | "karma-jasmine-html-reporter": "^1.4.2", 48 | "protractor": "~5.4.3", 49 | "ts-node": "~8.3.0", 50 | "tslint": "~6.1.0", 51 | "typescript": "~3.8.3" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/admin/admin-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { ProductFormComponent } from './components/product-form/product-form.component'; 5 | import { NavComponent } from './components/nav/nav.component'; 6 | import { ListProductsComponent } from './components/list-products/list-products.component'; 7 | import { DashboardComponent } from './components/dashboard/dashboard.component'; 8 | import { ProductsListComponent } from './components/products-list/products-list.component'; 9 | import { FormProductComponent } from './components/form-product/form-product.component'; 10 | import { ProductEditComponent } from './components/product-edit/product-edit.component'; 11 | 12 | const routes: Routes = [ 13 | { 14 | path: '', 15 | component: NavComponent, 16 | children: [ 17 | { 18 | path: 'create', 19 | component: ProductFormComponent 20 | }, 21 | { 22 | path: 'list', 23 | component: ListProductsComponent 24 | }, 25 | { 26 | path: 'dashboard', 27 | component: DashboardComponent 28 | }, 29 | { 30 | path: 'products', 31 | component: ProductsListComponent 32 | }, 33 | { 34 | path: 'products/create', 35 | component: FormProductComponent 36 | }, 37 | { 38 | path: 'products/edit/:id', 39 | component: ProductEditComponent 40 | }, 41 | ] 42 | }, 43 | ]; 44 | 45 | @NgModule({ 46 | imports: [RouterModule.forChild(routes)], 47 | exports: [RouterModule] 48 | }) 49 | export class AdminRoutingModule { } 50 | -------------------------------------------------------------------------------- /src/app/admin/admin.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { MaterialModule } from '../material/material.module'; 4 | 5 | import { AdminRoutingModule } from './admin-routing.module'; 6 | import { ProductFormComponent } from './components/product-form/product-form.component'; 7 | import { NavComponent } from './components/nav/nav.component'; 8 | import { LayoutModule } from '@angular/cdk/layout'; 9 | import { ListProductsComponent } from './components/list-products/list-products.component'; 10 | import { DashboardComponent } from './components/dashboard/dashboard.component'; 11 | import { ProductsListComponent } from './components/products-list/products-list.component'; 12 | import { FormProductComponent } from './components/form-product/form-product.component'; 13 | import { ProductEditComponent } from './components/product-edit/product-edit.component'; 14 | 15 | @NgModule({ 16 | declarations: [ProductFormComponent, NavComponent, ListProductsComponent, DashboardComponent, ProductsListComponent, FormProductComponent, ProductEditComponent], 17 | imports: [ 18 | CommonModule, 19 | AdminRoutingModule, 20 | MaterialModule, 21 | LayoutModule, 22 | ] 23 | }) 24 | export class AdminModule { } 25 | -------------------------------------------------------------------------------- /src/app/admin/components/dashboard/dashboard.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Dashboard

3 | 4 | 5 | 6 | 7 | 8 | {{card.title}} 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
Card Content Here
20 |
21 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /src/app/admin/components/dashboard/dashboard.component.scss: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin: 20px; 3 | } 4 | 5 | .dashboard-card { 6 | position: absolute; 7 | top: 15px; 8 | left: 15px; 9 | right: 15px; 10 | bottom: 15px; 11 | } 12 | 13 | .more-button { 14 | position: absolute; 15 | top: 5px; 16 | right: 10px; 17 | } 18 | 19 | .dashboard-card-content { 20 | text-align: center; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/admin/components/dashboard/dashboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { LayoutModule } from '@angular/cdk/layout'; 2 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 4 | import { MatButtonModule } from '@angular/material/button'; 5 | import { MatCardModule } from '@angular/material/card'; 6 | import { MatGridListModule } from '@angular/material/grid-list'; 7 | import { MatIconModule } from '@angular/material/icon'; 8 | import { MatMenuModule } from '@angular/material/menu'; 9 | 10 | import { DashboardComponent } from './dashboard.component'; 11 | 12 | describe('DashboardComponent', () => { 13 | let component: DashboardComponent; 14 | let fixture: ComponentFixture; 15 | 16 | beforeEach(async(() => { 17 | TestBed.configureTestingModule({ 18 | declarations: [DashboardComponent], 19 | imports: [ 20 | NoopAnimationsModule, 21 | LayoutModule, 22 | MatButtonModule, 23 | MatCardModule, 24 | MatGridListModule, 25 | MatIconModule, 26 | MatMenuModule, 27 | ] 28 | }).compileComponents(); 29 | })); 30 | 31 | beforeEach(() => { 32 | fixture = TestBed.createComponent(DashboardComponent); 33 | component = fixture.componentInstance; 34 | fixture.detectChanges(); 35 | }); 36 | 37 | it('should compile', () => { 38 | expect(component).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/app/admin/components/dashboard/dashboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { map } from 'rxjs/operators'; 3 | import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout'; 4 | 5 | @Component({ 6 | selector: 'app-dashboard', 7 | templateUrl: './dashboard.component.html', 8 | styleUrls: ['./dashboard.component.scss'] 9 | }) 10 | export class DashboardComponent { 11 | /** Based on the screen size, switch from standard to one column per row */ 12 | cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe( 13 | map(({ matches }) => { 14 | if (matches) { 15 | return [ 16 | { title: 'Card 1', cols: 1, rows: 1 }, 17 | { title: 'Card 2', cols: 1, rows: 1 }, 18 | { title: 'Card 3', cols: 1, rows: 1 }, 19 | { title: 'Card 4', cols: 1, rows: 1 } 20 | ]; 21 | } 22 | 23 | return [ 24 | { title: 'Card 1', cols: 2, rows: 1 }, 25 | { title: 'Card 2', cols: 1, rows: 1 }, 26 | { title: 'Card 3', cols: 1, rows: 2 }, 27 | { title: 'Card 4', cols: 1, rows: 1 } 28 | ]; 29 | }) 30 | ); 31 | 32 | constructor(private breakpointObserver: BreakpointObserver) {} 33 | } 34 | -------------------------------------------------------------------------------- /src/app/admin/components/form-product/form-product.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Producto 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 | 24 | 26 | The price is invalid 27 | 28 |
29 |
30 |
31 |
32 | 33 | 35 | 36 |
37 |
38 |
39 | 40 | 41 | 42 |
43 |
44 | -------------------------------------------------------------------------------- /src/app/admin/components/form-product/form-product.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/admin/components/form-product/form-product.component.scss -------------------------------------------------------------------------------- /src/app/admin/components/form-product/form-product.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FormProductComponent } from './form-product.component'; 4 | 5 | describe('FormProductComponent', () => { 6 | let component: FormProductComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ FormProductComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FormProductComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/admin/components/form-product/form-product.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | import { ProductsService } from 'src/app/core/services/products/products.service'; 4 | import { Router } from '@angular/router'; 5 | import { MyValidators } from '../../../utils/validators'; 6 | @Component({ 7 | selector: 'app-form-product', 8 | templateUrl: './form-product.component.html', 9 | styleUrls: ['./form-product.component.scss'] 10 | }) 11 | export class FormProductComponent implements OnInit { 12 | 13 | form: FormGroup; 14 | 15 | constructor( 16 | private formBuilder: FormBuilder, 17 | private productsService: ProductsService, 18 | private router: Router, 19 | ){ 20 | this.buildForm(); 21 | } 22 | 23 | ngOnInit(): void { 24 | } 25 | 26 | saveProduct(event: Event) { 27 | event.preventDefault(); 28 | // console.log( Object.keys(this.form.get('title').errors) ); 29 | 30 | 31 | if(this.form.valid) { 32 | const newProduct = this.form.value; 33 | this.productsService.createProduct(newProduct) 34 | .subscribe( product => { 35 | this.router.navigate(['/admin/products']) 36 | console.log(product); 37 | }); 38 | } 39 | } 40 | 41 | private buildForm() { 42 | this.form = this.formBuilder.group({ 43 | id: ['', Validators.required], 44 | title: ['', Validators.required], 45 | price: [ 46 | 0, 47 | [ 48 | Validators.required, 49 | MyValidators.isPriceValid 50 | ] 51 | ], 52 | image: [''], 53 | description: ['', Validators.required], 54 | }); 55 | } 56 | 57 | get priceField() { 58 | return this.form.get('price'); 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/app/admin/components/list-products/list-products-datasource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from '@angular/cdk/collections'; 2 | import { MatPaginator } from '@angular/material/paginator'; 3 | import { MatSort } from '@angular/material/sort'; 4 | import { map } from 'rxjs/operators'; 5 | import { Observable, of as observableOf, merge } from 'rxjs'; 6 | 7 | // TODO: Replace this with your own data model type 8 | export interface ListProductsItem { 9 | name: string; 10 | id: number; 11 | } 12 | 13 | // TODO: replace this with real data from your application 14 | const EXAMPLE_DATA: ListProductsItem[] = [ 15 | {id: 1, name: 'Hydrogen'}, 16 | {id: 2, name: 'Helium'}, 17 | {id: 3, name: 'Lithium'}, 18 | {id: 4, name: 'Beryllium'}, 19 | {id: 5, name: 'Boron'}, 20 | {id: 6, name: 'Carbon'}, 21 | {id: 7, name: 'Nitrogen'}, 22 | {id: 8, name: 'Oxygen'}, 23 | {id: 9, name: 'Fluorine'}, 24 | {id: 10, name: 'Neon'}, 25 | {id: 11, name: 'Sodium'}, 26 | {id: 12, name: 'Magnesium'}, 27 | {id: 13, name: 'Aluminum'}, 28 | {id: 14, name: 'Silicon'}, 29 | {id: 15, name: 'Phosphorus'}, 30 | {id: 16, name: 'Sulfur'}, 31 | {id: 17, name: 'Chlorine'}, 32 | {id: 18, name: 'Argon'}, 33 | {id: 19, name: 'Potassium'}, 34 | {id: 20, name: 'Calcium'}, 35 | ]; 36 | 37 | /** 38 | * Data source for the ListProducts view. This class should 39 | * encapsulate all logic for fetching and manipulating the displayed data 40 | * (including sorting, pagination, and filtering). 41 | */ 42 | export class ListProductsDataSource extends DataSource { 43 | data: ListProductsItem[] = EXAMPLE_DATA; 44 | paginator: MatPaginator; 45 | sort: MatSort; 46 | 47 | constructor() { 48 | super(); 49 | } 50 | 51 | /** 52 | * Connect this data source to the table. The table will only update when 53 | * the returned stream emits new items. 54 | * @returns A stream of the items to be rendered. 55 | */ 56 | connect(): Observable { 57 | // Combine everything that affects the rendered data into one update 58 | // stream for the data-table to consume. 59 | const dataMutations = [ 60 | observableOf(this.data), 61 | this.paginator.page, 62 | this.sort.sortChange 63 | ]; 64 | 65 | return merge(...dataMutations).pipe(map(() => { 66 | return this.getPagedData(this.getSortedData([...this.data])); 67 | })); 68 | } 69 | 70 | /** 71 | * Called when the table is being destroyed. Use this function, to clean up 72 | * any open connections or free any held resources that were set up during connect. 73 | */ 74 | disconnect() {} 75 | 76 | /** 77 | * Paginate the data (client-side). If you're using server-side pagination, 78 | * this would be replaced by requesting the appropriate data from the server. 79 | */ 80 | private getPagedData(data: ListProductsItem[]) { 81 | const startIndex = this.paginator.pageIndex * this.paginator.pageSize; 82 | return data.splice(startIndex, this.paginator.pageSize); 83 | } 84 | 85 | /** 86 | * Sort the data (client-side). If you're using server-side sorting, 87 | * this would be replaced by requesting the appropriate data from the server. 88 | */ 89 | private getSortedData(data: ListProductsItem[]) { 90 | if (!this.sort.active || this.sort.direction === '') { 91 | return data; 92 | } 93 | 94 | return data.sort((a, b) => { 95 | const isAsc = this.sort.direction === 'asc'; 96 | switch (this.sort.active) { 97 | case 'name': return compare(a.name, b.name, isAsc); 98 | case 'id': return compare(+a.id, +b.id, isAsc); 99 | default: return 0; 100 | } 101 | }); 102 | } 103 | } 104 | 105 | /** Simple sort comparator for example ID/Name columns (for client-side sorting). */ 106 | function compare(a: string | number, b: string | number, isAsc: boolean) { 107 | return (a < b ? -1 : 1) * (isAsc ? 1 : -1); 108 | } 109 | -------------------------------------------------------------------------------- /src/app/admin/components/list-products/list-products.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
Id{{row.id}}Name{{row.name}}
18 | 19 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /src/app/admin/components/list-products/list-products.component.scss: -------------------------------------------------------------------------------- 1 | .full-width-table { 2 | width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/admin/components/list-products/list-products.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { MatPaginatorModule } from '@angular/material/paginator'; 4 | import { MatSortModule } from '@angular/material/sort'; 5 | import { MatTableModule } from '@angular/material/table'; 6 | 7 | import { ListProductsComponent } from './list-products.component'; 8 | 9 | describe('ListProductsComponent', () => { 10 | let component: ListProductsComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [ ListProductsComponent ], 16 | imports: [ 17 | NoopAnimationsModule, 18 | MatPaginatorModule, 19 | MatSortModule, 20 | MatTableModule, 21 | ] 22 | }).compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(ListProductsComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should compile', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/admin/components/list-products/list-products.component.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'; 2 | import { MatPaginator } from '@angular/material/paginator'; 3 | import { MatSort } from '@angular/material/sort'; 4 | import { MatTable } from '@angular/material/table'; 5 | import { ListProductsDataSource, ListProductsItem } from './list-products-datasource'; 6 | 7 | @Component({ 8 | selector: 'app-list-products', 9 | templateUrl: './list-products.component.html', 10 | styleUrls: ['./list-products.component.scss'] 11 | }) 12 | export class ListProductsComponent implements AfterViewInit, OnInit { 13 | @ViewChild(MatPaginator) paginator: MatPaginator; 14 | @ViewChild(MatSort) sort: MatSort; 15 | @ViewChild(MatTable) table: MatTable; 16 | dataSource: ListProductsDataSource; 17 | 18 | /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ 19 | displayedColumns = ['id', 'name']; 20 | 21 | ngOnInit() { 22 | this.dataSource = new ListProductsDataSource(); 23 | } 24 | 25 | ngAfterViewInit() { 26 | this.dataSource.sort = this.sort; 27 | this.dataSource.paginator = this.paginator; 28 | this.table.dataSource = this.dataSource; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/admin/components/nav/nav.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | Menu 8 | 9 | 10 | 11 | 12 | Products 13 | Create Product 14 | 15 | 16 | 17 | 18 | 19 | 27 | platzi-store 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/app/admin/components/nav/nav.component.scss: -------------------------------------------------------------------------------- 1 | .sidenav-container { 2 | height: 100%; 3 | } 4 | 5 | .sidenav { 6 | width: 200px; 7 | } 8 | 9 | .sidenav .mat-toolbar { 10 | background: inherit; 11 | } 12 | 13 | .mat-toolbar.mat-primary { 14 | position: sticky; 15 | top: 0; 16 | z-index: 1; 17 | } 18 | 19 | .example-icon { 20 | padding: 0 14px; 21 | } 22 | 23 | .example-spacer { 24 | flex: 1 1 auto; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/admin/components/nav/nav.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { LayoutModule } from '@angular/cdk/layout'; 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 4 | import { MatButtonModule } from '@angular/material/button'; 5 | import { MatIconModule } from '@angular/material/icon'; 6 | import { MatListModule } from '@angular/material/list'; 7 | import { MatSidenavModule } from '@angular/material/sidenav'; 8 | import { MatToolbarModule } from '@angular/material/toolbar'; 9 | 10 | import { NavComponent } from './nav.component'; 11 | 12 | describe('NavComponent', () => { 13 | let component: NavComponent; 14 | let fixture: ComponentFixture; 15 | 16 | beforeEach(async(() => { 17 | TestBed.configureTestingModule({ 18 | declarations: [NavComponent], 19 | imports: [ 20 | NoopAnimationsModule, 21 | LayoutModule, 22 | MatButtonModule, 23 | MatIconModule, 24 | MatListModule, 25 | MatSidenavModule, 26 | MatToolbarModule, 27 | ] 28 | }).compileComponents(); 29 | })); 30 | 31 | beforeEach(() => { 32 | fixture = TestBed.createComponent(NavComponent); 33 | component = fixture.componentInstance; 34 | fixture.detectChanges(); 35 | }); 36 | 37 | it('should compile', () => { 38 | expect(component).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/app/admin/components/nav/nav.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; 3 | import { Observable } from 'rxjs'; 4 | import { map, shareReplay } from 'rxjs/operators'; 5 | 6 | @Component({ 7 | selector: 'app-nav', 8 | templateUrl: './nav.component.html', 9 | styleUrls: ['./nav.component.scss'] 10 | }) 11 | export class NavComponent { 12 | 13 | isHandset$: Observable = this.breakpointObserver.observe(Breakpoints.Handset) 14 | .pipe( 15 | map(result => result.matches), 16 | shareReplay() 17 | ); 18 | 19 | constructor(private breakpointObserver: BreakpointObserver) {} 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/app/admin/components/product-edit/product-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Producto 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | 19 | The price is invalid 20 | 21 |
22 |
23 |
24 |
25 | 26 | 28 | 29 |
30 |
31 |
32 | 33 | 34 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /src/app/admin/components/product-edit/product-edit.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/admin/components/product-edit/product-edit.component.scss -------------------------------------------------------------------------------- /src/app/admin/components/product-edit/product-edit.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductEditComponent } from './product-edit.component'; 4 | 5 | describe('ProductEditComponent', () => { 6 | let component: ProductEditComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ProductEditComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProductEditComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/admin/components/product-edit/product-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | import { ProductsService } from 'src/app/core/services/products/products.service'; 4 | import { Router, ActivatedRoute, Params } from '@angular/router'; 5 | import { MyValidators } from '../../../utils/validators'; 6 | import { Product } from 'src/app/product.model'; 7 | 8 | @Component({ 9 | selector: 'app-form-product', 10 | templateUrl: './product-edit.component.html', 11 | styleUrls: ['./product-edit.component.scss'] 12 | }) 13 | export class ProductEditComponent implements OnInit { 14 | 15 | form: FormGroup; 16 | id: string; 17 | 18 | constructor( 19 | private formBuilder: FormBuilder, 20 | private productsService: ProductsService, 21 | private router: Router, 22 | private activeRoute: ActivatedRoute, 23 | ){ 24 | this.buildForm(); 25 | } 26 | 27 | ngOnInit(): void { 28 | this.activeRoute.params.subscribe((params: Params) => { 29 | this.id = params.id; 30 | this.productsService.getProduct(this.id) 31 | .subscribe((product: Product) => { 32 | this.form.patchValue(product); 33 | }) 34 | }) 35 | } 36 | 37 | saveProduct(event: Event) { 38 | event.preventDefault(); 39 | if(this.form.valid) { 40 | const updatedProduct = this.form.value; 41 | this.productsService.updateProduct(this.id, updatedProduct) 42 | .subscribe( product => { 43 | this.router.navigate(['/admin/products']) 44 | console.log(product); 45 | }); 46 | } 47 | } 48 | 49 | private buildForm() { 50 | this.form = this.formBuilder.group({ 51 | title: ['', Validators.required], 52 | price: [ 53 | 0, 54 | [ 55 | Validators.required, 56 | MyValidators.isPriceValid 57 | ] 58 | ], 59 | image: [''], 60 | description: ['', Validators.required], 61 | }); 62 | } 63 | 64 | get priceField() { 65 | return this.form.get('price'); 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/app/admin/components/product-form/product-form.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Shipping Information 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 | First name is required 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | Last name is required 28 | 29 | 30 |
31 |
32 |
33 |
34 | 35 | 36 | 37 | Address is required 38 | 39 | 40 |
41 |
42 |
43 |
44 | 47 |
48 |
49 |
50 |
51 | 52 | 53 | 54 |
55 |
56 |
57 |
58 | 59 | 60 | 61 | City is required 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | {{ state.name }} 70 | 71 | 72 | 73 | State is required 74 | 75 | 76 |
77 |
78 |
79 |
80 | 81 | 82 | {{postalCode.value.length}} / 5 83 | 84 |
85 |
86 |
87 |
88 | 89 | Free Shipping 90 | Priority Shipping 91 | Next Day Shipping 92 | 93 |
94 |
95 |
96 | 97 | 98 | 99 |
100 |
101 | -------------------------------------------------------------------------------- /src/app/admin/components/product-form/product-form.component.scss: -------------------------------------------------------------------------------- 1 | .full-width { 2 | width: 100%; 3 | } 4 | 5 | .shipping-card { 6 | min-width: 120px; 7 | margin: 20px auto; 8 | } 9 | 10 | .mat-radio-button { 11 | display: block; 12 | margin: 5px 0; 13 | } 14 | 15 | .row { 16 | display: flex; 17 | flex-direction: row; 18 | } 19 | 20 | .col { 21 | flex: 1; 22 | margin-right: 20px; 23 | } 24 | 25 | .col:last-child { 26 | margin-right: 0; 27 | } 28 | -------------------------------------------------------------------------------- /src/app/admin/components/product-form/product-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { ReactiveFormsModule } from '@angular/forms'; 3 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 4 | import { MatButtonModule } from '@angular/material/button'; 5 | import { MatCardModule } from '@angular/material/card'; 6 | import { MatInputModule } from '@angular/material/input'; 7 | import { MatRadioModule } from '@angular/material/radio'; 8 | import { MatSelectModule } from '@angular/material/select'; 9 | 10 | import { ProductFormComponent } from './product-form.component'; 11 | 12 | describe('ProductFormComponent', () => { 13 | let component: ProductFormComponent; 14 | let fixture: ComponentFixture; 15 | 16 | beforeEach(async(() => { 17 | TestBed.configureTestingModule({ 18 | declarations: [ ProductFormComponent ], 19 | imports: [ 20 | NoopAnimationsModule, 21 | ReactiveFormsModule, 22 | MatButtonModule, 23 | MatCardModule, 24 | MatInputModule, 25 | MatRadioModule, 26 | MatSelectModule, 27 | ] 28 | }).compileComponents(); 29 | })); 30 | 31 | beforeEach(() => { 32 | fixture = TestBed.createComponent(ProductFormComponent); 33 | component = fixture.componentInstance; 34 | fixture.detectChanges(); 35 | }); 36 | 37 | it('should compile', () => { 38 | expect(component).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/app/admin/components/product-form/product-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FormBuilder, Validators } from '@angular/forms'; 3 | 4 | @Component({ 5 | selector: 'app-product-form', 6 | templateUrl: './product-form.component.html', 7 | styleUrls: ['./product-form.component.scss'] 8 | }) 9 | export class ProductFormComponent { 10 | addressForm = this.fb.group({ 11 | company: null, 12 | firstName: [null, Validators.required], 13 | lastName: [null, Validators.required], 14 | address: [null, Validators.required], 15 | address2: null, 16 | city: [null, Validators.required], 17 | state: [null, Validators.required], 18 | postalCode: [null, Validators.compose([ 19 | Validators.required, Validators.minLength(5), Validators.maxLength(5)]) 20 | ], 21 | shipping: ['free', Validators.required] 22 | }); 23 | 24 | hasUnitNumber = false; 25 | 26 | states = [ 27 | {name: 'Alabama', abbreviation: 'AL'}, 28 | {name: 'Alaska', abbreviation: 'AK'}, 29 | {name: 'American Samoa', abbreviation: 'AS'}, 30 | {name: 'Arizona', abbreviation: 'AZ'}, 31 | {name: 'Arkansas', abbreviation: 'AR'}, 32 | {name: 'California', abbreviation: 'CA'}, 33 | {name: 'Colorado', abbreviation: 'CO'}, 34 | {name: 'Connecticut', abbreviation: 'CT'}, 35 | {name: 'Delaware', abbreviation: 'DE'}, 36 | {name: 'District Of Columbia', abbreviation: 'DC'}, 37 | {name: 'Federated States Of Micronesia', abbreviation: 'FM'}, 38 | {name: 'Florida', abbreviation: 'FL'}, 39 | {name: 'Georgia', abbreviation: 'GA'}, 40 | {name: 'Guam', abbreviation: 'GU'}, 41 | {name: 'Hawaii', abbreviation: 'HI'}, 42 | {name: 'Idaho', abbreviation: 'ID'}, 43 | {name: 'Illinois', abbreviation: 'IL'}, 44 | {name: 'Indiana', abbreviation: 'IN'}, 45 | {name: 'Iowa', abbreviation: 'IA'}, 46 | {name: 'Kansas', abbreviation: 'KS'}, 47 | {name: 'Kentucky', abbreviation: 'KY'}, 48 | {name: 'Louisiana', abbreviation: 'LA'}, 49 | {name: 'Maine', abbreviation: 'ME'}, 50 | {name: 'Marshall Islands', abbreviation: 'MH'}, 51 | {name: 'Maryland', abbreviation: 'MD'}, 52 | {name: 'Massachusetts', abbreviation: 'MA'}, 53 | {name: 'Michigan', abbreviation: 'MI'}, 54 | {name: 'Minnesota', abbreviation: 'MN'}, 55 | {name: 'Mississippi', abbreviation: 'MS'}, 56 | {name: 'Missouri', abbreviation: 'MO'}, 57 | {name: 'Montana', abbreviation: 'MT'}, 58 | {name: 'Nebraska', abbreviation: 'NE'}, 59 | {name: 'Nevada', abbreviation: 'NV'}, 60 | {name: 'New Hampshire', abbreviation: 'NH'}, 61 | {name: 'New Jersey', abbreviation: 'NJ'}, 62 | {name: 'New Mexico', abbreviation: 'NM'}, 63 | {name: 'New York', abbreviation: 'NY'}, 64 | {name: 'North Carolina', abbreviation: 'NC'}, 65 | {name: 'North Dakota', abbreviation: 'ND'}, 66 | {name: 'Northern Mariana Islands', abbreviation: 'MP'}, 67 | {name: 'Ohio', abbreviation: 'OH'}, 68 | {name: 'Oklahoma', abbreviation: 'OK'}, 69 | {name: 'Oregon', abbreviation: 'OR'}, 70 | {name: 'Palau', abbreviation: 'PW'}, 71 | {name: 'Pennsylvania', abbreviation: 'PA'}, 72 | {name: 'Puerto Rico', abbreviation: 'PR'}, 73 | {name: 'Rhode Island', abbreviation: 'RI'}, 74 | {name: 'South Carolina', abbreviation: 'SC'}, 75 | {name: 'South Dakota', abbreviation: 'SD'}, 76 | {name: 'Tennessee', abbreviation: 'TN'}, 77 | {name: 'Texas', abbreviation: 'TX'}, 78 | {name: 'Utah', abbreviation: 'UT'}, 79 | {name: 'Vermont', abbreviation: 'VT'}, 80 | {name: 'Virgin Islands', abbreviation: 'VI'}, 81 | {name: 'Virginia', abbreviation: 'VA'}, 82 | {name: 'Washington', abbreviation: 'WA'}, 83 | {name: 'West Virginia', abbreviation: 'WV'}, 84 | {name: 'Wisconsin', abbreviation: 'WI'}, 85 | {name: 'Wyoming', abbreviation: 'WY'} 86 | ]; 87 | 88 | constructor(private fb: FormBuilder) {} 89 | 90 | onSubmit() { 91 | alert('Thanks!'); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/app/admin/components/products-list/products-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | Crear producto 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 |
ID {{product.id}} title {{product.title}} price {{product.price}} actions 20 | 21 | 22 |
27 | -------------------------------------------------------------------------------- /src/app/admin/components/products-list/products-list.component.scss: -------------------------------------------------------------------------------- 1 | table { 2 | width: 96%; 3 | margin: 2%; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/admin/components/products-list/products-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductsListComponent } from './products-list.component'; 4 | 5 | describe('ProductsListComponent', () => { 6 | let component: ProductsListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ProductsListComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProductsListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/admin/components/products-list/products-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ProductsService } from 'src/app/core/services/products/products.service'; 3 | import { Product } from 'src/app/product.model'; 4 | 5 | @Component({ 6 | selector: 'app-products-list', 7 | templateUrl: './products-list.component.html', 8 | styleUrls: ['./products-list.component.scss'] 9 | }) 10 | export class ProductsListComponent implements OnInit { 11 | 12 | products: Product[] = []; 13 | displayedColumns: string[] = ['id', 'title', 'price', 'actions']; 14 | 15 | constructor( 16 | private productService: ProductsService 17 | ) { } 18 | 19 | ngOnInit(): void { 20 | this.fetchProducts(); 21 | } 22 | 23 | fetchProducts(){ 24 | this.productService.getAllProducts() 25 | .subscribe((products)=>{ 26 | this.products = products; 27 | }) 28 | } 29 | 30 | deleteProduct(id: string) { 31 | this.productService.deleteProduct(id).subscribe(deleted => { 32 | if (deleted){ 33 | console.log(`Product ${id} deleted`) 34 | this.products = this.products.filter(product => product.id !== id) 35 | }else { 36 | console.error('Unable to delete the product'); 37 | } 38 | }); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; 3 | 4 | import { TestComponent } from './test/test.component'; 5 | import { NotFoundComponent } from './components/not-found/not-found.component'; 6 | import { LayoutComponent } from './layout/layout.component'; 7 | 8 | import { AdminGuard } from './guardians/admin.guard' 9 | 10 | const routes: Routes = [ 11 | { 12 | path: '', 13 | component: LayoutComponent, 14 | children: [ 15 | { 16 | path: '', 17 | redirectTo: '/home', 18 | pathMatch: 'full', 19 | }, 20 | { 21 | path: 'home', 22 | loadChildren: () => import('./home/home.module').then(m => m.HomeModule) 23 | }, 24 | { 25 | path: 'products', 26 | loadChildren: () => import('./products/products.module').then(m => m.ProductsModule) 27 | }, 28 | { 29 | path: 'contact', 30 | loadChildren: () => import('./contact/contact.module').then(m => m.ContactModule) 31 | }, 32 | { 33 | path: 'order', 34 | loadChildren: () => import('./order/order.module').then(m => m.OrderModule) 35 | }, 36 | ] 37 | }, 38 | { 39 | path: 'demo', 40 | canActivate: [AdminGuard], 41 | component: TestComponent 42 | }, 43 | { 44 | path: 'admin', 45 | loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) 46 | }, 47 | { 48 | path: '**', 49 | component: NotFoundComponent 50 | } 51 | ]; 52 | 53 | @NgModule({ 54 | imports: [RouterModule.forRoot(routes, { 55 | preloadingStrategy: PreloadAllModules 56 | })], 57 | exports: [RouterModule] 58 | }) 59 | export class AppRoutingModule { } 60 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'platzi-store'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.componentInstance; 26 | expect(app.title).toEqual('platzi-store'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.nativeElement; 33 | expect(compiled.querySelector('.content span').textContent).toContain('platzi-store app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {Product} from './product.model' 3 | @Component({ 4 | selector: 'app-root', 5 | template: '', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent {} 9 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { LOCALE_ID, NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms' 4 | import { HttpClientModule } from '@angular/common/http' 5 | 6 | import localeEs from '@angular/common/locales/es-MX'; 7 | import { AppRoutingModule } from './app-routing.module'; 8 | import { AppComponent } from './app.component'; 9 | import { TestComponent } from './test/test.component'; 10 | import { CartComponent } from './components/cart/cart.component'; 11 | import { registerLocaleData } from '@angular/common'; 12 | import { NotFoundComponent } from './components/not-found/not-found.component'; 13 | import { LayoutComponent } from './layout/layout.component'; 14 | 15 | import { SharedModule } from './shared/shared.module' 16 | import { CoreModule } from './core/core.module'; 17 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations' 18 | 19 | registerLocaleData(localeEs); 20 | 21 | @NgModule({ 22 | declarations: [ 23 | AppComponent, 24 | TestComponent, 25 | CartComponent, 26 | NotFoundComponent, 27 | LayoutComponent 28 | ], 29 | imports: [ 30 | BrowserModule, 31 | AppRoutingModule, 32 | FormsModule, 33 | SharedModule, 34 | CoreModule, 35 | BrowserAnimationsModule, 36 | HttpClientModule 37 | ], 38 | providers: [ { provide: LOCALE_ID, useValue: 'es-mx' } ], 39 | bootstrap: [AppComponent] 40 | }) 41 | export class AppModule { } 42 | -------------------------------------------------------------------------------- /src/app/components/cart/cart.component.html: -------------------------------------------------------------------------------- 1 |

cart works!

2 | -------------------------------------------------------------------------------- /src/app/components/cart/cart.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/components/cart/cart.component.scss -------------------------------------------------------------------------------- /src/app/components/cart/cart.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CartComponent } from './cart.component'; 4 | 5 | describe('CartComponent', () => { 6 | let component: CartComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CartComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CartComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/cart/cart.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-cart', 5 | templateUrl: './cart.component.html', 6 | styleUrls: ['./cart.component.scss'] 7 | }) 8 | export class CartComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/components/not-found/not-found.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |

404

8 |

Looks like the page you were looking for is no longer here.

9 |
10 |
11 |
12 | 13 | 14 | 16 | 17 | 18 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 34 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | 51 | 52 |
53 |
54 | -------------------------------------------------------------------------------- /src/app/components/not-found/not-found.component.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Fira+Sans"); 2 | /*Variables */ 3 | .left-section .inner-content { 4 | position: absolute; 5 | top: 50%; 6 | transform: translateY(-50%); 7 | } 8 | 9 | * { 10 | box-sizing: border-box; 11 | } 12 | 13 | html, body { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | body { 19 | font-family: "Fira Sans", sans-serif; 20 | color: #f5f6fa; 21 | } 22 | 23 | .background { 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | width: 100%; 28 | height: 100%; 29 | background: linear-gradient(#0C0E10, #446182); 30 | } 31 | .background .ground { 32 | position: absolute; 33 | bottom: 0; 34 | width: 100%; 35 | height: 25vh; 36 | background: #0C0E10; 37 | } 38 | @media (max-width: 770px) { 39 | .background .ground { 40 | height: 0vh; 41 | } 42 | } 43 | 44 | .container { 45 | position: relative; 46 | margin: 0 auto; 47 | width: 85%; 48 | height: 100vh; 49 | padding-bottom: 25vh; 50 | display: flex; 51 | flex-direction: row; 52 | justify-content: space-around; 53 | } 54 | @media (max-width: 770px) { 55 | .container { 56 | flex-direction: column; 57 | padding-bottom: 0vh; 58 | } 59 | } 60 | 61 | .left-section, .right-section { 62 | position: relative; 63 | } 64 | 65 | .left-section { 66 | width: 40%; 67 | } 68 | @media (max-width: 770px) { 69 | .left-section { 70 | width: 100%; 71 | height: 40%; 72 | position: absolute; 73 | top: 0; 74 | } 75 | } 76 | @media (max-width: 770px) { 77 | .left-section .inner-content { 78 | position: relative; 79 | padding: 1rem 0; 80 | } 81 | } 82 | 83 | .heading { 84 | text-align: center; 85 | font-size: 9em; 86 | line-height: 1.3em; 87 | margin: 2rem 0 0.5rem 0; 88 | padding: 0; 89 | text-shadow: 0 0 1rem #fefefe; 90 | } 91 | @media (max-width: 770px) { 92 | .heading { 93 | font-size: 7em; 94 | line-height: 1.15; 95 | margin: 0; 96 | } 97 | } 98 | 99 | .subheading { 100 | text-align: center; 101 | max-width: 480px; 102 | font-size: 1.5em; 103 | line-height: 1.15em; 104 | padding: 0 1rem; 105 | margin: 0 auto; 106 | } 107 | @media (max-width: 770px) { 108 | .subheading { 109 | font-size: 1.3em; 110 | line-height: 1.15; 111 | max-width: 100%; 112 | } 113 | } 114 | 115 | .right-section { 116 | width: 50%; 117 | } 118 | @media (max-width: 770px) { 119 | .right-section { 120 | width: 100%; 121 | height: 60%; 122 | position: absolute; 123 | bottom: 0; 124 | } 125 | } 126 | 127 | .svgimg { 128 | position: absolute; 129 | bottom: 0; 130 | padding-top: 10vh; 131 | padding-left: 1vh; 132 | max-width: 100%; 133 | max-height: 100%; 134 | } 135 | @media (max-width: 770px) { 136 | .svgimg { 137 | padding: 0; 138 | } 139 | } 140 | .svgimg .bench-legs { 141 | fill: #0C0E10; 142 | } 143 | .svgimg .top-bench, .svgimg .bottom-bench { 144 | stroke: #0C0E10; 145 | stroke-width: 1px; 146 | fill: #5B3E2B; 147 | } 148 | .svgimg .bottom-bench path:nth-child(1) { 149 | fill: #432d20; 150 | } 151 | .svgimg .lamp-details { 152 | fill: #202425; 153 | } 154 | .svgimg .lamp-accent { 155 | fill: #2c3133; 156 | } 157 | .svgimg .lamp-bottom { 158 | fill: linear-gradient(#202425, #0C0E10); 159 | } 160 | .svgimg .lamp-light { 161 | fill: #EFEFEF; 162 | } 163 | 164 | @keyframes glow { 165 | 0% { 166 | text-shadow: 0 0 1rem #fefefe; 167 | } 168 | 50% { 169 | text-shadow: 0 0 1.85rem #ededed; 170 | } 171 | 100% { 172 | text-shadow: 0 0 1rem #fefefe; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/app/components/not-found/not-found.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NotFoundComponent } from './not-found.component'; 4 | 5 | describe('NotFoundComponent', () => { 6 | let component: NotFoundComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ NotFoundComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(NotFoundComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/not-found/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-not-found', 5 | templateUrl: './not-found.component.html', 6 | styleUrls: ['./not-found.component.scss'] 7 | }) 8 | export class NotFoundComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/contact/components/contact.component.html: -------------------------------------------------------------------------------- 1 |

contact works!

2 | -------------------------------------------------------------------------------- /src/app/contact/components/contact.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/contact/components/contact.component.scss -------------------------------------------------------------------------------- /src/app/contact/components/contact.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ContactComponent } from './contact.component'; 4 | 5 | describe('ContactComponent', () => { 6 | let component: ContactComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ContactComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ContactComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/contact/components/contact.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-contact', 5 | templateUrl: './contact.component.html', 6 | styleUrls: ['./contact.component.scss'] 7 | }) 8 | export class ContactComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/contact/contact-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ContactComponent } from './components/contact.component'; 3 | 4 | import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: ContactComponent 10 | }, 11 | ] 12 | 13 | 14 | @NgModule({ 15 | imports: [ 16 | RouterModule.forChild(routes), 17 | ], 18 | exports: [RouterModule] 19 | }) 20 | export class ContactRouterModule { } 21 | -------------------------------------------------------------------------------- /src/app/contact/contact.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { ContactComponent } from './components/contact.component'; 5 | 6 | import { ContactRouterModule } from './contact-routing.module'; 7 | 8 | import { SharedModule } from '../shared/shared.module' 9 | 10 | @NgModule({ 11 | declarations: [ 12 | ContactComponent 13 | ], 14 | imports: [ 15 | CommonModule, 16 | SharedModule, 17 | ContactRouterModule 18 | ] 19 | }) 20 | export class ContactModule { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { ProductsService } from './services/products/products.service'; 5 | 6 | 7 | @NgModule({ 8 | declarations: [], 9 | imports: [ 10 | CommonModule 11 | ], 12 | providers: [ 13 | ProductsService 14 | ] 15 | }) 16 | export class CoreModule { } 17 | -------------------------------------------------------------------------------- /src/app/core/services/cart.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { CartService } from './cart.service'; 4 | 5 | describe('CartService', () => { 6 | let service: CartService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(CartService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/core/services/cart.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Product } from 'src/app/product.model'; 3 | import { BehaviorSubject } from 'rxjs'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class CartService { 9 | private products: Product[] = []; 10 | private cart = new BehaviorSubject([]); 11 | cart$ = this.cart.asObservable(); 12 | constructor() { } 13 | addToCart(product: Product) { 14 | this.products = [...this.products, product]; 15 | this.cart.next(this.products); 16 | } 17 | removeFromCart(id: string){} 18 | getCart(){} 19 | } 20 | -------------------------------------------------------------------------------- /src/app/core/services/products/products.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductsService } from './products.service'; 4 | 5 | describe('ProductsService', () => { 6 | let service: ProductsService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ProductsService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/core/services/products/products.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Product } from '../../../product.model'; 3 | import { HttpClient } from '@angular/common/http'; 4 | 5 | import { environment } from '../../../../environments/environment' 6 | import { Observable } from 'rxjs'; 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class ProductsService { 11 | 12 | constructor( 13 | private http: HttpClient, 14 | ) { } 15 | getAllProducts(): Observable { 16 | return this.http.get(`${environment.url_api}/products`); 17 | } 18 | getProduct(id: string): Observable { 19 | return this.http.get(`${environment.url_api}/products/${id}`); 20 | } 21 | createProduct(product: Product): any { 22 | return this.http.post(`${environment.url_api}/products`, product); 23 | } 24 | updateProduct(id: string, changes: Partial): any { 25 | return this.http.put(`${environment.url_api}/products/${id}`, changes); 26 | } 27 | 28 | deleteProduct(id: string): any { 29 | return this.http.delete(`${environment.url_api}/products/${id}`); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/guardians/admin.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AdminGuard } from './admin.guard'; 4 | 5 | describe('AdminGuard', () => { 6 | let guard: AdminGuard; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | guard = TestBed.inject(AdminGuard); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(guard).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/guardians/admin.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class AdminGuard implements CanActivate { 9 | canActivate( 10 | next: ActivatedRouteSnapshot, 11 | state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { 12 | return false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/home/components/banner/banner.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | banner image 5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /src/app/home/components/banner/banner.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/home/components/banner/banner.component.scss -------------------------------------------------------------------------------- /src/app/home/components/banner/banner.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { BannerComponent } from './banner.component'; 4 | 5 | describe('BannerComponent', () => { 6 | let component: BannerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ BannerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(BannerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/home/components/banner/banner.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, AfterViewInit } from '@angular/core'; 2 | 3 | import Swiper from 'swiper'; 4 | 5 | @Component({ 6 | selector: 'app-banner', 7 | templateUrl: './banner.component.html', 8 | styleUrls: ['./banner.component.scss'] 9 | }) 10 | export class BannerComponent implements OnInit, AfterViewInit { 11 | 12 | mySwiper:Swiper 13 | 14 | images: string[] = [ 15 | '/assets/images/banner-1.jpg', 16 | '/assets/images/banner-2.jpg', 17 | '/assets/images/banner-3.jpg', 18 | ] 19 | 20 | constructor() { } 21 | 22 | ngOnInit(): void { 23 | 24 | } 25 | ngAfterViewInit(): void { 26 | this.mySwiper = new Swiper('.swiper-container') 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/home/components/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/home/components/home/home.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/home/components/home/home.component.scss -------------------------------------------------------------------------------- /src/app/home/components/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/home/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; 3 | 4 | import { HomeComponent } from './components/home/home.component'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: HomeComponent 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [ 15 | RouterModule.forChild(routes), 16 | ], 17 | exports: [ RouterModule ] 18 | }) 19 | export class HomeRoutingModule {} 20 | -------------------------------------------------------------------------------- /src/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { BannerComponent } from './components/banner/banner.component'; 5 | import { HomeComponent } from './components/home/home.component'; 6 | 7 | import { HomeRoutingModule } from './home-routing.module'; 8 | 9 | import { SharedModule } from '../shared/shared.module' 10 | 11 | @NgModule({ 12 | declarations: [ 13 | BannerComponent, 14 | HomeComponent 15 | ], 16 | imports: [ 17 | CommonModule, 18 | SharedModule, 19 | HomeRoutingModule 20 | ] 21 | }) 22 | export class HomeModule { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/layout/layout.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/layout/layout.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/layout/layout.component.scss -------------------------------------------------------------------------------- /src/app/layout/layout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LayoutComponent } from './layout.component'; 4 | 5 | describe('LayoutComponent', () => { 6 | let component: LayoutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LayoutComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LayoutComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/layout/layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-layout', 5 | templateUrl: './layout.component.html', 6 | styleUrls: ['./layout.component.scss'] 7 | }) 8 | export class LayoutComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/material/material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { MatButtonModule } from '@angular/material/button'; 5 | import { MatToolbarModule } from '@angular/material/toolbar'; 6 | import { MatIconModule } from '@angular/material/icon'; 7 | import { MatBadgeModule } from '@angular/material/badge'; 8 | import { MatCardModule } from '@angular/material/card'; 9 | import { MatInputModule } from '@angular/material/input'; 10 | import { MatSelectModule } from '@angular/material/select'; 11 | import { MatRadioModule } from '@angular/material/radio'; 12 | import { ReactiveFormsModule } from '@angular/forms'; 13 | import { MatSidenavModule } from '@angular/material/sidenav'; 14 | import { MatListModule } from '@angular/material/list'; 15 | import { MatTableModule } from '@angular/material/table'; 16 | import { MatPaginatorModule } from '@angular/material/paginator'; 17 | import { MatSortModule } from '@angular/material/sort'; 18 | import { MatGridListModule } from '@angular/material/grid-list'; 19 | import { MatMenuModule } from '@angular/material/menu'; 20 | import {MatFormFieldModule} from '@angular/material/form-field'; 21 | import {MatStepperModule} from '@angular/material/stepper'; 22 | 23 | 24 | @NgModule({ 25 | declarations: [], 26 | imports: [ 27 | CommonModule, 28 | MatButtonModule, 29 | MatToolbarModule, 30 | MatIconModule, 31 | MatBadgeModule, 32 | MatCardModule, 33 | MatInputModule, 34 | MatSelectModule, 35 | MatRadioModule, 36 | ReactiveFormsModule, 37 | MatSidenavModule, 38 | MatTableModule, 39 | MatPaginatorModule, 40 | MatSortModule, 41 | MatGridListModule, 42 | MatMenuModule, 43 | MatFormFieldModule, 44 | MatStepperModule, 45 | ], 46 | exports: [ 47 | MatButtonModule, 48 | MatToolbarModule, 49 | MatIconModule, 50 | MatBadgeModule, 51 | MatCardModule, 52 | MatInputModule, 53 | MatSelectModule, 54 | MatRadioModule, 55 | ReactiveFormsModule, 56 | MatSidenavModule, 57 | MatListModule, 58 | MatTableModule, 59 | MatPaginatorModule, 60 | MatSortModule, 61 | MatGridListModule, 62 | MatMenuModule, 63 | MatFormFieldModule, 64 | MatStepperModule, 65 | ] 66 | }) 67 | export class MaterialModule { } 68 | -------------------------------------------------------------------------------- /src/app/order/components/order/order.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Sus productos 5 |
6 |

no hay productos

7 | 8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 | {{ product.title }} 18 |
19 |
20 |
21 |
22 | {{ product.price }} 23 |
24 |
25 |
26 | 27 |
28 | 29 | Datos personales 30 |

contenifo

31 |
32 | 33 | Pago 34 |

contenifo

35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /src/app/order/components/order/order.component.scss: -------------------------------------------------------------------------------- 1 | .image{ 2 | max-width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/order/components/order/order.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { OrderComponent } from './order.component'; 4 | 5 | describe('OrderComponent', () => { 6 | let component: OrderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ OrderComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(OrderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/order/components/order/order.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Product } from 'src/app/product.model'; 3 | import { CartService } from 'src/app/core/services/cart.service'; 4 | import { Observable } from 'rxjs'; 5 | 6 | @Component({ 7 | selector: 'app-order', 8 | templateUrl: './order.component.html', 9 | styleUrls: ['./order.component.scss'] 10 | }) 11 | export class OrderComponent implements OnInit { 12 | products$: Observable; 13 | constructor( 14 | private cartService: CartService 15 | ) { 16 | this.products$ = this.cartService.cart$; 17 | } 18 | 19 | ngOnInit(): void { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/app/order/order-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { OrderComponent } from './components/order/order.component'; 4 | 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: OrderComponent, 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class OrderRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/order/order.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { OrderRoutingModule } from './order-routing.module'; 5 | import { OrderComponent } from './components/order/order.component'; 6 | import { MaterialModule } from '../material/material.module'; 7 | import { SharedModule } from '../shared/shared.module'; 8 | 9 | 10 | @NgModule({ 11 | declarations: [OrderComponent], 12 | imports: [ 13 | CommonModule, 14 | OrderRoutingModule, 15 | MaterialModule, 16 | SharedModule 17 | ] 18 | }) 19 | export class OrderModule { } 20 | -------------------------------------------------------------------------------- /src/app/product.model.ts: -------------------------------------------------------------------------------- 1 | export interface Product { 2 | id: string; 3 | image: string; 4 | title: string; 5 | price: number; 6 | description: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/products/components/product-detail/product-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
    7 |
  • Nombre: {{product.title}}
  • 8 |
  • Precio: {{product.price | currency:'MXN':true }}
  • 9 |
10 |

{{product.description}}

11 |
12 | 15 | 18 |
19 | -------------------------------------------------------------------------------- /src/app/products/components/product-detail/product-detail.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/products/components/product-detail/product-detail.component.scss -------------------------------------------------------------------------------- /src/app/products/components/product-detail/product-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductDetailComponent } from './product-detail.component'; 4 | 5 | describe('ProductDetailComponent', () => { 6 | let component: ProductDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ProductDetailComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProductDetailComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/products/components/product-detail/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Params } from '@angular/router'; 3 | import { ProductsService } from '../../../core/services/products/products.service'; 4 | import { Product } from 'src/app/product.model'; 5 | 6 | @Component({ 7 | selector: 'app-product-detail', 8 | templateUrl: './product-detail.component.html', 9 | styleUrls: ['./product-detail.component.scss'] 10 | }) 11 | export class ProductDetailComponent implements OnInit { 12 | 13 | constructor( 14 | private route: ActivatedRoute, 15 | private productsService: ProductsService, 16 | ) { } 17 | 18 | product: Product; 19 | id: string; 20 | 21 | ngOnInit(): void { 22 | this.route.params.subscribe((params: Params) => { 23 | const id = params.id; 24 | this.id = id; 25 | this.fetchProduct(id); 26 | console.log(this.product); 27 | }); 28 | } 29 | 30 | fetchProduct(id): void { 31 | this.productsService.getProduct(id) 32 | .subscribe((product) => { 33 | this.product = product; 34 | // console.log(product); 35 | }); 36 | } 37 | 38 | editProduct() { 39 | console.log('edit product'); 40 | const partialProduct: Partial = { 41 | description: 'Cambiando la descripción' 42 | }; 43 | this.productsService.updateProduct(this.id, partialProduct) 44 | .subscribe((product) => { 45 | this.product = product; 46 | console.log(product); 47 | }); 48 | } 49 | 50 | deleteProduct() { 51 | console.log('delete product', this.id); 52 | 53 | this.productsService.deleteProduct(this.id) 54 | .subscribe(rta => { 55 | console.log('deleted product', rta); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/app/products/components/product/product.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ product.title | uppercase }} 5 | 6 | 7 | {{ product.price | currency:'MXN':true }} 8 | 9 | 10 |
11 | 12 |
13 | 14 |

{{ product.description | slice:0:10 }}

15 |

{{ today | date:'longDate'}}

16 | 17 | Ver Detalle 18 | 22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/app/products/components/product/product.component.scss: -------------------------------------------------------------------------------- 1 | .crop-image{ 2 | height: 300px; 3 | width: 100%; 4 | overflow: hidden; 5 | position: relative; 6 | img { 7 | position: absolute; 8 | top: 50%; 9 | left: 50%; 10 | transform: translate(-50%, -50%); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/products/components/product/product.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | Output, 5 | EventEmitter, 6 | SimpleChanges, 7 | OnChanges, 8 | OnInit, 9 | // DoCheck, 10 | OnDestroy 11 | } from '@angular/core'; 12 | import { Product } from '../../../product.model'; 13 | import { CommonModule, registerLocaleData } from '@angular/common'; 14 | import localeEs from '@angular/common/locales/es-MX'; 15 | import { CartService } from '../../../core/services/cart.service'; 16 | 17 | registerLocaleData(localeEs); 18 | @Component({ 19 | selector: 'app-product', 20 | templateUrl: './product.component.html', 21 | styleUrls: ['./product.component.scss'] 22 | }) 23 | 24 | export class ProductComponent implements OnChanges, OnInit, OnDestroy { 25 | 26 | constructor( 27 | private cartService: CartService, 28 | ) { 29 | let i = 1; 30 | console.log('0. Constructor producto'); 31 | } 32 | 33 | ngOnChanges(changes: SimpleChanges): void{ 34 | let i = 1; 35 | console.log('1. ngOnChange producto',this.product.id, ', Llamada#', i++); 36 | console.log('changes', changes) 37 | } 38 | 39 | ngOnInit(){ 40 | let i = 1; 41 | console.log('2. ngOnInit producto',this.product.id, ', Llamada#', i++); 42 | } 43 | 44 | // ngDoCheck(){ 45 | // let i = 1; 46 | // console.log('3. ngDoCheck producto',this.product.id, ', Llamada#', i++); 47 | // } 48 | 49 | ngOnDestroy(){ 50 | let i = 1; 51 | console.log('3. ngOnDestroy producto',this.product.id, ', Llamada#', i++); 52 | } 53 | 54 | today = new Date() 55 | 56 | @Input() product: Product; // Equivalente a prop, dónde le vamos a pasar la data al componente 57 | @Output() clickAddToCart = new EventEmitter(); // (clickAddToCart)= eventHandler($event):function 58 | addToCart(){ 59 | this.clickAddToCart.emit(this.product.id) 60 | this.cartService.addToCart(this.product); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/app/products/components/products/products.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |
7 | 11 |
12 | -------------------------------------------------------------------------------- /src/app/products/components/products/products.component.scss: -------------------------------------------------------------------------------- 1 | .products { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); 4 | grid-gap: 50px; 5 | justify-items: space-around; 6 | & #new-product { 7 | // min-height: 100px; 8 | padding: 10px; 9 | min-height: 200px; 10 | width: 100%; 11 | & button { 12 | height: calc(100% - 20px); 13 | width: calc(100% - 20px); 14 | } 15 | } 16 | } 17 | 18 | .mat-card-image { 19 | max-height: 300px!important; 20 | width: 100%; 21 | } 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/app/products/components/products/products.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductsComponent } from './products.component'; 4 | 5 | describe('ProductsComponent', () => { 6 | let component: ProductsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ProductsComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProductsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/products/components/products/products.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ProductsService } from '../../../core/services/products/products.service'; 3 | import { Product } from '../../../product.model'; 4 | 5 | @Component({ 6 | selector: 'app-products', 7 | templateUrl: './products.component.html', 8 | styleUrls: ['./products.component.scss'] 9 | }) 10 | export class ProductsComponent implements OnInit { 11 | 12 | constructor(private productsService: ProductsService) { } 13 | 14 | products: Product[] = []; 15 | 16 | handleProductAddToCart(id: number) { 17 | console.log('product -> id', id); 18 | } 19 | 20 | ngOnInit(): void { 21 | this.fetchProducts(); 22 | // this.products = this.productsService.getAllProducts(); 23 | } 24 | 25 | fetchProducts(): void { 26 | this.productsService.getAllProducts() 27 | .subscribe((products) => { 28 | this.products = products; 29 | // console.log(products); 30 | }); 31 | } 32 | 33 | createProduct() { 34 | console.log("holi"); 35 | const newProduct: Product = { 36 | id: '223', 37 | title: 'nuevo desde Angular', 38 | image: 'assets/images/banner-1.jpg', 39 | price: 123, 40 | description:'Descripción Angulera' 41 | } 42 | // console.log("creating product") 43 | this.productsService.createProduct(newProduct) 44 | .subscribe((product) => { 45 | this.products.push(newProduct); 46 | console.log(product); 47 | }); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/app/products/product-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; 3 | 4 | import { ProductsComponent } from './components/products/products.component'; 5 | import { ProductDetailComponent } from './components/product-detail/product-detail.component' 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | component: ProductsComponent 11 | }, 12 | { 13 | path: ':id', 14 | component: ProductDetailComponent 15 | } 16 | ]; 17 | 18 | @NgModule({ 19 | imports: [ 20 | RouterModule.forChild(routes), 21 | ], 22 | exports: [ RouterModule ] 23 | }) 24 | export class ProductsRoutingModule {} 25 | -------------------------------------------------------------------------------- /src/app/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { ProductComponent } from './components/product/product.component'; 5 | import { ProductDetailComponent } from './components/product-detail/product-detail.component'; 6 | import { ProductsComponent } from './components/products/products.component'; 7 | 8 | import { SharedModule } from './../shared/shared.module'; 9 | import { LOCALE_ID } from '@angular/core'; 10 | import { registerLocaleData } from '@angular/common'; 11 | import { CoreModule } from '../core/core.module' 12 | import localeEs from '@angular/common/locales/es-MX'; 13 | 14 | import { ProductsRoutingModule } from './product-routing.module'; 15 | 16 | import { AppComponent } from '../app.component'; 17 | import { MaterialModule } from '../material/material.module'; 18 | 19 | registerLocaleData(localeEs); 20 | 21 | 22 | @NgModule({ 23 | declarations: [ 24 | ProductsComponent, 25 | ProductComponent, 26 | ProductDetailComponent, 27 | ], 28 | imports: [ 29 | CommonModule, 30 | CoreModule, 31 | SharedModule, 32 | ProductsRoutingModule, 33 | MaterialModule, 34 | ], 35 | providers: [ { provide: LOCALE_ID, useValue: 'es-mx' } ], 36 | bootstrap: [AppComponent] 37 | }) 38 | export class ProductsModule { } 39 | -------------------------------------------------------------------------------- /src/app/shared/cart/cart.component.html: -------------------------------------------------------------------------------- 1 |

cart works!

2 | -------------------------------------------------------------------------------- /src/app/shared/cart/cart.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/shared/cart/cart.component.scss -------------------------------------------------------------------------------- /src/app/shared/cart/cart.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CartComponent } from './cart.component'; 4 | 5 | describe('CartComponent', () => { 6 | let component: CartComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CartComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CartComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/cart/cart.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-cart', 5 | templateUrl: './cart.component.html', 6 | styleUrls: ['./cart.component.scss'] 7 | }) 8 | export class CartComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/shared/components/footer/footer.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

Productos

7 | 24 |
25 |
26 |
27 |
28 |

Acerca de PlatziStore

29 | 33 |
34 |
35 |
36 |
37 |

Contacto

38 | 39 | Email Address 40 | 41 | Enter a valid email address 42 | {{input.value?.length || 0}}/20 43 | 44 | 45 |
46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/app/shared/components/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | @import '~@angular/material/theming'; 2 | 3 | $primary: mat-palette($mat-indigo); 4 | 5 | footer { 6 | padding: 50px; 7 | margin-top: 20px; 8 | background-color: mat-color($primary); 9 | .box { 10 | h3 { 11 | color: white; 12 | } 13 | input{ 14 | color: white; 15 | } 16 | ul { 17 | list-style: none; 18 | margin: 0px; 19 | padding: 0px; 20 | li { 21 | margin: 10px 0px; 22 | a { 23 | text-decoration: none; 24 | color: white; 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/shared/components/footer/footer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FooterComponent } from './footer.component'; 4 | 5 | describe('FooterComponent', () => { 6 | let component: FooterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ FooterComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FooterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/components/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, Validators, AbstractControl } from '@angular/forms'; 3 | 4 | @Component({ 5 | selector: 'app-footer', 6 | templateUrl: './footer.component.html', 7 | styleUrls: ['./footer.component.scss'] 8 | }) 9 | export class FooterComponent implements OnInit { 10 | 11 | emailField: FormControl; 12 | 13 | constructor() { 14 | // FormControl('Default value', validators[], asyncValidators[]); 15 | // FormControl(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]): FormControl 16 | 17 | this.emailField = new FormControl('', [ 18 | Validators.required, 19 | Validators.maxLength(50), 20 | Validators.minLength(4), 21 | Validators.email, 22 | Validators.pattern(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/) 23 | ]); 24 | this.emailField.valueChanges 25 | .subscribe( value => { 26 | }); 27 | } 28 | 29 | 30 | ngOnInit(): void { 31 | } 32 | 33 | sendEmail(){ 34 | if(this.emailField.valid) { 35 | console.log(this.emailField.value); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/app/shared/components/header/header.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/shared/components/header/header.component.scss: -------------------------------------------------------------------------------- 1 | nav a { 2 | padding:5px 10px; 3 | text-decoration: none; 4 | &.active { 5 | background-color:rgba(255, 255, 255, 0.13); 6 | } 7 | } 8 | .header-row { 9 | width: 100%; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/shared/components/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | 5 | describe('HeaderComponent', () => { 6 | let component: HeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeaderComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { CartService } from '../../../core/services/cart.service' 3 | import { Product } from 'src/app/product.model'; 4 | import { map } from 'rxjs/operators'; 5 | import { Observable } from 'rxjs'; 6 | @Component({ 7 | selector: 'app-header', 8 | templateUrl: './header.component.html', 9 | styleUrls: ['./header.component.scss'] 10 | }) 11 | export class HeaderComponent implements OnInit { 12 | total$: Observable; 13 | constructor( 14 | private cartService: CartService, 15 | ) { 16 | this.total$ = this.cartService.cart$ 17 | .pipe(map(products => products.length)); 18 | } 19 | 20 | ngOnInit(): void { 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/shared/directives/highlight/highlight.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { HighlightDirective } from './highlight.directive'; 2 | 3 | describe('HighlightDirective', () => { 4 | it('should create an instance', () => { 5 | const directive = new HighlightDirective(); 6 | expect(directive).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/app/shared/directives/highlight/highlight.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[appHighlight]' 5 | }) 6 | export class HighlightDirective { 7 | 8 | constructor( 9 | element: ElementRef 10 | ) { 11 | // element.nativeElement.style.backgroundColor = 'blue'; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/app/shared/pipes/exponential/exponential.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { ExponentialPipe } from './exponential.pipe'; 2 | 3 | describe('ExponentialPipe', () => { 4 | it('create an instance', () => { 5 | const pipe = new ExponentialPipe(); 6 | expect(pipe).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/app/shared/pipes/exponential/exponential.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'exponential' 5 | }) 6 | export class ExponentialPipe implements PipeTransform { 7 | 8 | transform(value: number, exp:number=2): number { 9 | return Math.pow(value, exp); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { ExponentialPipe } from './pipes/exponential/exponential.pipe'; 6 | import { HighlightDirective } from './directives/highlight/highlight.directive'; 7 | import { HeaderComponent } from './components/header/header.component'; 8 | import { FooterComponent } from './components/footer/footer.component'; 9 | import { MaterialModule } from '../material/material.module'; 10 | import { CartComponent } from './cart/cart.component'; 11 | import { ReactiveFormsModule } from '@angular/forms'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | ExponentialPipe, 16 | HighlightDirective, 17 | HeaderComponent, 18 | FooterComponent, 19 | CartComponent 20 | ], 21 | exports: [ 22 | ExponentialPipe, 23 | HighlightDirective, 24 | HeaderComponent, 25 | FooterComponent 26 | ], 27 | imports: [ 28 | CommonModule, 29 | RouterModule, 30 | MaterialModule, 31 | ReactiveFormsModule 32 | ] 33 | }) 34 | export class SharedModule { } 35 | -------------------------------------------------------------------------------- /src/app/test/test.component.html: -------------------------------------------------------------------------------- 1 | 2 |

{{title}}

3 | 4 |
6 | Esto se muestra en un ngIf 7 |
8 |
9 |

Este es david

10 |

Este es nicolas

11 |

Este es julian

12 |

No hace match, intenta con 'david'

13 |
14 | 15 | 16 |
    17 |
  • La lista está vacía
  • 18 |
  • 19 | {{item}} ({{i}}) 20 | 21 |
  • 22 |
23 | 24 | 25 | 26 |
27 | 28 | 29 |
30 | 31 |

{{input}}

32 | 33 |

34 | {{ "Hola Mundo " + 1 }} 35 |

36 | 37 | 38 | {{ power | exponential:3 }} 39 | -------------------------------------------------------------------------------- /src/app/test/test.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/app/test/test.component.scss -------------------------------------------------------------------------------- /src/app/test/test.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TestComponent } from './test.component'; 4 | 5 | describe('TestComponent', () => { 6 | let component: TestComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TestComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TestComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/test/test.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-test', 5 | templateUrl: './test.component.html', 6 | styleUrls: ['./test.component.scss'] 7 | }) 8 | export class TestComponent { 9 | title = 'platzi-store'; 10 | input = ''; 11 | array = ['🍎', '🍏', '🍇', '🍌', '🍑']; 12 | power = 10; 13 | addItem() { 14 | this.array.push(this.title) 15 | } 16 | deleteItem(index:number) { 17 | this.array.splice(index, 1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/utils/validators.ts: -------------------------------------------------------------------------------- 1 | import { AbstractControl } from '@angular/forms'; 2 | 3 | export class MyValidators { 4 | static isPriceValid(control: AbstractControl) { 5 | const value = control.value; 6 | if (value > 10000) { 7 | return {price_invalid: true}; 8 | } 9 | return null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/images/banner-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/images/banner-1.jpg -------------------------------------------------------------------------------- /src/assets/images/banner-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/images/banner-2.jpg -------------------------------------------------------------------------------- /src/assets/images/banner-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/images/banner-3.jpg -------------------------------------------------------------------------------- /src/assets/images/camiseta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/images/camiseta.png -------------------------------------------------------------------------------- /src/assets/images/hoodie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/images/hoodie.png -------------------------------------------------------------------------------- /src/assets/images/mug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/images/mug.png -------------------------------------------------------------------------------- /src/assets/images/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/images/pin.png -------------------------------------------------------------------------------- /src/assets/images/stickers1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/images/stickers1.png -------------------------------------------------------------------------------- /src/assets/images/stickers2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/assets/images/stickers2.png -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | url_api: 'http://platzi-store.herokuapp.com', 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.stag.ts: -------------------------------------------------------------------------------- 1 | 2 | export const environment = { 3 | production: false, 4 | url_api: 'http://platzi-store-stag.herokuapp.com', 5 | }; 6 | -------------------------------------------------------------------------------- /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 | url_api: 'http://platzi-store.herokuapp.com', 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/behagoras/angular-store/d42f63e7ad69178b6c24f8c128cd8e3ad45b7730/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlatziStore 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 | /** 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'; 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 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /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 | 6 | .container { 7 | width: 100%; 8 | padding-left: 0; 9 | padding-right: 0; 10 | margin-right: auto; 11 | margin-left: auto; 12 | } 13 | 14 | @media screen and (min-width:576px) { 15 | .container { 16 | max-width: 1110px; 17 | padding-right: 16px; 18 | padding-right: 16px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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/dist/zone-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 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-return-shorthand": true, 12 | "curly": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "directive-class-suffix": true, 19 | "directive-selector": [ 20 | true, 21 | "attribute", 22 | "app", 23 | "camelCase" 24 | ], 25 | "component-selector": [ 26 | true, 27 | "element", 28 | "app", 29 | "kebab-case" 30 | ], 31 | "eofline": true, 32 | "import-blacklist": [ 33 | true, 34 | "rxjs/Rx" 35 | ], 36 | "import-spacing": true, 37 | "indent": { 38 | "options": [ 39 | "spaces" 40 | ] 41 | }, 42 | "max-classes-per-file": false, 43 | "max-line-length": [ 44 | true, 45 | 140 46 | ], 47 | "member-ordering": [ 48 | true, 49 | { 50 | "order": [ 51 | "static-field", 52 | "instance-field", 53 | "static-method", 54 | "instance-method" 55 | ] 56 | } 57 | ], 58 | "no-console": [ 59 | true, 60 | "debug", 61 | "info", 62 | "time", 63 | "timeEnd", 64 | "trace" 65 | ], 66 | "no-empty": false, 67 | "no-inferrable-types": [ 68 | true, 69 | "ignore-params" 70 | ], 71 | "no-non-null-assertion": true, 72 | "no-redundant-jsdoc": true, 73 | "no-switch-case-fall-through": true, 74 | "no-var-requires": false, 75 | "object-literal-key-quotes": [ 76 | true, 77 | "as-needed" 78 | ], 79 | "quotemark": [ 80 | true, 81 | "single" 82 | ], 83 | "semicolon": { 84 | "options": [ 85 | "always" 86 | ] 87 | }, 88 | "space-before-function-paren": { 89 | "options": { 90 | "anonymous": "never", 91 | "asyncArrow": "always", 92 | "constructor": "never", 93 | "method": "never", 94 | "named": "never" 95 | } 96 | }, 97 | "typedef-whitespace": { 98 | "options": [ 99 | { 100 | "call-signature": "nospace", 101 | "index-signature": "nospace", 102 | "parameter": "nospace", 103 | "property-declaration": "nospace", 104 | "variable-declaration": "nospace" 105 | }, 106 | { 107 | "call-signature": "onespace", 108 | "index-signature": "onespace", 109 | "parameter": "onespace", 110 | "property-declaration": "onespace", 111 | "variable-declaration": "onespace" 112 | } 113 | ] 114 | }, 115 | "variable-name": { 116 | "options": [ 117 | "ban-keywords", 118 | "check-format", 119 | "allow-pascal-case" 120 | ] 121 | }, 122 | "whitespace": { 123 | "options": [ 124 | "check-branch", 125 | "check-decl", 126 | "check-operator", 127 | "check-separator", 128 | "check-type", 129 | "check-typecast" 130 | ] 131 | }, 132 | "no-conflicting-lifecycle": true, 133 | "no-host-metadata-property": true, 134 | "no-input-rename": true, 135 | "no-inputs-metadata-property": true, 136 | "no-output-native": true, 137 | "no-output-on-prefix": true, 138 | "no-output-rename": true, 139 | "no-outputs-metadata-property": true, 140 | "template-banana-in-box": true, 141 | "template-no-negated-async": true, 142 | "use-lifecycle-interface": true, 143 | "use-pipe-transform-interface": true 144 | }, 145 | "rulesDirectory": [ 146 | "codelyzer" 147 | ] 148 | } --------------------------------------------------------------------------------