Une implémentation simple du commerce électronique avec Spring

Une mise en œuvre simple du commerce électronique avec Spring

1. Vue d'ensemble de notre application de commerce électronique

Dans ce didacticiel, nous allons mettre en œuvre une application de commerce électronique simple. Nous allons développer une API en utilisantSpring Boot et une application cliente qui consommera l'API en utilisantAngular.

Fondamentalement, l'utilisateur pourra ajouter / supprimer des produits d'une liste de produits à / d'un panier d'achat et passer une commande.

2. Partie Backend

Pour développer l'API, nous utiliserons la dernière version de Spring Boot. Nous utilisons également les bases de données JPA et H2 pour la persistance.

To learn more about Spring Boot,you could check out our Spring Boot series of articles et si vous souhaitezto get familiar with building a REST API, please check out another series.

2.1. Dépendances Maven

Préparons notre projet et importons les dépendances requises dans nospom.xml.

Nous aurons besoin de quelquesSpring Boot dependencies principaux:


    org.springframework.boot
    spring-boot-starter-data-jpa
    2.0.4.RELEASE


    org.springframework.boot
    spring-boot-starter-web
    2.0.4.RELEASE

Ensuite, lesH2 database:


    com.h2database
    h2
    1.4.197
    runtime

Et enfin - lesJackson library:


    com.fasterxml.jackson.datatype
    jackson-datatype-jsr310
    2.9.6

Nous avons utiliséSpring Initializr pour configurer rapidement le projet avec les dépendances nécessaires.

2.2. Configuration de la base de données

Bien que nous puissions utiliser la base de données H2 en mémoire avec Spring Boot, nous allons encore faire quelques ajustements avant de commencer à développer notre API.

Nous allonsenable H2 console dans notre fichierapplication.propertiesso we can actually check the state of our database and see if everything is going as we’d expect.

En outre, il pourrait être utile de consigner les requêtes SQL sur la console lors du développement:

spring.datasource.name=ecommercedb
spring.jpa.show-sql=true

#H2 settings
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

Après avoir ajouté ces paramètres, nous pourrons accéder à la base de données àhttp://localhost:8080/h2-console en utilisant l'URL JDBCjdbc:h2:mem:ecommercedb as et l'utilisateursa ans mot de passe.

2.3. La structure du projet

Le projet sera organisé en plusieurs packages standard, avec l'application Angular placée dans le dossier frontal:

├───pom.xml
├───src
    ├───main
    │   ├───frontend
    │   ├───java
    │   │   └───com
    │   │       └───example
    │   │           └───ecommerce
    │   │               │   EcommerceApplication.java
    │   │               ├───controller
    │   │               ├───dto
    │   │               ├───exception
    │   │               ├───model
    │   │               ├───repository
    │   │               └───service
    │   │
    │   └───resources
    │       │   application.properties
    │       ├───static
    │       └───templates
    └───test
        └───java
            └───com
                └───example
                    └───ecommerce
                            EcommerceApplicationIntegrationTest.java

Nous devons noter que toutes les interfaces du package de référentiel sont simples et étendent lesCrudRepository de Spring Data, nous allons donc omettre de les afficher ici.

2.4. Gestion des exceptions

Nous aurons besoin d'un gestionnaire d'exceptions pour notre API afin de traiter correctement les éventuelles exceptions.

You can find more details about the topic in our Error Handling for REST with Spring and Custom Error Message Handling for REST API articles.

Ici, nous nous concentrons surConstraintViolationException et nosResourceNotFoundException personnalisés:

@RestControllerAdvice
public class ApiExceptionHandler {

    @SuppressWarnings("rawtypes")
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity handle(ConstraintViolationException e) {
        ErrorResponse errors = new ErrorResponse();
        for (ConstraintViolation violation : e.getConstraintViolations()) {
            ErrorItem error = new ErrorItem();
            error.setCode(violation.getMessageTemplate());
            error.setMessage(violation.getMessage());
            errors.addError(error);
        }
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }

    @SuppressWarnings("rawtypes")
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity handle(ResourceNotFoundException e) {
        ErrorItem error = new ErrorItem();
        error.setMessage(e.getMessage());

        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }
}

2.5. Des produits

If you need more knowledge about persistence in Spring, there is a lot of useful articles in Spring Persistence series.

Notre application supporteraonly reading products from the database, nous devons donc en ajouter d'abord.

Créons une simple classeProduct:

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull(message = "Product name is required.")
    @Basic(optional = false)
    private String name;

    private Double price;

    private String pictureUrl;

    // all arguments contructor
    // standard getters and setters
}

Bien que l'utilisateur n'ait pas la possibilité d'ajouter des produits via l'application, nous prendrons en charge l'enregistrement d'un produit dans la base de données afin de préremplir la liste de produits.

Un simple service suffira à nos besoins:

@Service
@Transactional
public class ProductServiceImpl implements ProductService {

    // productRepository constructor injection

    @Override
    public Iterable getAllProducts() {
        return productRepository.findAll();
    }

    @Override
    public Product getProduct(long id) {
        return productRepository
          .findById(id)
          .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }

    @Override
    public Product save(Product product) {
        return productRepository.save(product);
    }
}

Un simple contrôleur traitera les demandes d'extraction de la liste de produits:

@RestController
@RequestMapping("/api/products")
public class ProductController {

    // productService constructor injection

    @GetMapping(value = { "", "/" })
    public @NotNull Iterable getProducts() {
        return productService.getAllProducts();
    }
}

Tout ce dont nous avons besoin maintenant pour exposer la liste de produits à l’utilisateur, c’est de mettre effectivement certains produits dans la base de données. Par conséquent, nous allons utiliser la classeCommandLineRunner pour créer unBean dans notre classe d’application principale.

De cette façon, nous insérerons des produits dans la base de données lors du démarrage de l'application:

@Bean
CommandLineRunner runner(ProductService productService) {
    return args -> {
        productService.save(...);
        // more products
}

Si nous démarrons maintenant notre application, nous pourrions récupérer la liste de produits viahttp://localhost:8080/api/products. De plus, si nous allons àhttp://localhost:8080/h2-console et nous nous connectons, nous verrons qu'il y a une table nomméePRODUCT avec le produits que nous venons d'ajouter.

2.6. Ordres

Du côté des API, nous devons activer les requêtes POST pour enregistrer les commandes que l’utilisateur final passera.

Commençons par créer le modèle:

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JsonFormat(pattern = "dd/MM/yyyy")
    private LocalDate dateCreated;

    private String status;

    @JsonManagedReference
    @OneToMany(mappedBy = "pk.order")
    @Valid
    private List orderProducts = new ArrayList<>();

    @Transient
    public Double getTotalOrderPrice() {
        double sum = 0D;
        List orderProducts = getOrderProducts();
        for (OrderProduct op : orderProducts) {
            sum += op.getTotalPrice();
        }
        return sum;
    }

    @Transient
    public int getNumberOfProducts() {
        return this.orderProducts.size();
    }

    // standard getters and setters
}

Nous devrions noter quelques choses ici. L'une des choses les plus remarquables est certainement deremember to change the default name of our table. Puisque nous avons nommé la classeOrder, par défaut la table nomméeORDER doit être créée. Mais comme il s'agit d'un mot SQL réservé, nous avons ajouté@Table(name = “orders”) pour éviter les conflits.

De plus, nous avons deux@Transient methods that will return a total amount for that order and the number of products in it. Les deux représentent des données calculées, il n'est donc pas nécessaire de les stocker dans la base de données.

Enfin, nous avons un@OneToMany relation representing the order’s details. Pour cela, nous avons besoin d'une autre classe d'entités:

@Entity
public class OrderProduct {

    @EmbeddedId
    @JsonIgnore
    private OrderProductPK pk;

    @Column(nullable = false)
    private Integer quantity;

    // default constructor

    public OrderProduct(Order order, Product product, Integer quantity) {
        pk = new OrderProductPK();
        pk.setOrder(order);
        pk.setProduct(product);
        this.quantity = quantity;
    }

    @Transient
    public Product getProduct() {
        return this.pk.getProduct();
    }

    @Transient
    public Double getTotalPrice() {
        return getProduct().getPrice() * getQuantity();
    }

    // standard getters and setters

    // hashcode() and equals() methods
}

We have a composite primary keyhere:

@Embeddable
public class OrderProductPK implements Serializable {

    @JsonBackReference
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    private Product product;

    // standard getters and setters

    // hashcode() and equals() methods
}

Ces classes ne sont pas trop compliquées, mais notons que dans la classeOrderProduct, nous mettons@JsonIgnore sur la clé primaire. C'est parce que nous ne voulons pas sérialiser la partieOrder de la clé primaire car elle serait redondante.

Nous n'avons besoin que desProduct pour être affichés à l'utilisateur, c'est pourquoi nous avons la méthode transitoiregetProduct().

Ensuite, nous avons besoin d’une implémentation de service simple:

@Service
@Transactional
public class OrderServiceImpl implements OrderService {

    // orderRepository constructor injection

    @Override
    public Iterable getAllOrders() {
        return this.orderRepository.findAll();
    }

    @Override
    public Order create(Order order) {
        order.setDateCreated(LocalDate.now());
        return this.orderRepository.save(order);
    }

    @Override
    public void update(Order order) {
        this.orderRepository.save(order);
    }
}

Et un contrôleur mappé sur/api/orders pour gérer les requêtesOrder.

Le plus important est la méthodecreate ():

@PostMapping
public ResponseEntity create(@RequestBody OrderForm form) {
    List formDtos = form.getProductOrders();
    validateProductsExistence(formDtos);
    // create order logic
    // populate order with products

    order.setOrderProducts(orderProducts);
    this.orderService.update(order);

    String uri = ServletUriComponentsBuilder
      .fromCurrentServletMapping()
      .path("/orders/{id}")
      .buildAndExpand(order.getId())
      .toString();
    HttpHeaders headers = new HttpHeaders();
    headers.add("Location", uri);

    return new ResponseEntity<>(order, headers, HttpStatus.CREATED);
}

Tout d'abord,we accept a list of products with their corresponding quantities. Après cela,we check if all products exist dans la base de données etthen create and save a new order. Nous conservons une référence à l'objet nouvellement créé afin de pouvoir y ajouter des détails de commande.

Enfin,we create a “Location” header.

L'implémentation détaillée est dans le référentiel - le lien vers celle-ci est mentionné à la fin de cet article.

3. L'extrémité avant

Maintenant que notre application Spring Boot est créée, il est temps de déplacerthe Angular part of the project. Pour ce faire, nous devons d'abord installerNode.js avec NPM et, ensuite, unAngular CLI, une interface de ligne de commande pour Angular.

Il est très facile d’installer les deux comme nous avons pu le voir dans la documentation officielle.

3.1. Mise en place du projet angulaire

Comme nous l'avons mentionné, nous utiliseronsAngular CLI pour créer notre application. Pour garder les choses simples et avoir tout en un seul endroit, nous conserverons notre application Angular dans le dossier/src/main/frontend.

Pour le créer, nous devons ouvrir un terminal (ou une invite de commande) dans le dossier/src/main et exécuter:

ng new frontend

Cela créera tous les fichiers et dossiers dont nous avons besoin pour notre application Angular. Dans le fichierpakage.json, nous pouvons vérifier quelles versions de nos dépendances sont installées. Ce tutoriel est basé sur Angular v6.0.3, mais les anciennes versions devraient faire le travail, au moins les versions 4.3 et plus récentes (HttpClient que nous utilisons ici a été introduit dans Angular 4.3).

Nous devons noter quewe’ll run all our commands from the /frontend folder sauf indication contraire.

Cette configuration suffit pour démarrer l'application Angular en exécutant la commandeng serve. Par défaut, il fonctionne surhttp://localhost:4200 et si nous y allons maintenant, nous verrons l'application Angular de base chargée.

3.2. Ajout de Bootstrap

Avant de procéder à la création de nos propres composants, ajoutons d'abordBootstrap à notre projet afin que nous puissions rendre nos pages plus belles.

Nous avons besoin de quelques choses pour y parvenir. First, we need torun a command to install it:

npm install --save bootstrap

etthen to say to Angular to actually use it. Pour cela, nous devons ouvrir un fichiersrc/main/frontend/angular.json et ajouter la propriéténode_modules/bootstrap/dist/css/bootstrap.min.css under“styles”. Et c'est tout.

3.3. Composants et modèles

Avant de commencer à créer les composants de notre application, voyons d'abord à quoi ressemblera réellement notre application:

image

Maintenant, nous allons créer un composant de base, nomméecommerce:

ng g c ecommerce

Cela créera notre composant dans le dossier/frontend/src/app. To load it at application startup, we’llinclude itinto the app.component.html:

Ensuite, nous allons créer d'autres composants à l'intérieur de ce composant de base:

ng g c /ecommerce/products
ng g c /ecommerce/orders
ng g c /ecommerce/shopping-cart

Certes, nous aurions pu créer tous ces dossiers et fichiers manuellement si vous le souhaitez, mais dans ce cas, nous aurions besoin deremember to register those components in our AppModule.

Nous aurons également besoin de modèles pour manipuler facilement nos données:

export class Product {
    id: number;
    name: string;
    price: number;
    pictureUrl: string;

    // all arguments constructor
}
export class ProductOrder {
    product: Product;
    quantity: number;

    // all arguments constructor
}
export class ProductOrders {
    productOrders: ProductOrder[] = [];
}

Le dernier modèle mentionné correspond à nosOrderForm sur le backend.

3.4. Composant de base

En haut de notre composantecommerce, nous allons mettre une barre de navigation avec le lien Accueil sur la droite:

Nous chargerons également d'autres composants à partir d'ici:

Nous devons garder à l'esprit que, pour voir le contenu de nos composants, puisque nous utilisons la classenavbar, nous devons ajouter du CSS auxapp.component.css:

.container {
    padding-top: 65px;
}

Vérifions le fichier.ts avant de commenter les parties les plus importantes:

@Component({
    selector: 'app-ecommerce',
    templateUrl: './ecommerce.component.html',
    styleUrls: ['./ecommerce.component.css']
})
export class EcommerceComponent implements OnInit {
    private collapsed = true;
    orderFinished = false;

    @ViewChild('productsC')
    productsC: ProductsComponent;

    @ViewChild('shoppingCartC')
    shoppingCartC: ShoppingCartComponent;

    @ViewChild('ordersC')
    ordersC: OrdersComponent;

    toggleCollapsed(): void {
        this.collapsed = !this.collapsed;
    }

    finishOrder(orderFinished: boolean) {
        this.orderFinished = orderFinished;
    }

    reset() {
        this.orderFinished = false;
        this.productsC.reset();
        this.shoppingCartC.reset();
        this.ordersC.paid = false;
    }
}

Comme nous pouvons le voir, cliquer sur le lienHome réinitialisera les composants enfants. Nous devons accéder aux méthodes et à un champ à l'intérieur des composants enfants depuis le parent, c'est pourquoi nous conservons les références aux enfants et utilisons celles à l'intérieur de la méthodereset().

3.5. Le service

Poursiblings components to communicate with each otherand to retrieve/send data from/to our API, nous devons créer un service:

@Injectable()
export class EcommerceService {
    private productsUrl = "/api/products";
    private ordersUrl = "/api/orders";

    private productOrder: ProductOrder;
    private orders: ProductOrders = new ProductOrders();

    private productOrderSubject = new Subject();
    private ordersSubject = new Subject();
    private totalSubject = new Subject();

    private total: number;

    ProductOrderChanged = this.productOrderSubject.asObservable();
    OrdersChanged = this.ordersSubject.asObservable();
    TotalChanged = this.totalSubject.asObservable();

    constructor(private http: HttpClient) {
    }

    getAllProducts() {
        return this.http.get(this.productsUrl);
    }

    saveOrder(order: ProductOrders) {
        return this.http.post(this.ordersUrl, order);
    }

    // getters and setters for shared fields
}

Des choses relativement simples sont ici, comme nous pourrions le remarquer. Nous faisons une demande GET et POST pour communiquer avec l'API. De plus, nous rendons observables les données que nous devons partager entre les composants afin que nous puissions nous y abonner ultérieurement.

Néanmoins, nous devons souligner une chose concernant la communication avec l’API. Si nous exécutons l'application maintenant, nous en recevrions 404 sans récupérer aucune donnée. La raison en est que, puisque nous utilisons des URL relatives, Angular essaiera par défaut de faire un appel àhttp://localhost:4200/api/products et notre application backend s'exécute surlocalhost:8080.

Nous pourrions coder en dur les URL enlocalhost:8080, bien sûr, mais ce n’est pas quelque chose que nous voulons faire. Au lieu de cela,when working with different domains, we should create a file named proxy-conf.json in our /frontend folder:

{
    "/api": {
        "target": "http://localhost:8080",
        "secure": false
    }
}

Et puis nous avons besoin deopen package.json and change scripts.start property pour correspondre:

"scripts": {
    ...
    "start": "ng serve --proxy-config proxy-conf.json",
    ...
  }

Et maintenant, nous devrions justekeep in mind to start the application with npm start instead ng serve.

3.6. Des produits

Dans nosProductsComponent, nous allons injecter le service que nous avons créé précédemment, charger la liste de produits à partir de l'API et la transformer en liste deProductOrders puisque nous voulons ajouter un champ de quantité à chaque produit:

export class ProductsComponent implements OnInit {
    productOrders: ProductOrder[] = [];
    products: Product[] = [];
    selectedProductOrder: ProductOrder;
    private shoppingCartOrders: ProductOrders;
    sub: Subscription;
    productSelected: boolean = false;

    constructor(private ecommerceService: EcommerceService) {}

    ngOnInit() {
        this.productOrders = [];
        this.loadProducts();
        this.loadOrders();
    }

    loadProducts() {
        this.ecommerceService.getAllProducts()
            .subscribe(
                (products: any[]) => {
                    this.products = products;
                    this.products.forEach(product => {
                        this.productOrders.push(new ProductOrder(product, 0));
                    })
                },
                (error) => console.log(error)
            );
    }

    loadOrders() {
        this.sub = this.ecommerceService.OrdersChanged.subscribe(() => {
            this.shoppingCartOrders = this.ecommerceService.ProductOrders;
        });
    }
}

Nous avons également besoin d’une option pour ajouter le produit au panier ou en supprimer un:

addToCart(order: ProductOrder) {
    this.ecommerceService.SelectedProductOrder = order;
    this.selectedProductOrder = this.ecommerceService.SelectedProductOrder;
    this.productSelected = true;
}

removeFromCart(productOrder: ProductOrder) {
    let index = this.getProductIndex(productOrder.product);
    if (index > -1) {
        this.shoppingCartOrders.productOrders.splice(
            this.getProductIndex(productOrder.product), 1);
    }
    this.ecommerceService.ProductOrders = this.shoppingCartOrders;
    this.shoppingCartOrders = this.ecommerceService.ProductOrders;
    this.productSelected = false;
}

Enfin, nous allons créer une méthodereset () que nous avons mentionnée dans la section 3.4:

reset() {
    this.productOrders = [];
    this.loadProducts();
    this.ecommerceService.ProductOrders.productOrders = [];
    this.loadOrders();
    this.productSelected = false;
}

Nous allons parcourir la liste de produits dans notre fichier HTML et l'afficher à l'utilisateur:

{{order.product.name}}

${{order.product.price}}

Nous ajouterons également une classe simple au fichier CSS correspondant afin que tout puisse bien s'adapter:

.padding-0 {
    padding-right: 0;
    padding-left: 1;
}

3.7. Chariot

Dans le composantShoppingCart, nous allons également injecter le service. Nous l'utiliserons pour nous abonner aux modifications desProductsComponent (pour savoir quand le produit est sélectionné pour être placé dans le panier), puis mettre à jour le contenu du panier et recalculer le coût total en conséquence:

export class ShoppingCartComponent implements OnInit, OnDestroy {
    orderFinished: boolean;
    orders: ProductOrders;
    total: number;
    sub: Subscription;

    @Output() onOrderFinished: EventEmitter;

    constructor(private ecommerceService: EcommerceService) {
        this.total = 0;
        this.orderFinished = false;
        this.onOrderFinished = new EventEmitter();
    }

    ngOnInit() {
        this.orders = new ProductOrders();
        this.loadCart();
        this.loadTotal();
    }

    loadTotal() {
        this.sub = this.ecommerceService.OrdersChanged.subscribe(() => {
            this.total = this.calculateTotal(this.orders.productOrders);
        });
    }

    loadCart() {
        this.sub = this.ecommerceService.ProductOrderChanged.subscribe(() => {
            let productOrder = this.ecommerceService.SelectedProductOrder;
            if (productOrder) {
                this.orders.productOrders.push(new ProductOrder(
                    productOrder.product, productOrder.quantity));
            }
            this.ecommerceService.ProductOrders = this.orders;
            this.orders = this.ecommerceService.ProductOrders;
            this.total = this.calculateTotal(this.orders.productOrders);
        });
    }

    ngOnDestroy() {
        this.sub.unsubscribe();
    }
}

Nous envoyons un événement au composant parent d'ici lorsque la commande est terminée et que nous devons passer à la caisse. Il y a la méthodereset () ici aussi:

finishOrder() {
    this.orderFinished = true;
    this.ecommerceService.Total = this.total;
    this.onOrderFinished.emit(this.orderFinished);
}

reset() {
    this.orderFinished = false;
    this.orders = new ProductOrders();
    this.orders.productOrders = []
    this.loadTotal();
    this.total = 0;
}

Le fichier HTML est simple:

Shopping Cart
Total: ${{total}}

Items bought:
  • {{ order.product.name }} - {{ order.quantity}} pcs.

3.8. Ordres

Nous allons garder les choses aussi simples que possible et dans leOrdersComponent simuler le paiement en définissant la propriété sur true et en enregistrant l'ordre dans la base de données. On peut vérifier que les commandes sont sauvegardées soit viah2-console soit en appuyant surhttp://localhost:8080/api/orders.

Nous avons également besoin desEcommerceService ici afin de récupérer la liste de produits du panier et le montant total de notre commande:

export class OrdersComponent implements OnInit {
    orders: ProductOrders;
    total: number;
    paid: boolean;
    sub: Subscription;

    constructor(private ecommerceService: EcommerceService) {
        this.orders = this.ecommerceService.ProductOrders;
    }

    ngOnInit() {
        this.paid = false;
        this.sub = this.ecommerceService.OrdersChanged.subscribe(() => {
            this.orders = this.ecommerceService.ProductOrders;
        });
        this.loadTotal();
    }

    pay() {
        this.paid = true;
        this.ecommerceService.saveOrder(this.orders).subscribe();
    }
}

Et enfin, nous devons afficher des informations à l'utilisateur:

ORDER

  • {{ order.product.name }} - ${{ order.product.price }} x {{ order.quantity}} pcs.

Total amount: ${{ total }}

4. Fusion des applications Spring Boot et Angular

Nous avons terminé le développement de nos deux applications et il est probablement plus facile de le développer séparément comme nous l'avons fait. Mais, en production, il serait beaucoup plus pratique d’avoir une seule application, alors fusionnons maintenant ces deux applications.

Ce que nous voulons faire ici, c'est debuild the Angular app which calls Webpack to bundle up all the assets and push them into the /resources/static directory of the Spring Boot app. De cette façon, nous pouvons simplement exécuter l’application Spring Boot, tester notre application et emballer tout cela et le déployer en une seule application.

Pour rendre cela possible, nous devonsopen ‘package.json‘ again add some new scripts after scripts.build:

"postbuild": "npm run deploy",
"predeploy": "rimraf ../resources/static/ && mkdirp ../resources/static",
"deploy": "copyfiles -f dist/** ../resources/static",

Nous utilisons des packages que nous n’avons pas installés, nous allons donc les installer:

npm install --save-dev rimraf
npm install --save-dev mkdirp
npm install --save-dev copyfiles

La commanderimraf va regarder le répertoire et créer un nouveau répertoire (en le nettoyant en fait), tandis quecopyfiles copie les fichiers du dossier de distribution (où Angular place tout) dans notrestaticdossier de s.

Maintenant, nous avons juste besoin derun npm run build command and this should run all those commands and the ultimate output will be our packaged application in the static folder.

Nous exécutons ensuite notre application Spring Boot sur le port 8080, y accédons et utilisons l’application Angular.

5. Conclusion

Dans cet article, nous avons créé une application de commerce électronique simple. Nous avons créé une API sur le back-end en utilisant Spring Boot, puis nous l'avons utilisée dans notre application frontale made in Angular. Nous avons montré comment créer les composants dont nous avons besoin, les faire communiquer les uns avec les autres et récupérer / envoyer des données depuis / vers l'API.

Enfin, nous avons montré comment fusionner les deux applications en une seule application Web packagée au sein du dossier statique.

Comme toujours, le projet complet que nous avons décrit dans cet article se trouve dans leGitHub project.