Как настроить модели Spring для поддержки компонента редактирования Angular для сложного объекта? - PullRequest
0 голосов
/ 31 августа 2018

Я пишу свое первое веб-приложение на Spring и Angular. Я использовал PHP около 10 лет, затем открыл и использовал Rails в течение последних 10 лет, и сделал пару ASP-проектов для загрузки, так что я не новичок в веб-разработке в целом. Я пытаюсь создать свой первый полный набор действий CRUD, но я не могу найти документацию о том, как создать форму редактирования для «сложного» объекта (с родителями и / или детьми). Официальное руководство по Angular останавливается на этом, и я нигде не могу найти ни одного учебника в Интернете, который бы охватил это всесторонне. В моем примере я хочу компонент редактирования для «Продукта», где я могу изменить текущий выбранный «Механизм».

В Spring у меня есть классы, репозитории и контроллеры, настроенные для моих двух начальных моделей, которые выглядят так:

Двигатель (родительский) -> Продукт (дочерний)

Product.java

package com.xyz.cddm_ng;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import java.time.LocalDateTime;
import static javax.persistence.GenerationType.IDENTITY;

@Entity
@Getter @Setter
@NoArgsConstructor
@ToString @EqualsAndHashCode
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Product {
    @Id
    @GeneratedValue(strategy=IDENTITY)
    Long id;

    @NonNull String title;

    String note;

    @CreationTimestamp
    LocalDateTime createDateTime;

    @UpdateTimestamp
    LocalDateTime updateDateTime;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "engine_id")
    @JsonManagedReference
    Engine engine;
}

Engine.java (фрагмент):

@OneToMany(fetch = FetchType.LAZY, mappedBy = "engine")
@JsonBackReference
Collection<Product> products;

Контроллер и репозитории просто отключены.

ProductController.java

package com.xyz.cddm_ng;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("products")
@CrossOrigin(origins = "http://localhost:4200")
class ProductController { }

ProductRepository.java

package com.xyz.cddm_ng;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.web.bind.annotation.CrossOrigin;

@RepositoryRestResource
@CrossOrigin(origins = "http://localhost:4200")
interface ProductRepository extends JpaRepository<Product, Long> { }

На угловой стороне у меня есть product.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class ProductService {

  public API = '//localhost:8080';
  public PRODUCT_API = this.API + '/products';

  constructor(private http: HttpClient) { }

  getAll(): Observable<any> {
    return this.http.get(this.PRODUCT_API);
  }

  get(id: string) {
    return this.http.get(this.PRODUCT_API + '/' + id);
  }

  save(product: any): Observable<any> {
    let result: Observable<Object>;
    if (product['href']) {
      result = this.http.put(product.href, product);
    } else {
      result = this.http.post(this.PRODUCT_API, product);
    }
    return result;
  }

  remove(href: string) {
    return this.http.delete(href);
  }

}

product-edit.component.ts:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { ActivatedRoute, Router } from '@angular/router';
import { ProductService } from '../shared/product/product.service';
import { EngineService } from '../shared/engine/engine.service';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app-product-edit',
  templateUrl: './product-edit.component.html',
  styleUrls: ['./product-edit.component.css']
})
export class ProductEditComponent implements OnInit, OnDestroy {
  prod: any = { };

  sub: Subscription;

  engines: any[] = [ ];

  constructor(private route: ActivatedRoute,
              private router: Router,
              private productService: ProductService,
              private engineService: EngineService) { }

  ngOnInit() {
    this.sub = this.route.params.subscribe( params => {
      const id = params['id'];
      if (id) {
        this.productService.get(id)
          .subscribe(
            data => {
              this.prod = data;
            },
            error => {
              console.log(`Product with '${id}' not found, returning to list. Error was '${error}'.`);
              this.gotoList();
            });
        this.engineService.getAll().subscribe(
          data => {
            this.engines = data;
          });
      }
    });
  }

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

  gotoList() {
    this.router.navigate(['/product-list']);
  }

  save(form: NgForm) {
    this.productService.save(form).subscribe(result => {
      this.gotoList();
    }, error => console.error(error));
  }

  remove(href) {
    this.productService.remove(href).subscribe(result => {
      this.gotoList();
    }, error => console.error(error));
  }

}

product-edit.component.html

<form #f="ngForm" (ngSubmit)="save(f.value)">

  <div class="form-group">
    <label class="form-control-label" for="title">Title:</label>
    <input type="text" class="form-control" id="title" required [(ngModel)]="prod.title" name="title">
  </div>

  <div class="form-group">
    <label class="form-control-label" for="note">Note:</label>
    <input type="text" class="form_control" id="note" required [(ngModel)]="prod.note" name="note">
  </div>

  <div class="form-group">
    <label class="form-control-label" for="engine" #selectedValue>Engine</label>
    <select class="form-control" id="engine" name="engine" [(ngModel)]="prod.engine">
      <option [value]="null"></option>
      <option *ngFor="let engine of engines" [ngValue]="engine">{{engine.name}}</option>
    </select>
  </div>

  <button type="submit" class="btn btn-success">Submit</button>

</form>

Моя текущая проблема связана с генерацией правильного JSON со стороны Spring для потребления со стороны Angular. Я считаю, что мне нужно включить вложенный движок в продукт, чтобы текущий мог отображаться в выпадающем списке. (Насколько я понимаю, часть [(ngModel)]="prod.engine" должна позаботиться об этом.)

В моей попытке создать двунаправленную связь между Product и Engine я создал рекурсивный цикл. Если я нажимаю http://localhost:8080/products/1, Продукт загружает Двигатели, которые загружают Продукты, которые ... ждут его ... переполняет стек и вызывает ошибку браузера с 500. Попытка решить эту проблему приведите меня к этому вопросу: Двунаправленные отношения Джексона (Один-ко-многим) не работают , что все о @JsonIdentityInfo и связанных с ним аннотациях.

Итак, я поместил эти аннотации на мою модель, но затем я получил ошибки о том, что «не могу найти свойство с именем id». Попытка исправить эту проблему приводит к популярному вопросу: Spring boot @ResponseBody не сериализует идентификатор объекта . Там было обсуждение о том, что «ID» является «неправильной» вещью в REST-бэкэнде, но решение проблемы рекурсивной нетерпеливой загрузки с помощью @JsonIdentityInfo, похоже, НУЖНО идентификаторам. Итак, теперь я исправил эту проблему, и я получил идентификаторы в моем JSON, и у меня нет рекурсивного цикла, но я все еще не получаю связанные данные движка в JSON для продукта:

{
  "id" : 1,
  "title" : "eowir",
  "note" : "noerw",
  "createDateTime" : "2018-08-22T16:10:07.349752",
  "updateDateTime" : "2018-08-22T16:10:07.349752",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/products/1"
    },
    "product" : {
      "href" : "http://localhost:8080/products/1"
    },
    "engine" : {
      "href" : "http://localhost:8080/products/1/engine"
    }
  }
}

Удар http://localhost:8080/products/1/engine работает, как и http://localhost:8080/engines/1/products. Таким образом, модель работает, правильно связана, и маршрутизация работает как задумано. Что мешает мне выбросить двигатель как часть продукта JSON?

Кроме того, если я исправлю эту проблему и получу раздел двигателя вместе с продуктом JSON, это заставит работать форму редактирования? Будет ли это автоматически заполнять выпадающий список двигателя связанным с ним в данный момент?

1 Ответ

0 голосов
/ 04 сентября 2018

Итак, у меня была дискуссия на Reddit по этому поводу, и люди отвлекли меня от идеи использования Spring REST. Я указал на https://github.com/spring-projects/spring-petclinic,, который, согласно его README, является давним примером различных проектов в Spring. Во всем, что я читал до этого момента, я никогда не сталкивался с этим.

Из pom.xml в этом проекте я вижу, что комбинация Spring Data JPA и Spring Web предоставляет сервисы REST - все само по себе - для этого я и думал, что мне нужен Spring REST. (Мне все еще нужны аннотации Jackson @JsonIdentityInfo, чтобы предотвратить переполнение стека рекурсивным циклом в моих двунаправленных отношениях «один ко многим», но мне не нужны аннотации с обратной или управляемой ссылкой).

Из компонента и формы в подпроекте spring-petclinic-angular frontend я также, наконец, вижу, что I do должен вручную установить определенный связанный объект в выпадающем списке выберите (qv, https://github.com/spring-petclinic/spring-petclinic-angular/blob/master/src/app/pets/pet-edit/pet-edit.component.html),, а затем «пересоберите» этот выбор с родительским объектом в сервисе перед отправкой его в функцию обновления в бэкэнде. Это то, что я использовал для обработки Rails для меня, и Я еще не видел пример того, будет ли Angular каким-то образом делать это и для меня. Кажется, ответ «нет»

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...