0

I have angular shopping template and I am trying to connect the cart service that was used to my backend, so I have done so but to make thing easier in the database I had to change the request to my backend from {quantity, amount, product} to {quantity, amount, productId} so I don't have to store the whole product in the database and its working fine so far, now when I got the response I want to map it to its interface but first I need to find the product with the id that I have in the response, I am able to map the response if I have the whole product but again I don't want to have the product saved in my database. here is my code for the mapping but I have an issue calling my function that find the product by id:

this.getAllProducts().pipe(map(p => p.map((s): CartItem => ({
  currency: s.currency,
  product: this.productService.getProduct(s.product).subscribe(p => p),
  quantity: s.quantity,
  selectedColor: s.selectedColor,
  selectedSize: s.selectedSize,
  userId: s.userId
}))));


  private getAllProducts(): Observable<CartResponse[]> {
    return this.http.get<CartResponse[]>('http://localhost:8082/cart/getAllItems?userId=111').pipe(
      map(o => o.map((sp): CartResponse => ({
        quantity: sp.quantity,
        currency: sp.currency,
        product: sp.product,
        selectedColor: sp.selectedColor,
        selectedSize: sp.selectedSize,
        userId: sp.userId
      }))));
  }


export interface CartResponse {
    product: string;
    quantity: number;
    selectedSize: any;
    selectedColor: string;
    currency: string;
    userId: string;
}

export interface CartItem {
    product: Product;
    quantity: number;
    selectedSize: any;
    selectedColor: string;
    currency: string;
    userId: string;
}
5
  • The complete map section in the getAllProducts method is not necessary. Remove it. Commented Jul 13, 2019 at 12:06
  • Something that I dont understand: why are you handling elements from p in the nested map call as CarItem? Shouldnt the type of s by CartResponse? Commented Jul 13, 2019 at 12:07
  • Furthermore, please add the definitions of the used interfaces, specially CartResponse. Commented Jul 13, 2019 at 12:08
  • @Jota.Toledo I have added the interfaces definitions to the question Commented Jul 13, 2019 at 12:13
  • Added an answer :) Commented Jul 13, 2019 at 12:37

2 Answers 2

2

You can go with the following approach, notice that I created some factory methods with fake data in order to test it. You can replace them with your actual implementations:

import { of, Observable } from 'rxjs';
import { map, mergeMap, toArray } from 'rxjs/operators';

export interface Product {
  id: string;
  name: string;
}

export interface CartResponse {
  product: string;
  quantity: number;
  selectedSize: any;
  selectedColor: string;
  currency: string;
  userId: string;
}

export interface CartItem {
  product: Product;
  quantity: number;
  selectedSize: any;
  selectedColor: string;
  currency: string;
  userId: string;
}

const fakeGetAllProducts = (): Observable<CartResponse[]> => of<CartResponse[]>([
  { currency: "US", product: "PX1", quantity: 10, selectedColor: "red", selectedSize: "L", userId: "UX1" },
  { currency: "EU", product: "PX50", quantity: 10, selectedColor: "blue", selectedSize: "S", userId: "UX2" }
]);
const fakeGetProduct = (id: string): Observable<Product> => of<Product>({ id, name: `Product ${id}` });

// Here the cart response is destructured into 2 values: the product id and everything else
const mapResponseToItem = ({ product, ...noProduct }: CartResponse): Observable<CartItem> => fakeGetProduct(product).pipe(
  map<Product, CartItem>(product => ({ ...noProduct, product }))
);

fakeGetAllProducts().pipe(
  // flatten the array in order to process single items from it sequentially
  mergeMap(items => items),
  // map a cart response into a cart item observable and flatten it
  mergeMap(mapResponseToItem),
  // collect the sequentially processed elements into an array
  toArray()
).subscribe(console.log);

You can see the code working in this blitz

Sign up to request clarification or add additional context in comments.

1 Comment

Clever and elegant!
1

It's kind of tricky, but you can use switchMap + forkJoin to do the job. Notice I'm using [email protected]. It's important because in the previous versions forkJoin do not receive an array as an argument.

import {forkJoin, of as observableOf} from 'rxjs';
import {switchMap, map} from 'rxjs/operators';

....

this.getAllProducts().pipe(
  switchMap((items: CartResponse[]) => {
    // build an array of observables to get all products
    const arrayOfObservables = items.map((item: CartResponse) => 
        this.productService.getProduct(item.product));

    // now go to the database, grab the products, and combine the response
    // with the array of CartResponse you already had
    return forkJoin([
      observableOf(items), 
      ...arrayOfObservables
    ]);
  }),
  map((resultOfForkJoin) => {
    // Notice that resultOfForkJoin is an array
    // - The first item of the array is the original items 
    //    returned by getAllProducts(): CartResponse[]
    // - The rest of the elements of the array are the products

    const items: CartResponse[] = resultOfForkJoin[0];

    // filter the products of resultOfForkJoin and
    // build a JSON of them (for performance), where the attributes 
    // are the products id (I'm suposing each product
    // has a property named after 'id')
    const products = resultOfForkJoin
        .filter((r,index) => index > 0)
        .reduce((acc,a) => {acc[a.id] = a; return acc;}, {});

    // now you can assemble the desired items
    const itemsToReturn = items.map((s: CartResponse): CartItem => ({
      currency: s.currency,
      product: products[s.product],
      quantity: s.quantity,
      selectedColor: s.selectedColor,
      selectedSize: s.selectedSize,
      userId: s.userId
    }));

    return itemsToReturn;
  })
).subscribe((item) => console.log(item));

UPDATE: If you're using previous versions of rxjs, you can switch the forkJoin to:

forkJoin(observableOf(items), ...arrayOfObservables);

A stackblitz demo.

12 Comments

sorry mate I'm new to angular and I can't get this code to compile can you help?
Ok, I put a demo together at the end of my post. Also I fixed a thing in my code, so update yours if you had just copied and pasted it. One more thing: the hardest part of learning Angular, are the reactive programming (rxjs). So, keep your cool and do not give up. And notice my imports statement at the beginning, because I'm aliasing the of operator to observableOf
This site can be useful in the process of mastering in reactive programming: learnrxjs.io, but unfortunately it's not fully updated (the forkJoin, for example, do not use the last version yet)
shouldn't the item type should be CartResponse instead of CartItem because cartResponse the one who has productId, so I can pass it to this.productService.getProduct(item.product)); ?
Yeah, you were right. I saw the new information you added and fixed my code.
|

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.