I am trying to fetch the customerID from the angular class "UserFacade" and then using the retrieved customerID to retrieve the points for this customer using a http client GET call to a REST endpoint.
The approach I am using is creating 2 variables user$ and point$ which must be initialized
correctly which will guarantee me that the points are received correctly. Is there a
better way implement this logic for example not using these two variables user$ and point$ and directly retrieve them via let's say function getPts():number
?
The HTML:
<div >
<ng-container *ngIf="user$ | async as user">
<ng-container *ngIf="point$ | async as point">
<p> {{getCustomerPoints()}} </p>
</ng-container>
</ng-container>
</div>
This is where main logic resides:
import { Component, OnInit } from '@angular/core';
import { AuthService, CmsComponent, User, UserIdService, } from '@spartacus/core';
import { CmsComponentData } from '@spartacus/storefront';
import { UserAccountFacade } from '@spartacus/user/account/root';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { switchMap } from 'rxjs/operators';
import { PointsRetrievalService } from '../points-retrieval.service';
import { FOOTER_VALUE, HEADER_VALUE } from './component-labels';
export interface CustomerPoints {
points: number;
}
export interface CmsSlpCustomUserPointsComponent4 extends CmsComponent {
title: string;
headerText: string;
footerText: string;
}
@Component({
selector: 'app-userpoints',
templateUrl: './userpoints.component.html',
styleUrls: ['./userpoints.component.scss']
})
export class UserpointsComponent implements OnInit {
public point$!: Observable<CustomerPoints>;
user$: Observable<User | undefined> | undefined;
data$: Observable<CmsSlpCustomUserPointsComponent4> = this.component.data$;
private customerPointsRetrieved = 0
constructor(
public component: CmsComponentData<CmsSlpCustomUserPointsComponent4>, private pointsRetrievalService: PointsRetrievalService,
protected auth: AuthService,
protected userAccount: UserAccountFacade,
protected userIdService: UserIdService,
) {}
ngOnInit() : void {
this.user$ = this.auth.isUserLoggedIn().pipe(
switchMap(isUserLoggedIn => {
if (isUserLoggedIn) {
this.userAccount.get().subscribe(
(user) =>
{
if(user && user?.customerId)
{
this.point$ = this.pointsRetrievalService.getPointsForCustomerId(user?.customerId)
this.point$.subscribe(
(userPoints) =>
{
console.log("New points:" userPoints.points)
console.log(userPoints.points)
this.customerPointsRetrieved = userPoints.points
}
)
}
}
)
return this.userAccount.get();
} else {
return of(undefined);
}
})
);
}
getCustomerPoints()
{
return this.customerPointsRetrieved;
}
}
Service retrieving the points via http:
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { UserAccountFacade } from '@spartacus/user/account/root';
import { BASE_SITE_ID, CUSTOMER_ID, ELECTRONICS_SITE, HTTP_JSON_HEADERS, REST_URL } from './userpoints/rest-connection-constants';
import { CustomerPoints } from './userpoints/customer-points-interfaces';
@Injectable({
providedIn: 'root'
})
export class PointsRetrievalService{
constructor(private http: HttpClient,
protected userAccount: UserAccountFacade) {
}
getPointsForCustomerId(userID: string): Observable<CustomerPoints>
{
{
console.log("User id received:" userID)
const url = `${REST_URL}${userID}`;
//const url = this.mainRestUrl this.userIdUsed this.fieldParams;
console.log(url);
let params = new HttpParams().set(CUSTOMER_ID, userID).set(BASE_SITE_ID, ELECTRONICS_SITE);
const options = { params: params, headers: HTTP_JSON_HEADERS }
return this.http.get<CustomerPoints>(url, options);
}
}
}
CodePudding user response:
I don't see the point of user$
and point$
streams.
Also, the logic in ngOnInit seems a bit too complicated (usually nested subscribes are not a good pattern).
I've made some changes but I can't really test anything, so if you have issues let me know. Maybe you can adapt it to work.
import {
Observable,
of,
Subject,
} from 'rxjs';
import {
switchMap,
takeUntil,
tap,
} from 'rxjs/operators';
import {
Component,
OnDestroy,
OnInit,
} from '@angular/core';
import {
AuthService,
CmsComponent,
UserIdService,
} from '@spartacus/core';
import { CmsComponentData } from '@spartacus/storefront';
import { UserAccountFacade } from '@spartacus/user/account/root';
import { PointsRetrievalService } from '../points-retrieval.service';
export interface CustomerPoints {
points: number;
}
export interface CmsSlpCustomUserPointsComponent4 extends CmsComponent {
title: string;
headerText: string;
footerText: string;
}
const customerPointsKey = 'CUSTOMER_POINTS_KEY';
@Component({
selector: 'app-userpoints',
templateUrl: './userpoints.component.html',
styleUrls: ['./userpoints.component.scss']
})
export class UserpointsComponent implements OnInit, OnDestroy {
data$: Observable<CmsSlpCustomUserPointsComponent4> = this.component.data$;
private customerPointsRetrieved = localStorage.getItem(customerPointsKey) ?? 0;
private _destroyed$ = new Subject<void>();
constructor(
public component: CmsComponentData<CmsSlpCustomUserPointsComponent4>, private pointsRetrievalService: PointsRetrievalService,
protected auth: AuthService,
protected userAccount: UserAccountFacade,
protected userIdService: UserIdService,
) { }
ngOnInit(): void {
this.auth.isUserLoggedIn().pipe(
switchMap(isUserLoggedIn => {
if (isUserLoggedIn) {
return this.userAccount.get().pipe(
switchMap(user => {
if (user && user?.customerId) {
return this.pointsRetrievalService.getPointsForCustomerId(user.customerId).pipe(
tap(userPoints => {
console.log("New points:" userPoints.points)
console.log(userPoints.points)
this.customerPointsRetrieved = userPoints.points;
localStorage.setItem(customerPointsKey, userPoints.points);
})
)
}
return of(undefined);
})
)
}
return of(undefined);
}),
takeUntil(this._destroyed$),
).subscribe();
}
ngOnDestroy(): void {
this._destroyed$.next();
}
getCustomerPoints() {
return this.customerPointsRetrieved;
}
}
<div >
<p>{{getCustomerPoints()}}</p>
</div>
CodePudding user response:
You've missed the point of pipe
, you can pass any number of arguments, each is a step in the pipeline. The result of a previous step is passed to the next step. You don't subscribe inside the pipeline, you create the pipeline and then subscribe.
So an observable that returns the user points look like this:
userPoints$: Observable<Points[] | undefined> = this.auth
.isUserLoggedIn()
.pipe(
switchMap((isUserLoggedIn) =>
isUserLoggedIn ? this.userAccount.get() : of(undefined)
),
switchMap((user: User | undefined) => {
if (!user || !user.customerId) return of(undefined);
return this.pointsRetrievalService.getPointsForCustomerId(
user.customerId
);
}),
map((userPoints: UserPoints | undefined) => userPoints?.points)
);
Note: switchMap
and mergeMap
will pass the result of the observable to the next step, not the observable itself. The result of the final step is what ultimately gets returned.
You can add steps by calling pipe on another observable as well. So if you want a separate observable to return just the user you can do this:
user$: Observable<User | undefined> = this.auth
.isUserLoggedIn()
.pipe(
switchMap((isUserLoggedIn) =>
isUserLoggedIn ? this.userAccount.get() : of(undefined)
)
);
userPoints$: Observable<Points[] | undefined> = this.user$.pipe(
switchMap((user: User | undefined) => {
if (!user || !user.customerId) return of(undefined);
return this.pointsRetrievalService.getPointsForCustomerId(
user.customerId
);
}),
map((userPoints: UserPoints | undefined) => userPoints?.points)
);
You can initialize these outside of any function, nothing will be executed until you subscribe.
You can subscribe to the new observables manually, or you can use the async
pipe in html to automatically subscribe / unsubscribe and inject the data into your view.
ngOnInit(): void {
// Requests just user
this.user$.subscribe((user) => {
console.log(user);
});
// Requests user and then user points
this.userPoints$.subscribe((points) => {
console.log('New points:' points);
this.customerPointsRetrieved = points;
});
}
OR
<h1>User</h1>
<pre>{{ user$ | async | json }}</pre>
<h1>User Points</h1>
<pre>{{ userPoints$ | async | json }}</pre>
Notice how |
is also a pipe
operator, ie. the result of the previous step is passed to the next.
Just keep in mind every instance of userPoints$ | async
in html will be a separate request, you can create a template variable if it appears more than once in the view.
Also worth mentioning the async
await
variant to request the data once instead of subscribing.
async ngOnInit(): void {
const user = await firstValueFrom(this.user$);
const userPoints = await firstValueFrom(this.userPoints$);
console.log(user, userPoints);
}