Eine einfache E-Commerce-Implementierung mit Spring

Eine einfache E-Commerce-Implementierung mit Spring

1. Übersicht unserer E-Commerce-Anwendung

In diesem Tutorial implementieren wir eine einfache E-Commerce-Anwendung. Wir werden eine API mitSpring Boot und eine Clientanwendung entwickeln, die die API mitAngular verwendet.

Grundsätzlich ist der Benutzer in der Lage, Produkte aus einer Produktliste zu einem Warenkorb hinzuzufügen / daraus zu entfernen und eine Bestellung aufzugeben.

2. Backend-Teil

Für die Entwicklung der API verwenden wir die neueste Version von Spring Boot. Wir verwenden auch JPA- und H2-Datenbanken für die Persistenz.

To learn more about Spring Boot,you could check out our Spring Boot series of articles und wenn Sieto get familiar with building a REST API, please check out another series möchten.

2.1. Maven-Abhängigkeiten

Bereiten wir unser Projekt vor und importieren die erforderlichen Abhängigkeiten in unserepom.xml.

Wir benötigen einige KernSpring Boot dependencies:


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


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

Dann sind dieH2 database:


    com.h2database
    h2
    1.4.197
    runtime

Und schließlich - dieJackson library:


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

Wir habenSpring Initializr verwendet, um das Projekt mit den erforderlichen Abhängigkeiten schnell einzurichten.

2.2. Einrichten der Datenbank

Obwohl wir mit Spring Boot die In-Memory-H2-Datenbank sofort verwenden können, werden wir noch einige Anpassungen vornehmen, bevor wir mit der Entwicklung unserer API beginnen.

Wir werdenenable H2 console in unsererapplication.properties-Dateiso we can actually check the state of our database and see if everything is going as we’d expect.

Es kann auch nützlich sein, SQL-Abfragen während der Entwicklung in der Konsole zu protokollieren:

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

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

Nach dem Hinzufügen dieser Einstellungen können wir unterhttp://localhost:8080/h2-console mitjdbc:h2:mem:ecommercedb as JDBC-URL und Benutzersa ohne Kennwort auf die Datenbank zugreifen.

2.3. Die Projektstruktur

Das Projekt wird in mehrere Standardpakete unterteilt, wobei die Angular-Anwendung im Frontend-Ordner abgelegt wird:

├───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

Wir sollten beachten, dass alle Schnittstellen im Repository-Paket einfach sind und dieCrudRepositoryvon Spring Data erweitern. Daher werden wir sie hier nicht anzeigen.

2.4. Ausnahmebehandlung

Wir benötigen einen Ausnahmehandler für unsere API, um eventuelle Ausnahmen ordnungsgemäß behandeln zu können.

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.

Hier konzentrieren wir uns aufConstraintViolationException und unsere benutzerdefiniertenResourceNotFoundException:

@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. Produkte

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

Unsere Anwendung unterstütztonly reading products from the database, daher müssen wir zuerst einige hinzufügen.

Erstellen wir eine einfacheProduct-Klasse:

@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
}

Obwohl der Benutzer nicht die Möglichkeit hat, Produkte über die Anwendung hinzuzufügen, unterstützen wir das Speichern eines Produkts in der Datenbank, um die Produktliste vorab auszufüllen.

Ein einfacher Service wird für unsere Bedürfnisse ausreichen:

@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);
    }
}

Ein einfacher Controller verarbeitet Anforderungen zum Abrufen der Produktliste:

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

    // productService constructor injection

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

Jetzt müssen wir nur noch einige Produkte in die Datenbank aufnehmen, um die Produktliste dem Benutzer zugänglich zu machen. Daher verwenden wir die KlasseCommandLineRunner, umBeanin unserer Hauptanwendungsklasse zu erstellen.

Auf diese Weise fügen wir beim Start der Anwendung Produkte in die Datenbank ein:

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

Wenn wir jetzt unsere Anwendung starten, können wir die Produktliste überhttp://localhost:8080/api/products. abrufen. Wenn wir zuhttp://localhost:8080/h2-console gehen und uns anmelden, sehen wir, dass es eine Tabelle mit dem NamenPRODUCT mit dem gibt Produkte, die wir gerade hinzugefügt haben.

2.6. Aufträge

Auf der API-Seite müssen wir POST-Anforderungen aktivieren, um die vom Endbenutzer ausgeführten Bestellungen zu speichern.

Erstellen wir zunächst das Modell:

@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
}

Wir sollten hier ein paar Dinge beachten. Sicherlich ist eines der bemerkenswertesten Dingeremember to change the default name of our table. Da wir die KlasseOrder benannt haben, sollte standardmäßig die TabelleORDER erstellt werden. Da dies jedoch ein reserviertes SQL-Wort ist, haben wir@Table(name = “orders”) hinzugefügt, um Konflikte zu vermeiden.

Darüber hinaus haben wir zwei@Transient methods that will return a total amount for that order and the number of products in it. Beide repräsentieren berechnete Daten, sodass sie nicht in der Datenbank gespeichert werden müssen.

Schließlich haben wir ein@OneToMany relation representing the order’s details. Dafür brauchen wir eine andere Entitätsklasse:

@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
}

Diese Klassen sind nicht allzu kompliziert, aber wir sollten beachten, dass wir inOrderProduct Klasse@JsonIgnore auf den Primärschlüssel setzen. Dies liegt daran, dass wir denOrder-Teil des Primärschlüssels nicht serialisieren möchten, da er redundant wäre.

Wir brauchen nur dieProduct, die dem Benutzer angezeigt werden sollen. Deshalb haben wir die vorübergehendegetProduct()-Methode.

Als nächstes benötigen wir eine einfache Service-Implementierung:

@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);
    }
}

Und ein Controller, der/api/orders zugeordnet ist, umOrder Anforderungen zu verarbeiten.

Am wichtigsten ist diecreate () -Methode:

@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);
}

Zunächstwe accept a list of products with their corresponding quantities. Danachwe check if all products exist in der Datenbank undthen create and save a new order. Wir behalten einen Verweis auf das neu erstellte Objekt bei, damit wir ihm Bestelldetails hinzufügen können.

Schließlichwe create a “Location” header.

Die detaillierte Implementierung befindet sich im Repository - der Link dazu wird am Ende dieses Artikels erwähnt.

3. Vorderes Ende

Nachdem wir unsere Spring Boot-Anwendung erstellt haben, ist es Zeit,the Angular part of the project zu verschieben. Dazu müssen wir zuerstNode.js mit NPM und danachAngular CLI, eine Befehlszeilenschnittstelle für Angular, installieren.

Es ist wirklich einfach, beide zu installieren, wie wir in der offiziellen Dokumentation sehen konnten.

3.1. Einrichten des Winkelprojekts

Wie bereits erwähnt, verwenden wirAngular CLI, um unsere Anwendung zu erstellen. Um die Dinge einfach zu halten und alles an einem Ort zu haben, behalten wir unsere Angular-Anwendung im Ordner/src/main/frontend.

Um es zu erstellen, müssen wir ein Terminal (oder eine Eingabeaufforderung) im Ordner/src/mainöffnen und Folgendes ausführen:

ng new frontend

Dadurch werden alle Dateien und Ordner erstellt, die wir für unsere Angular-Anwendung benötigen. In der Dateipakage.json können wir überprüfen, welche Versionen unserer Abhängigkeiten installiert sind. Dieses Tutorial basiert auf Angular v6.0.3, aber ältere Versionen sollten den Job machen, mindestens Versionen 4.3 und neuere (HttpClient, die wir hier verwenden, wurden in Angular 4.3 eingeführt).

Wir sollten beachten, dasswe’ll run all our commands from the /frontend folder ist, sofern nicht anders angegeben.

Dieses Setup reicht aus, um die Angular-Anwendung durch Ausführen des Befehlsng serve zu starten. Standardmäßig wird es aufhttp://localhost:4200 ausgeführt. Wenn wir jetzt dorthin gehen, wird die Basis-Angular-Anwendung geladen.

3.2. Bootstrap hinzufügen

Bevor wir mit dem Erstellen unserer eigenen Komponenten fortfahren, fügen wir zunächstBootstrap zu unserem Projekt hinzu, damit unsere Seiten gut aussehen.

Wir brauchen nur ein paar Dinge, um dies zu erreichen. First, we need torun a command to install it:

npm install --save bootstrap

undthen to say to Angular to actually use it. Dazu müssen wir eine Dateisrc/main/frontend/angular.json öffnen und die Eigenschaftnode_modules/bootstrap/dist/css/bootstrap.min.css under“styles”hinzufügen. Und das ist es.

3.3. Komponenten und Modelle

Bevor wir mit dem Erstellen der Komponenten für unsere Anwendung beginnen, überprüfen wir zunächst, wie unsere App tatsächlich aussehen wird:

image

Jetzt erstellen wir eine Basiskomponente mit dem Namenecommerce:

ng g c ecommerce

Dadurch wird unsere Komponente im Ordner/frontend/src/apperstellt. To load it at application startup, we’llinclude itinto the app.component.html:

Als Nächstes erstellen wir weitere Komponenten in dieser Basiskomponente:

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

Natürlich hätten wir alle diese Ordner und Dateien bei Bedarf manuell erstellen können, aber in diesem Fall müssten wirremember to register those components in our AppModule.

Wir benötigen auch einige Modelle, um unsere Daten einfach zu manipulieren:

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[] = [];
}

Das zuletzt genannte Modell entspricht unserenOrderForm im Backend.

3.4. Basiskomponente

Oben in unsererecommerce-Komponente wird eine Navigationsleiste mit dem Home-Link rechts eingefügt:

Von hier aus werden auch andere Komponenten geladen:

Wir sollten bedenken, dass wir, um den Inhalt unserer Komponenten zu sehen, da wir die Klassenavbarverwenden, etwas CSS zu denapp.component.csshinzufügen müssen:

.container {
    padding-top: 65px;
}

Schauen wir uns die.ts-Datei an, bevor wir die wichtigsten Teile kommentieren:

@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;
    }
}

Wie wir sehen können, werden durch Klicken auf den LinkHomeuntergeordnete Komponenten zurückgesetzt. Wir müssen vom übergeordneten Element aus auf Methoden und ein Feld in untergeordneten Komponenten zugreifen. Aus diesem Grund behalten wir Verweise auf die untergeordneten Komponenten bei und verwenden diese in derreset()-Methode.

3.5. Der Service

Umsiblings components to communicate with each otherand to retrieve/send data from/to our API zu erhalten, müssen wir einen Service erstellen:

@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
}

Relativ einfache Dinge sind hier drin, wie wir bemerken konnten. Wir stellen eine GET- und eine POST-Anfrage, um mit der API zu kommunizieren. Außerdem machen wir Daten, die wir für die gemeinsame Nutzung zwischen Komponenten benötigen, beobachtbar, damit wir sie später abonnieren können.

Dennoch müssen wir auf eines in Bezug auf die Kommunikation mit der API hinweisen. Wenn wir die Anwendung jetzt ausführen, würden wir 404 erhalten und keine Daten abrufen. Der Grund dafür ist, dass Angular standardmäßig versucht,http://localhost:4200/api/products aufzurufen, da wir relative URLs verwenden, und unsere Backend-Anwendung auflocalhost:8080 ausgeführt wird.

Wir könnten die URLs natürlich fest inlocalhost:8080 codieren, aber das wollen wir nicht. Stattdessenwhen working with different domains, we should create a file named proxy-conf.json in our /frontend folder:

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

Und dann müssen wiropen package.json and change scripts.start property übereinstimmen:

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

Und jetzt sollten wir nurkeep in mind to start the application with npm start instead ng serve.

3.6. Produkte

In unserenProductsComponent fügen wir den zuvor erstellten Service ein, laden die Produktliste aus der API und wandeln sie in die Liste derProductOrders um, da wir jedem Produkt ein Mengenfeld hinzufügen möchten:

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;
        });
    }
}

Wir benötigen auch eine Option, um das Produkt in den Warenkorb zu legen oder ein Produkt daraus zu entfernen:

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;
}

Schließlich erstellen wir einereset () -Methode, die wir in Abschnitt 3.4 erwähnt haben:

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

Wir werden die Produktliste in unserer HTML-Datei durchlaufen und sie dem Benutzer anzeigen:

{{order.product.name}}

${{order.product.price}}

Wir werden der entsprechenden CSS-Datei auch eine einfache Klasse hinzufügen, damit alles gut passt:

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

3.7. Einkaufswagen

In derShoppingCart-Komponente wird auch der Service eingefügt. Wir werden es verwenden, um die Änderungen inProductsComponent zu abonnieren (um festzustellen, wann das Produkt ausgewählt wurde, um in den Warenkorb gelegt zu werden) und dann den Inhalt des Warenkorbs zu aktualisieren und die Gesamtkosten entsprechend neu zu berechnen:

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();
    }
}

Wir senden von hier aus ein Ereignis an die übergeordnete Komponente, wenn die Bestellung abgeschlossen ist und wir zur Kasse gehen müssen. Hier gibt es auch diereset () -Methode:

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;
}

HTML-Datei ist einfach:

Shopping Cart
Total: ${{total}}

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

3.8. Aufträge

Wir werden die Dinge so einfach wie möglich halten und inOrdersComponent das Bezahlen simulieren, indem wir die Eigenschaft auf true setzen und die Bestellung in der Datenbank speichern. Wir können überprüfen, ob die Bestellungen entweder überh2-console oder durch Drücken vonhttp://localhost:8080/api/orders. gespeichert wurden

Wir benötigen auch hier dieEcommerceService, um die Produktliste aus dem Warenkorb und den Gesamtbetrag für unsere Bestellung abzurufen:

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();
    }
}

Und schließlich müssen wir dem Benutzer Informationen anzeigen:

ORDER

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

Total amount: ${{ total }}

4. Zusammenführen von Spring Boot- und Angular-Anwendungen

Wir haben die Entwicklung unserer beiden Anwendungen abgeschlossen und es ist wahrscheinlich einfacher, sie getrennt zu entwickeln, als wir es getan haben. In der Produktion wäre es jedoch viel bequemer, eine einzige Anwendung zu haben. Lassen Sie uns diese beiden nun zusammenführen.

Was wir hier tun wollen, istbuild 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. Auf diese Weise können wir einfach die Spring Boot-Anwendung ausführen und unsere Anwendung testen und all dies packen und als eine App bereitstellen.

Um dies zu ermöglichen, müssen wiropen ‘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",

Wir verwenden einige Pakete, die wir nicht installiert haben. Installieren wir sie also:

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

Der Befehlrimraf überprüft das Verzeichnis und erstellt ein neues Verzeichnis (bereinigt es tatsächlich), währendcopyfiles die Dateien aus dem Verteilungsordner (in dem Angular alles ablegt) in unserstatickopiert. s Ordner.

Jetzt brauchen wir nur nochrun npm run build command and this should run all those commands and the ultimate output will be our packaged application in the static folder.

Dann führen wir unsere Spring Boot-Anwendung auf dem Port 8080 aus, greifen dort darauf zu und verwenden die Angular-Anwendung.

5. Fazit

In diesem Artikel haben wir eine einfache E-Commerce-Anwendung erstellt. Wir haben eine API im Backend mit Spring Boot erstellt und diese dann in unserer Frontend-Anwendung aus Angular verwendet. Wir haben gezeigt, wie die von uns benötigten Komponenten hergestellt, miteinander kommuniziert und Daten von der API abgerufen bzw. an diese gesendet werden.

Schließlich haben wir gezeigt, wie Sie beide Anwendungen in einer Webanwendung zusammenführen, die sich im statischen Ordner befindet.

Wie immer finden Sie das vollständige Projekt, das wir in diesem Artikel beschrieben haben, inGitHub project.