Я пишу свое первое веб-приложение на 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, это заставит работать форму редактирования? Будет ли это автоматически заполнять выпадающий список двигателя связанным с ним в данный момент?