Search code examples
angularrxjsbehaviorsubject

Rxjs include deeply nested objects in behaviorsubject


I am starting to play with the BehaviorSubject in Rxjs with limited experience in this area. So far I am able to get the root level parameters in the component, but accesing nested objects results in 'cannot read property x of undefined'.

Classes:

export class Basket extends X23Object {
    public BasketId: number;
    public UserID: number;
    public User: User;
    public BasketLines: BasketLine[] = [];
}

export class BasketLine extends X23Object {
    public BasketLineId: number;
    public BasketId: number;
    public ProductId: number;
    public Quantity: number;
    public Product: Product;
    public Price: number;
    public Name: string;
}
export class Product extends X23Object {
    public ProductId: number;
    public CategoryId: number;
    public Name: string;
    public Code: string;
    public Price: number;
    public Details: string;
    public Images: Image[];
    public Category: Category;
}

BasketBackendService

GetBasket() {
    return this.authHttp.get(this.apiUrl + 'api/GetBasketByUser?id=' + parseInt(this.decodedJwt['userId']), { headers: contentHeaders });
}

BasketService

private _lines: BehaviorSubject<BasketLine[]> = new BehaviorSubject([new BasketLine]);
get getLines() {
    return this._lines.asObservable();
}
loadBasket() {
    this.basketBackendService.GetBasket()
        .subscribe(
            response => {
                let lines = <BasketLine[]>response.json().basket.BasketLines;

                this._lines.next(lines);
            },
            error => console.log(error.text())
        );
}

Template (snippet)

<tr *ngFor="let line of basketService.getLines | async">
    <td><img class="login-logo" src="{{ line.Product.Images[0].ThumbUrl }}" width="100%" /></td>
    <td><a [routerLink]="['Product', { id: line.ProductId }]"> {{ line.Product.Code }} </a></td>
    <td><a [routerLink]="['Product', { id: line.ProductId }]">{{ line.Product.Name }}</a></td>
    <td class="text-right">{{ line.Price | currency:'GBP':true:'.2-2' }}</td>
    <td class="text-center">{{ line.Quantity }}</td>
    <td class="text-right">{{ line.Quantity * line.Price | currency:'GBP':true:'.2-2' }}</td>
    <td><button (click)="DeleteLine(line.BasketLineId)">Remove</button></td>
</tr>

If I remove the references of deeply nested objects, I get the expected results returned.

I am trying to use a BehaviorSubject to update several components, but not sure if this is even the best solution!


Solution

  • The code looks fine to me, I guess by "deep nested" you mean for example line.Product.Code. If it works with line.Quantity then the problem is most likely not in BehaviorSubject but in the structure of your data.

    I don't know what's your particular use-case but you don't need to use neither BehaviorSubject nor async pipe at all.

    For BasketService you can use just:

    export class BasketService {
        lines: BasketLine[];
    
        // ...
    
        loadBasket() {
            this.basketBackendService.GetBasket().subscribe(
                response => {
                    this.lines = <BasketLine[]>response.json().basket.BasketLines;
                },
                error => console.log(error.text())
            );
        }
    }
    

    Then render it with just:

    <tr *ngFor="let line of basketService.lines">
        // ...
    </tr>