'How to emit a subscribe event in a nested object with Angular?

I have a application in Angular that has a object that i want his acess in every component. This is the object:

export class CartModel {

  public items: ItemModel[] = [];
  public coupon?: Coupon;
  public payment?: PaymentOrderModel;
  public discount: number;
  public totalQty: number;
  public totalFee: number;
  public totalValue: number;

}

This object have a nested object called payment that can be null, this is the object:

export class PaymentOrderModel {

  public type: string = '';
  public method: string = '';
  public change: number = 0;
  public card?: CreditCardModel;
  public extra: any;

}

In my application i have a service called CartService that have this CartModel object.

This is the service line:

    private currentCartSubject: BehaviorSubject<CartModel> = new BehaviorSubject<CartModel>(new CartModel());
    
      constructor(
        private storageService: StorageService,
        private snackBarService: SnackBarService,
        private authService: AuthService,
        private companyService: CompanyService,
        private http: HttpClient
      ) {}

      getCurrentCart() {
        return this.currentCartSubject.asObservable();
      }
    
      get cart(): CartModel {
        return this.currentCartSubject.value;
      }
    
      set cart(cart: CartModel) {
        this.currentCartSubject.next(cart);
      }

This is the subscribe line:

this.cartService.getCurrentCart().subscribe(
  cart => {
    console.log("EMITED");
    this.cart = cart;
  }
);

In another component when a user click the button to choose his payment option i do this:

  selectCard(payment: any) {
    this.cartService.cart.payment = PaymentOrderModel.setPaymentOnDelivery(payment);
    this.facebookService.emitEvent('track', 'AddPaymentInfo');
    this.closeModal$.next(true);
  }

The problem is this line this.cartService.cart.payment = PaymentOrderModel.setPaymentOnDelivery(payment);, this is the function:

 static setPaymentOnDelivery(payment: any): PaymentOrderModel {
    return new PaymentOrderModel(
      'payment_on_delivery',
      payment.method,
      0,
      null as any,
      { name: payment.name, flag: payment.srcimg, brand: payment.brand }
    );
  }

This event is not fired and my subscribe don't recieve the object, but if i do this:

this.cartService.cart = new CartModel();

It fire, so the problem is the nested object, in some time ago i have a problem with this in a VueJs application and there they have this function for nested objects:

Vue.set(this.cart, 'payment', { name: payment.name, srcimg: payment.srcimg, brand: payment.brand });

How i can do this in Angular way?



Solution 1:[1]

Regarding following line,

this.cartService.cart.payment = PaymentOrderModel.setPaymentOnDelivery(payment);

You're not calling the setter which is

set cart(cart: CartModel) {
    this.currentCartSubject.next(cart);
}

You are firstly calling getter get cart(), retrieving value and then changing the value of one of its field called payment.

A BehaviorSubject will not emit changes if you do not explicitly call next() method. In your case, you are just modifying the inner variable held by BehaviorSubject and this change will not be detected.

What I recommend is to clone a CartModel, override the field, and call setter directly.

this.cartService.cart = {
  ...this.cartService.cart, 
  payment: PaymentOrderModel.setPaymentOnDelivery(payment)
}

The three dots operator is called spread operator, you can find more details in MDN

Solution 2:[2]

Your subscriber will receive data when observable will emit data,In your case you are emitting from CartSerice setter.

this.cartService.cart.payment - this line calling CartService getter

this.cartService.cart = new CartModel(); -this line actually calls CartService setter,thats why subscriber receiving data

So in order to receive data on setting payment,you can do something like bellow

add inside CartService

set payment(payment: PaymentOrderModel) {
    this.currentCartSubject.value.payment = payment;
    this.currentCartSubject.next(this.currentCartSubject.value);
  }

And inside component replace

this.cartService.cart.payment = PaymentOrderModel.setPaymentOnDelivery(payment);

with

this.cartService.payment = PaymentOrderModel.setPaymentOnDelivery(payment);

modified selectCard

selectCard(payment: any) {
   this.cartService.payment = PaymentOrderModel.setPaymentOnDelivery(payment);
   this.facebookService.emitEvent('track', 'AddPaymentInfo');
   this.closeModal$.next(true);
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tamashii
Solution 2