import { Injectable } from '@angular/core' ; import { of , ReplaySubject , Observable } from 'rxjs' ; import { concatMap , first , map } from 'rxjs/operators' ; import { addEntity , collectionSnapshot , updateEntity , Customer , EntityCache , LineItem , Order , Product } from '../model' ; import { getNewCache } from './data' ; import { OrderGraph , ProductMap } from './order-graph' ; /**
 * Simulate getting and saving model data in a reactive (RxJS) and immutable way.
 * Kind of sort of like a data service facade you might write with Ngrx Data.
 */ @ Injectable ( { providedIn : 'root' } ) export class CustomerOrdersDataService { private nextId : number ; private cacheStore = new ReplaySubject < EntityCache > ( 1 ) ; cache$ = this . cacheStore . asObservable ( ) ; customers$ = this . cache$ . pipe ( map ( c => c . customers ) ) ; lineItems$ = this . cache$ . pipe ( map ( l => l . lineItems ) ) ; orders$ = this . cache$ . pipe ( map ( o => o . orders ) ) ; products$ = this . cache$ . pipe ( map ( p => p . products ) ) ; get customers ( ) { return collectionSnapshot < Customer > ( 'customers' , this . cache$ ) ; } get lineItems ( ) { return collectionSnapshot < LineItem > ( 'lineItems' , this . cache$ ) ; } get orders ( ) { return collectionSnapshot < Order > ( 'orders' , this . cache$ ) ; } get products ( ) { return collectionSnapshot < Product > ( 'products' , this . cache$ ) ; } /** Reinitialize cache with clones of the source raw data  */ reset ( ) { this . cacheStore . next ( getNewCache ( ) ) ; this . nextId = 100 ; console . log ( 'ViewModel Demo Cache Reset' ) ; } /**
     * Terminating Observable of the Customer with this id. Undefined if not found.
     * Must subscribe but no need to unsubscribe.
     */ getCustomerById ( customerId ) { return this . customers$ . pipe ( first ( ) , map ( customers => customers . find ( o => o . id === customerId ) ) ) ; } /**
     * Terminating Observable of an order, that order's lineItems, and the lineItems' products as a hash map.
     * Must subscribe but no need to unsubscribe.
     */ getOrderGraphByOrderId ( orderId ) { return this . orders$ . pipe ( first ( ) , map ( orders => orders . find ( o => o . id === orderId ) ) , concatMap ( order => this . getOrderGraphForOrder ( order ) ) ) ; } /**
     * Terminating Observable of an order, that order's lineItems, and the lineItems' products as a hash map.
     * Must subscribe but no need to unsubscribe.
     */ getOrderGraphForOrder ( order ) { if ( order == null ) { return of ( { order , lineItems : [ ] as LineItem [ ] , products : { } as ProductMap } ) ; } const lineItems$ = this . lineItems$ . pipe ( first ( ) , map ( items => items . filter ( item => item . orderId === order . id ) ) ) ; return lineItems$ . pipe ( concatMap ( lineItems => this . getLineItemProducts ( lineItems ) . pipe ( map ( products => ( { order , lineItems , products } ) ) ) ) ) ; } /**
     * Terminating Observable emitting a product map of products associated with the given Order line items.
     * Must subscribe but no need to unsubscribe.
     */ private getLineItemProducts = ( lineItems ) => { const productIds = lineItems && lineItems . length > 0 ? lineItems . map ( item => item . productId ) : [ ] ; return this . getProductMap ( productIds ) ; } ; /**
     * Terminating Observable emitting a product map of products with the given product ids
     * Must subscribe but no need to unsubscribe.
     */ private getProductMap ( productIds ) { if ( productIds && productIds . length > 0 ) { return this . products$ . pipe ( first ( ) , map ( products => products . filter ( p => productIds . includes ( p . id ) ) ) , map ( orderedProducts => orderedProducts . reduce ( ( acc , product ) => { acc [ product . id ] = product ; return acc ; } , { } as ProductMap ) ) ) ; } else { return of ( { } as ProductMap ) ; } } returns watchCustomerOrders ( customerOrId ) { const customerId = typeof customerOrId === 'number' ? customerOrId : customerOrId . id ; return this . orders$ . pipe ( map ( orders => orders . filter ( o => o . customerId === customerId ) ) ) ; } addCustomer ( customer = { } ) { const id = this . nextId ++ ; const newCustomer = Customer . create ( id , customer ) ; return addEntity < Customer > ( newCustomer , 'customers' , this . cacheStore ) ; } addLineItem ( lineItem = { } , order ? , product ? ) { const id = this . nextId ++ ; const newLineItem = LineItem . create ( id , lineItem , order , product ) ; return addEntity < LineItem > ( newLineItem , 'lineItems' , this . cacheStore ) ; } addOrder ( order = { } , customer ? ) { const id = this . nextId ++ ; const newOrder = Order . create ( id , order , customer ) ; return addEntity < Order > ( newOrder , 'orders' , this . cacheStore ) ; } updateCustomer ( customer ) { return updateEntity < Customer > ( customer as Customer , 'customers' , this . cacheStore ) ; } updateLineItem ( lineItem ) { return updateEntity < LineItem > ( lineItem as LineItem , 'lineItems' , this . cacheStore ) ; } updateOrder ( order ) { return updateEntity < Order > ( order as Order , 'orders' , this . cacheStore ) ; } }