0

On the Spring JPA side, in my controller, I have:

@GetMapping()
public List<Product> getProducts() {
    List<Product> products = repository.findAll();
    int a = 1;
    return products;
}

In my Angular (6) service, I have:

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

In my Angular component, I have:

products: Product[] = [];

ngOnInit() {
    this.productService.getAll().subscribe((data: Product[]) => {
        console.log(data);
        this.products = data;
    });
}

In my list component HTML, out of 4 Products in the database, I get the first Product's attributes emitted correctly, but I have nothing in the fields for the other 3.

In the browser's console, I see this:

Array(4) [ {…}, 2, 3, 4 ] product-list.component.ts:19:6

I can put a breakpoint on the bogus assignment in the code running in the Spring controller, and see that I'm returning an array of Product, as expected. But, by the time it leaves Spring and arrives at Angular, the array has become flattened to one object (the first Product), and 3 numbers.

Everything seems to be correctly typed. I can't figure out what I'm supposed to do to return an array of my Product type correctly.


According to the request to show the JSON from the API side, I've finally figured out how to do some basic logging, and dumped the List of Product. Now I'm even more confused. Sure enough, the Angular side is showing exactly what it received:

[ {
  "id" : 1,
  "title" : "New Friggin Product",
  "note" : "This is a note",
  "createDateTime" : {
    "month" : "AUGUST",
    "year" : 2018,
    "dayOfMonth" : 20,
    "dayOfWeek" : "MONDAY",
    "dayOfYear" : 232,
... <SNIP>
        "second" : 59,
        "monthValue" : 8,
        "chronology" : {
          "id" : "ISO",
          "calendarType" : "iso8601"
        }
      },
      "engine" : 1
    } ]
  }
}, 2, 3, 4 ]

There's an Array of Product in the JSON, and then the 3 numbers.

Double edit! I had listed the Repository code, but upon further reflection, the repo class is correct. It is returning 4 proper Products in its response.

Correct response before returning to Angular

The problem is the conversion to JSON, and I think I've logged exactly what the Jackson library is doing to the List when I return the response to Angular. The question is why, when everything has been typed correctly.

1
  • what does the browser network tab say the HTTP response is? Commented Sep 25, 2018 at 21:36

1 Answer 1

1

Turns out that this is a known issue. The JSON renderer in Jackson does not include references to a repeated sub-object in a list of objects. For some bizarre reason, this is by design.

To fix it, you need to generate your JSON response with JSOG (https://github.com/jsog/jsog) on the Spring side, and JSOG-Typescript (https://github.com/emundo/jsog-typescript) on the Angular side.

Note that you still need the @JsonBackReference and @JsonManagedReferences to prevent an infinite recursion loop.

My final classes look like this:

Engine.java (parent)

@Entity
@Getter @Setter
@NoArgsConstructor
@ToString @EqualsAndHashCode
@JsonIdentityInfo(generator = JSOGGenerator.class)
public class Engine implements Serializable { //

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

    @NonNull
    private String name;

    private String family;

    @CreationTimestamp
    private LocalDateTime createDateTime;

    @UpdateTimestamp
    private LocalDateTime updateDateTime;

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

}

Product.java (child)

@Entity
@Getter @Setter
@NoArgsConstructor
@ToString @EqualsAndHashCode
@JsonIdentityInfo(generator = JSOGGenerator.class)
public class Product implements Serializable { //

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

    @NonNull
    private String title;

    private String note;

    @CreationTimestamp
    private LocalDateTime createDateTime;

    @UpdateTimestamp
    private LocalDateTime updateDateTime;

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "engine_id")
    @JsonManagedReference
    private Engine engine;

}

product-list.component.ts

import { Component, OnInit } from '@angular/core';
import { ProductService } from '../shared/product/product.service';
import { Product } from '../shared/models/product';
import { JsogService } from 'jsog-typescript';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.css']
})

export class ProductListComponent implements OnInit {
  products: Product[] = [];

  constructor(private productService: ProductService) { }

  ngOnInit() {
    this.productService.getAll().subscribe(data => {
      const jsog = new JsogService();
      this.products = jsog.deserializeArray(data, Product);
    });
  }

}

Also, to make the above work, I had to go back to an UN-typed .getAll() in my Angular component:

getAll(): Observable<any> {
    return this.http.get(this.PRODUCT_API);
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.