En este tutorial te mostraré paso a paso cómo crear un CRUD sencillo con el stack de JavaScript MEAN (Mongo, Express, Angular, Node), te servirá de base para crear otros proyectos y seguir ampliando tus conocimientos. Consta de 3 partes: configurar el frontend, configurar el backend e integración. Al final del tutorial encontrarás la dirección del repositorio de este proyecto en GitHub. Sin más manos a la obra.
Requisitos previos
- Tener instalado Node de manera global en tu sistema operativo.
- Tener instalado Angular CLI.
- Crear una cuenta en Mongodb.com.
- Contar con Postman instalado en tu sistema para testear los endpoints de la API.
Configurar frontend en Angular
1. Crear nuevo proyecto usando Angular CLI
Crea una carpeta llamada crud-stack-mean desde la terminal de comandos ubicate dentro de ella, para crear tu nuevo proyecto en Angular escribe:
ng new productos
Escribes ‘Yes’ en las siguientes 2 preguntas y escoges ‘CSS’ en la pregunta ‘Which stylesheet format would you like to use‘, ahora espera a que se descarguen todas las dependencias que necesita el proyecto.
Cuando se termine de instalar el proyecto accede desde la terminal de comandos a la carpeta recién creada y enciende el servidor tecleando:
cd productos
ng serve --o
Se abrirá en el navegador la vista principal de tu proyecto:
En tu editor de texto favorito abre la carpeta del proyecto, en mi caso estaré usando VS Code, abre el archivo src/app/app.component.html y borra todo su contenido:
Agrega una etiqueta H1 con cualquier texto, ya que esto es solo una prueba, yo escribiré Hello World!, ahora, si vas al navegador verás reflejado el cambio:
2. Instalar dependencias
Para agregar bootstrap a tu proyecto abre una nueva terminal de comandos en la carpeta de tu proyecto y escribe:
npm install bootstrap
Una vez que termine de instalarse con tu editor de texto abre el archivo angular.json que se encuentra en la raíz de tu proyecto y en styles agrega la siguiente línea de código:
"node_modules/bootstrap/dist/css/bootstrap.min.css"
Para el servidor desde la terminal en donde se encuentra corriendo haciendo la combinación de teclas ‘Ctrl + c’ y vuelve a encender el servidor con el comando:
ng serve --o
En tu navegador se abrirá una nueva pestaña y notarás que han cambiado los estilos en tu proyecto, Bootstrap se ha instalado correctamente.
3. Crear componentes, modelo y servicio
Para este proyecto solo se usarán dos componentes uno para crear y editar y el otro para listar, para ello en la terminal escribe:
ng g c components/create-product
y luego:
ng g c components/list-products
Para crear el servicio, en la terminal escribe:
ng g s services/product
Crea la carpeta src/app/models y dentro de ella el archivo product.ts, en este archivo pondrás las propiedades del modelo y un constructor, para ello agrega el siguiente código:
export class Product{ _id?:number; name: string; category: string; price: number; status: string; constructor(name: string, category: string, price: number, status: string){ this.name = name; this.category = category; this.price = price; this.status = status; } }
4. Crear routing del proyecto
Con tu editor de texto abre el archivo src/app/app-routing.module.ts en este archivo se configuran las rutas del proyecto, vas a agregar las rutas ‘ ‘ que es la raíz, ‘create-product’, ‘edit-product’ y una ruta más que se encargará de redirigir a la raíz cuando el usuario introduzca una ruta que no exite:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CreateProductComponent } from './components/create-product/create-product.component'; import { ListProductsComponent } from './components/list-products/list-products.component'; const routes: Routes = [ { path: '', component: ListProductsComponent }, { path: 'create-product', component: CreateProductComponent }, { path: 'edit-product/:id', component: CreateProductComponent }, { path: '**', redirectTo: '', pathMatch: 'full' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
Abre el archivo src/app/app.component.html y vuelve a borrar su contenido que pusiste de prueba y agrega la siguiente directiva:
<router-outlet></router-outlet>
En el navegador prueba las rutas http://localhost:4200/create-product, http://localhost:4200/edit-product/1 verás que ambas rutas están funcionando correctamente.
5. Configurando las vistas
Para dar estilos generales a las vistas de tu proyecto abre el archivo src/styles.css y agrega el siguiente código o agrega tu código personalizado:
body { background: #373B44; /* fallback for old browsers */ background: -webkit-linear-gradient(to right, #4286f4, #373B44); /* Chrome 10-25, Safari 5.1-6 */ background: linear-gradient(to right, #4286f4, #373B44); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ } .title { font-size: 1.8rem; font-family: 'Open Sans', sans-serif; }
Para este proyecto estaré usando para los íconos la librería Font Awesome y para la fuente usaré Open Sans de Google Fonts, abre con tu editor de textos el archivo src/index.html y entre las etiquetas <head></head> agrega las siguientes líneas de código:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@800&display=swap" rel="stylesheet">
Abre el archivo src/app/components/list-products/list-products-component.html, borra su contenido y agrega el siguiente código:
<div class="container mt-5"> <div class="row"> <div class="col-lg-8 offset-lg-2"> <div class="card"> <div class="card-body text-center"> <span class="title">Product List</span> <button class="btn btn-success float-end" routerLink="/create-product">Create Product</button> <table *ngIf="listProducts.length > 0" class="table table-striped table-responsive mt-5"> <thead> <tr> <th scope="col">Product Name</th> <th scope="col">Category</th> <th scope="col">Price $USD</th> <th scope="col">Status</th> <th scope="col"></th> </tr> </thead> <tbody> <tr *ngFor="let product of listProducts"> <td>{{ product.name }}</td> <td>{{ product.category }}</td> <td>{{ product.price | currency: 'USD'}}</td> <td>{{ product.status }}</td> <td> <i [routerLink]="['/edit-product', product._id]" class="fas fa-edit text-primary"></i> <i (click)="deleteProduct(product._id)" class="fas fa-trash text-danger"></i> </td> </tr> </tbody> </table> <h5 class="no-available" *ngIf="listProducts.length == 0">There are no products available</h5> </div> </div> </div> </div> </div>
Recuerda que sólo estás configurando la estructura de los archivos, más adelante agregarás el código necesario para que funcione correctamente tu CRUD con el stack MEAN. Abre el archivo src/app/components/list-products/list-components.css y agrega el siguiente estilo para los íconos:
.fas{ cursor: pointer; margin-left: 7px; }
Abre el archivo src/app/components/create-product/create-product.component.html para agregar la estructura del archivo borra el contenido y pega el siguiente código:
<div class="container mt-5"> <div class="col-lg-6 offset-lg-3"> <div class="card"> <div class="card-body text-center"> <span class="title">{{ title | uppercase }}</span> <form class="mt-3" [formGroup]="productForm" (ngSubmit)="addProduct()"> <div class="mb-3"> <input type="text" formControlName="name" class="form-control form-control-lg" placeholder="Product name"> <div class="text-danger" *ngIf="productForm.get('name')?.hasError('required') && productForm.get('name')?.touched"> <span>The name is required</span> </div> </div> <div class="mb-3"> <input type="text" formControlName="category" class="form-control form-control-lg" placeholder="Category"> <div class="text-danger" *ngIf="productForm.get('category')?.hasError('required') && productForm.get('category')?.touched"> <span>The category is required</span> </div> </div> <div class="mb-3"> <input type="number" formControlName="price" class="form-control form-control-lg" placeholder="Price"> <div class="text-danger" *ngIf="productForm.get('price')?.hasError('required') && productForm.get('price')?.touched"> <span>The price is required</span> </div> </div> <div class="mb-3"> <input type="text" formControlName="status" class="form-control form-control-lg" placeholder="Status"> <div class="text-danger" *ngIf="productForm.get('status')?.hasError('required') && productForm.get('status')?.touched"> <span>The status is required</span> </div> </div> <div class="mb-3"> <button routerLink="/" class="btn btn-secondary btn-lg float-start">Back</button> <button type="submit" [disabled]="productForm.invalid" class="btn btn-success btn-lg float-end">Save</button> </div> </form> </div> </div> </div> </div>
6. Configurar formulario reactivo
Con tu editor de textos abre el archivo src/app/app.module.ts e importa el modulo ReactiveFormsModule:
Abre el archivo src/app/components/create-product/create-product.components.ts, vas a configurar las validaciones de tu formulario, para ello agrega las siguientes líneas de código:
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { Product } from 'src/app/models/product'; import { ProductService } from 'src/app/services/product.service'; @Component({ selector: 'app-create-product', templateUrl: './create-product.component.html', styleUrls: ['./create-product.component.css'] }) export class CreateProductComponent implements OnInit { productForm: FormGroup; title = 'Creat Product'; id: string | null; constructor(private fb: FormBuilder, private router: Router, private _productService: ProductService, private aRouter: ActivatedRoute) { this.productForm = this.fb.group({ name: ['', Validators.required], category: ['', Validators.required], price: ['', Validators.required], status: ['', Validators.required], }) this.id = this.aRouter.snapshot.paramMap.get('id'); } ngOnInit(): void { this.Edit(); } addProduct() { const PRODUCT: Product = { name: this.productForm.get('name')?.value, category: this.productForm.get('category')?.value, price: this.productForm.get('price')?.value, status: this.productForm.get('status')?.value, } //Verificar existe el producto if (this.id !== null) { //existe el producto se edita this._productService.editProduct(this.id, PRODUCT).subscribe(data => { this.router.navigate(['/']); }, error => { console.log(error); this.productForm.reset(); }) } else { //no existe el producto se crea console.log(PRODUCT); this._productService.saveProduct(PRODUCT).subscribe(data => { this.router.navigate(['/']) }, error => { console.log(error); this.productForm.reset(); }) } } Edit() { if (this.id !== null) { this.title = 'Edit Product'; this._productService.getAProduct(this.id).subscribe(data => { this.productForm.setValue({ name: data.name, category: data.category, price: data.price, status: data.status }) }) } } }
Después de terminar las validaciones toca enlazarlas con tu formulario para ello abre el archivo src/app/components/create-product/create-product.component.html y agrega el siguiente código:
<div class="container mt-5"> <div class="col-lg-6 offset-lg-3"> <div class="card"> <div class="card-body text-center"> <span class="title">{{ title | uppercase }}</span> <form class="mt-3" [formGroup]="productForm" (ngSubmit)="addProduct()"> <div class="mb-3"> <input type="text" formControlName="name" class="form-control form-control-lg" placeholder="Product name"> <div class="text-danger" *ngIf="productForm.get('name')?.hasError('required') && productForm.get('name')?.touched"> <span>The name is required</span> </div> </div> <div class="mb-3"> <input type="text" formControlName="category" class="form-control form-control-lg" placeholder="Category"> <div class="text-danger" *ngIf="productForm.get('category')?.hasError('required') && productForm.get('category')?.touched"> <span>The category is required</span> </div> </div> <div class="mb-3"> <input type="number" formControlName="price" class="form-control form-control-lg" placeholder="Price"> <div class="text-danger" *ngIf="productForm.get('price')?.hasError('required') && productForm.get('price')?.touched"> <span>The price is required</span> </div> </div> <div class="mb-3"> <input type="text" formControlName="status" class="form-control form-control-lg" placeholder="Status"> <div class="text-danger" *ngIf="productForm.get('status')?.hasError('required') && productForm.get('status')?.touched"> <span>The status is required</span> </div> </div> <div class="mb-3"> <button routerLink="/" class="btn btn-secondary btn-lg float-start">Back</button> <button type="submit" [disabled]="productForm.invalid" class="btn btn-success btn-lg float-end">Save</button> </div> </form> </div> </div> </div> </div>
Para dar algunos estilos al div donde se muestra el mensaje de error abre el archivo src/app/components/create-product/create-product.components.css y agrega las siguientes líneas de código:
.text-danger{ text-align: start; margin-left: 7px }
Para ver si hasta aquí esta funcionando correctamente tu CRUD con el stack MEAN ve al navegador y prueba tu app:
Las validaciones están funcionando:
Y si llenas el formulario y das clic en el botón Save te redirige a la página principal y si das clic derecho en el navegador e inspeccionas desde la consola el objeto product trae la información correctamente:
El frontend está listo ahora toca construir el backend.
Configurar backend
1. Instalar paquetes
Desde la terminal de comandos entra a la carpeta de tu proyecto crud-stack-mean y crea una carpeta nueva llamada servidor y accede a ella:
mkdir servidor
cd servidor
Para inicializar el proyecto escribe en la terminal:
npm init
Inmediatamente van a aparecer una serie de preguntas a las cuales debes de responder:
Ahora toca instalar algunas dependencias que necesitas para el desarrollo del backend, en la terminal escribe una a una las siguientes instrucciones:
npm install -D nodemon
npm install express mongoose dotenv
npm install cors
2. Configurar servidor
Crea el archivo servidor/index.js por el momento pondrás un código de prueba y más adelante lo modificarás, dentro de este nuevo archivo escribe:
console.log('Online!')
Abre el archivo servidor/package.json, en scripts borra la línea:
"test": "echo \"Error: no test specified\" && exit 1"
y agrega:
"dev": "nodemon ."
Debe de verse algo así:
Con este paquete cuando realizas un cambio en el backend automáticamente vuelve a compilar y se ve reflejado el cambio. Para que corra y se quede a la escucha en la terminal escribe:
npm run dev
Verás que muestra el mensaje que configuraste en el archivo index.js lo que significa que está corriendo sin problemas:
Para levantar el servidor abre el archivo servidor/index.js y agrega las siguientes instrucciones:
const express = require('express'); //Se crea el servidor const app = express(); //Ruta de prueba app.get('/', (req, res) => { res.send('Hello World'); }) app.listen(5001, () => { console.log('The server is runnig') })
En la terminal donde está corriendo nodemon verás el mensaje que pusiste en el consol.log:
Ahora abre el navegador en http://localhost:5001/ verás que el servidor esta corriendo y responde correctamente el mensaje de la ruta de prueba:
3. Configurar base de datos
Ingresa a tu cuenta en mongodb.com, si no tienes cuenta, crea una. Al ingresar te mostrará el dashboard, crea un proyecto nuevo:
Establece un nombre para tu proyecto:
En la siguiente ventana te preguntan si quieres agregar miembros al proyecto, en mi caso no lo haré:
Da clic en el botón ‘build a database’:
Escoge el cloud gratuito y da clic en el botón create:
En la siguiente ventana da clic al botón de la parte inferior ‘create cluster’:
Crea un nombre de usuario y password, al terminar da clic en el botón ‘create user’:
Ahora en el menú de la izquierda ve a ‘Database’:
Da clic en el botón ‘Connect’:
Escoge si quieres que solo se conecte desde una dirección IP en específico, desde diferentes direcciones IP identificadas o que cualquier dirección IP tenga acceso:
En mi caso escogí ‘Allow Access from Anywhere’:
Escoge como método de conexión ‘Connect using MongoDB Compass’:
En la siguiente ventana que se abre, escoge si ya tienes instalado o no MongoDB Compass, sino lo tienes escoge el sistema operativo de tu equipo y da clic en el botón Download Compass para que se descargue el instalador y en el punto 2 copia el string de conexión ya que más adelante lo usarás:
3.1 Instala MongoDB Compass
Abre el instalador que descargaste y sigue los pasos de instalación:
Una vez que termine de instalarse MongoDB Compass se abrirá una ventana en donde tienes que pegar la cadena de conexión que copiaste anteriormente agregando el password que configuraste:
Si todo sale bien en la nueva ventana que se abre ve a la pestaña Databases y da clic en el botón ‘Create database’:
Asigna un nombre para la base de datos y para la colección:
Se ha creado la nueva base de datos con la que trabajarás con tu CRUD con el stack MEAN.
4. Conectar la base de datos
Crea el archivo servidor/variables.env y en variable DB_MONGO pega la cadena de conexión:
DB_MONGO=mongodb+srv://diario_programador:[email protected]/crudproducts
Recuerda cambiar ‘tupassword’ por tu contraseña y que después del slash va el nombre de tu base de datos. Ahora crea la carpeta servidor/config y dentro crea el archivo db.js al cual agrega el siguiente código:
const mongoose = require('mongoose'); require('dotenv').config({ path: 'variables.env' }); const DBconnection = async () => { try { await mongoose.connect(process.env.DB_MONGO, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false }) console.log('BD Conectada'); } catch (error) { console.log(error); process.exit(1); // Detenemos la app } } module.exports = DBconnection
En tu archivo servidor/index.js es necesario agregar más instrucciones:
const express = require('express'); const DBconnection =require('./config/db'); const cors = require('cors'); //Se crea el servidor const app = express(); //Conectar a DB DBconnection(); app.use(cors()) //habilitar datos JSON app.use(express.json()); //Ruta app.use('/api/products', require('./routes/product')); app.listen(5001, () => { console.log('The server is runnig') })
5. Configurar routes
Crea la carpeta servidor/routes y dentro de ella el archivo product.js y agrega el siguiente código:
//Rutas const express = require('express'); const router = express.Router(); //api/products router.post('/', () => { console.log('creating a product...') }) module.exports = router;
Abre el programa Postman para hacer una prueba y usando el verbo POST prueba la ruta http://localhost:5001/api/products una vez que mandes la petición verás en la terminal donde esta corriendo nodemon que hay una respuesta:
6. Crear y configurar controlador
Crea la carpeta servidor/controllers y dentro de ella el archivo productControllers.js agrega el siguiente código de prueba:
exports.createProduct = (req, res) => { console.log(req, body); }
Ve al archivo servidor/routes/product.js agrega el siguiente código:
//Rutas const express = require('express'); const router = express.Router(); const productController = require('../controllers/productController'); //api/products router.post('/', productController.createProduct); module.exports = router;
7. Modelar la base de datos
Crea la carpeta servidor/models y dentro de ella el archivo Product.js, en este archivo se modelará la base de datos, para ello agrega el siguiente código:
const mongoose = require('mongoose'); const ProductSchema = mongoose.Schema({ name: { type: String, required: true }, category: { type: String, required: true }, price: { type: Number, required: true }, status: { type: String, required: true }, createAt: { type: Date, default: Date.now() } }); module.exports = mongoose.model('Product', ProductSchema);
8. Agregar productos a la base de datos
Abre el archivo del controlador que se encuentra en servidor/controllers/productController.js, en el quita el código de prueba y reemplazalo por el siguiente:
const Product = require("../models/Product") exports.createProduct = async (req, res) => { try { let product; //Create product product = new Product(req.body); await product.save(); res.send(product); } catch (error) { console.log(error); res.status(500).send('There was an error on the server'); } }
Para probar abre Postman y agrega un producto mandando la petición con los datos como se muestra en la siguiente imagen:
Como respuesta verás el objeto con sus propiedades:
Y si vas a MongoDB Compass verás que se ha guardado la información correctamente:
9. Obtener todos los productos
En el archivo servidor/controllers/productController.js al final agrega el método getProducts el cual traerá todos los productos de la base de datos:
exports.getProducts = async (req, res) => { try { const product = await Product.find(); res.json(product); } catch (error) { console.log(error); res.status(500).send('There was an error on the server'); } }
Abre el archivo servidor/routes/product.js y agrega la ruta:
router.get('/', productController.getProducts);
10. Actualizar producto
En el archivo servidor/routes/product.js agrega la ruta para actualizar el producto en la base de datos:
router.put('/:id', productController.updateProduct);
Para que tu CRUD con el stack MEAN funcione correctamente en el archivo servidor/controllers/productController.js agrega el método updateProduct al final del mismo:
exports.updateProduct = async (req, res) => { try { const { name, category, price, status } = req.body; let product = await Product.findById(req.params.id); if(!product){ res.status(404).json({msg:'Product not found, try with other product'}) } product.name = name; product.category = category; product.price = price; product.status = status; product = await Product.findOneAndUpdate({_id : req.params.id}, product, {new:true}) res.json(product); } catch (error) { console.log(error); res.status(500).send('There was an error on the server'); } }
11. Mostrar un producto
Para mostrar un solo producto por su id, abre el archivo servidor/routes/product.js y agrega la siguiente ruta:
router.get('/:id', productController.getProduct);
Y agrega el método getProduct al archivo servidor/controllers/productController.js:
exports.getProduct = async (req, res) => { try { let product = await Product.findById(req.params.id); if(!product){ res.status(404).json({msg:'Product not found, try with other product'}) } res.json(product); } catch (error) { console.log(error); res.status(500).send('There was an error on the server'); } }
12. Eliminar un producto
Para borrar un producto de la base de datos agrega la siguiente ruta al archivo servidor/routes/product.js:
router.delete('/:id', productController.deleteProduct);
Y agrega el método deleteProduct al controlador en servidor/controllers/productController.js:
exports.deleteProduct = async (req, res) => { try { let product = await Product.findById(req.params.id); if(!product){ res.status(404).json({msg:'Product not found, try with other product'}) } await Product.findOneAndRemove({_id: req.params.id}) res.json({msg:"Product deleted successfully"}) } catch (error) { console.log(error); res.status(500).send('There was an error on the server'); } }
Integrar el backend y frontend
1. Mostrar listado de productos
Ve a la carpeta productos en donde se muestra el frontend y abre el archivo src/app/app.module.ts, importa el módulo HttpClientModule:
import { HttpClientModule } from '@angular/common/http';
Recuerda agregarlo en imports:
En el archivo src/app/services/product.services.ts agrega el siguiente código:
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { Product } from '../models/product'; @Injectable({ providedIn: 'root' }) export class ProductService { url = 'http://localhost:5001/api/products/'; constructor(private http:HttpClient) { } getProducts(): Observable<any> { return this.http.get(this.url); } }
Y en el archivo src/app/components/list-prodcts/list-products.component.ts agrega el siguiente código:
import { Component, OnInit } from '@angular/core'; import { Product } from 'src/app/models/product'; import { ProductService } from 'src/app/services/product.service'; @Component({ selector: 'app-list-products', templateUrl: './list-products.component.html', styleUrls: ['./list-products.component.css'] }) export class ListProductsComponent implements OnInit { listProducts: Product[] = []; constructor(private _productService: ProductService) { } ngOnInit(): void { this.getProducts(); } getProducts() { this._productService.getProducts().subscribe(data => { console.log(data); this.listProducts = data; }, error => { console.log(error); }) } deleteProduct(id: any){ this._productService.deleteProduct(id).subscribe(data => { this.getProducts(); }, error => { console.log(error) }) } }
Para que se muestre el listado de productos es necesario iterarlos en el archivo src/app/components/list-products/list-products.component.html para ello agrega el siguiente código:
<div class="container mt-5"> <div class="row"> <div class="col-lg-8 offset-lg-2"> <div class="card"> <div class="card-body text-center"> <span class="title">Product List</span> <button class="btn btn-success float-end" routerLink="/create-product">Create Product</button> <table *ngIf="listProducts.length > 0" class="table table-striped table-responsive mt-5"> <thead> <tr> <th scope="col">Product Name</th> <th scope="col">Category</th> <th scope="col">Price $USD</th> <th scope="col">Status</th> <th scope="col"></th> </tr> </thead> <tbody> <tr *ngFor="let product of listProducts"> <td>{{ product.name }}</td> <td>{{ product.category }}</td> <td>{{ product.price | currency: 'USD'}}</td> <td>{{ product.status }}</td> <td> <i [routerLink]="['/edit-product', product._id]" class="fas fa-edit text-primary"></i> <i (click)="deleteProduct(product._id)" class="fas fa-trash text-danger"></i> </td> </tr> </tbody> </table> <h5 class="no-available" *ngIf="listProducts.length == 0">There are no products available</h5> </div> </div> </div> </div> </div>
Recuerda
para que se muestre correctamente la lista de productos tienes que tener dos terminales abiertas en cada carpeta del proyecto, en la carpeta servidor debe de estar corriendo:
npm run dev
y en la carpeta productos:
ng serve --o
En el navegador ve a la dirección http://localhost:4200/ si no hubo ningún error verás el listado de productos traídos desde la base de datos:
2. Eliminar un producto
Para eliminar un producto en el archivo src/app/services/product.service.ts agrega el método deleteProduct:
deleteProduct(id:string): Observable<any> { return this.http.delete(this.url + id); }
También hay que agregar el método deleteProduct en el archivo src/app/components/list-products/list-products.components.ts:
deleteProduct(id: any){ this._productService.deleteProduct(id).subscribe(data => { this.getProducts(); }, error => { console.log(error) }) }
Y agregar un evento click en el archivo src/app/components/list-products.component.html dentro de la etiqueta del icono de basura, quedaría así:
<i (click)="deleteProduct(product._id)" class="fas fa-trash text-danger"></i>
3. Crear producto
Abre el archivo src/app/services/product.service.ts y agrega el método saveProduct que será el encargado de conectarse al backend y se guarde el producto en la base de datos:
saveProduct(product:Product): Observable<any> { return this.http.post(this.url, product); }
Ve a src/app/components/create-product.components.ts importa el ProductService:
import { ProductService } from 'src/app/services/product.service';
En el constructor agrega:
private _productService: ProductService
En el método addProduct() agrega el siguiente código después del console.log(PRODUCT):
this._productService.saveProduct(PRODUCT).subscribe(data => { this.router.navigate(['/']) }, error => { console.log(error); this.productForm.reset(); })
Al final el código debe de quedar así:
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { Product } from 'src/app/models/product'; import { ProductService } from 'src/app/services/product.service'; @Component({ selector: 'app-create-product', templateUrl: './create-product.component.html', styleUrls: ['./create-product.component.css'] }) export class CreateProductComponent implements OnInit { productForm: FormGroup; title = 'Creat Product'; id: string | null; constructor(private fb: FormBuilder, private router: Router, private _productService: ProductService, private aRouter: ActivatedRoute) { this.productForm = this.fb.group({ name: ['', Validators.required], category: ['', Validators.required], price: ['', Validators.required], status: ['', Validators.required], }) this.id = this.aRouter.snapshot.paramMap.get('id'); } ngOnInit(): void { this.Edit(); } addProduct() { const PRODUCT: Product = { name: this.productForm.get('name')?.value, category: this.productForm.get('category')?.value, price: this.productForm.get('price')?.value, status: this.productForm.get('status')?.value, } //Verificar existe el producto if (this.id !== null) { //existe el producto se edita this._productService.editProduct(this.id, PRODUCT).subscribe(data => { this.router.navigate(['/']); }, error => { console.log(error); this.productForm.reset(); }) } else { //no existe el producto se crea console.log(PRODUCT); this._productService.saveProduct(PRODUCT).subscribe(data => { this.router.navigate(['/']) }, error => { console.log(error); this.productForm.reset(); }) } } Edit() { if (this.id !== null) { this.title = 'Edit Product'; this._productService.getAProduct(this.id).subscribe(data => { this.productForm.setValue({ name: data.name, category: data.category, price: data.price, status: data.status }) }) } } }
Desde el navegador prueba creando un producto, verás que está funcionando sin problema.
4. Editar producto
Para que tu CRUD con el stack MEAN logre editar un producto en el archivo src/app/services/product.service.ts agrega los métodos getAProduct y editProduct:
getAProduct(id:string): Observable<any> { return this.http.get(this.url + id); } editProduct(id:string, product:Product): Observable<any> { return this.http.put(this.url + id, product); }
Agrega la directiva RouterLink en el archivo src/app/components/create-product/create-product.component.html en la etiqueta del icono de editar la cual redirigirá a la ruta edit-product:
<i [routerLink]="['/edit-product', product._id]" class="fas fa-edit text-primary"></i>
Localiza la etiqueta span con la clase ‘title’ en donde se muestra el título:
<span class="title">Product List</span>
Para que se muestre el título dinámico dependiendo si se está creando un producto o editando cambia la etiqueta de arriba por la siguiente:
<span class="title">{{ title | uppercase }}</span>
Abre el archivo src/app/components/create-product/create-product.component.ts, aquí te muestro el código final de este archivo:
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { Product } from 'src/app/models/product'; import { ProductService } from 'src/app/services/product.service'; @Component({ selector: 'app-create-product', templateUrl: './create-product.component.html', styleUrls: ['./create-product.component.css'] }) export class CreateProductComponent implements OnInit { productForm: FormGroup; title = 'Creat Product'; id: string | null; constructor(private fb: FormBuilder, private router: Router, private _productService: ProductService, private aRouter: ActivatedRoute) { this.productForm = this.fb.group({ name: ['', Validators.required], category: ['', Validators.required], price: ['', Validators.required], status: ['', Validators.required], }) this.id = this.aRouter.snapshot.paramMap.get('id'); } ngOnInit(): void { this.Edit(); } addProduct() { const PRODUCT: Product = { name: this.productForm.get('name')?.value, category: this.productForm.get('category')?.value, price: this.productForm.get('price')?.value, status: this.productForm.get('status')?.value, } //Verificar existe el producto if (this.id !== null) { //existe el producto se edita this._productService.editProduct(this.id, PRODUCT).subscribe(data => { this.router.navigate(['/']); }, error => { console.log(error); this.productForm.reset(); }) } else { //no existe el producto se crea console.log(PRODUCT); this._productService.saveProduct(PRODUCT).subscribe(data => { this.router.navigate(['/']) }, error => { console.log(error); this.productForm.reset(); }) } } Edit() { if (this.id !== null) { this.title = 'Edit Product'; this._productService.getAProduct(this.id).subscribe(data => { this.productForm.setValue({ name: data.name, category: data.category, price: data.price, status: data.status }) }) } } }
Si vas al navegador y pruebas tu aplicación verás que edita correctamente los productos. El CRUD está funcionando correctamente.
Conclusión
En este tutorial creaste un CRUD básico con el stack MEAN desde cero y paso paso. Si te ha servido este tutorial te invito a compartirlo en tus redes sociales para alcanzar a más personas y si tienes dudas o comentarios déjalos en la caja de comentarios, estaré al pendiente de ellos. Te mando un saludo.
Te puede interesar: CRUD con Laravel 9 y React
Muy buena guía, me ha servido cantidad, gracias!
Muchas gracias por visitar y comentar Erick, me alegra que te sirvió este tutorial! Saludos!
Error cuando hago un post con postman le meto todos los datos name, category, price, etc en body y raw al pasar 10 segundo da el error ‘Operation `products.insertOne()` buffering timed out after 10000ms’