I'm having trouble finding out why my ShopManagerService
keeps returning undefined
when the data is accessed by a pipe. Here is the ShopManagerService
:
@Injectable({ providedIn: 'root' })
export class ShopManagerService {
private shopPreferences: ShopPreferences = null;
setPreferences(shopPreferences: ShopPreferences) {
this.shopPreferences = shopPreferences;
}
getDateFormat(){
if(this.shopPreferences == null || this.shopPreferences.time_format.date == null) return;
return this.shopPreferences.time_format.date;
}
...
// more data getters
}
The previous field shopPreferences
is set in the service ApiManagerService
, like so:
@Injectable({ providedIn: 'root' })
export class ApiManagerService {
private token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MjcwMjA3MiwidGltZXN0YW1wIjoiMjAyMS0wNC0wOSAwOToxNToxNS4';
private webzine_id: string = "2702072";
constructor(private http: HttpClient, private shopManager: ShopManagerService) {
// We want to keep shop preferences updated throughout execution
timer(0, 5000).pipe(
filter(() => this.webzine_id && this.webzine_id !== ""),
switchMap(() => this.fetchShopPreferences().pipe(first()))
)
.subscribe(preferences => this.shopManager.setPreferences(preferences));
}
fetchShopPreferences() {
const url = "https://commerce.ww-api.com/commerceapi/v1/front/front_url/" + this.webzine_id + "/";
return this.http
.get<ShopPreferences>(url, {
headers: new HttpHeaders({
token: this.token,
}),
});
}
...
// more api requests
}
The component looks like this:
@Component({
selector: 'app-order-details',
templateUrl: './order-details.component.html',
styleUrls: ['./order-details.component.css']
})
export class OrderDetailsComponent {
constructor(private apiManager: ApiManagerService, private shopPreferencesService: ShopManagerService){ }
closeDetails(){
/* implement close details functionality */
}
}
The pipe is called in the .html like so:
<div id="details-container">
<div id="details-header">
<div class="header-text">
<!-- hard coded for now -->
<label>#3172</label>
<label>{{"01/18/2021" | addOrderDateHourFormat:"date"}}</label>
</div>
<div class="close-button" (click)="closeDetails()">
<img src="../../../assets/my-orders/close-icon.svg">
</div>
</div>
<app-order-properties></app-order-properties>
<app-order-channel></app-order-channel>
</div>
Inside the pipe, the code looks like this:
@Pipe({
name: 'addOrderDateHourFormat',
})
export class OrderDateFormatPipe implements PipeTransform {
constructor(private formatService: FormatManagerService){}
transform(value: string, type: string, trigger: number) {
if(type === "hour"){
return this.formatService.formatTime(value);
}
else if(type === "date"){
return this.formatService.formatDate(value);
}
else if(type === "date+hour"){
return this.formatService.formatDateAndTime(value)
}
}
}
Finally, this is the look inside the FormatManagerService
:
@Injectable({ providedIn: 'root' })
export class FormatManagerService {
constructor(private datePipe: DatePipe, private shopPrefencesService: ShopManagerService) {}
formatDate(date: string){
let dateFormat = this.shopPrefencesService.getDateFormat(); // returning undefined
if(dateFormat === "GBCOMMERCE_DATEFORMAT_1"){
return this.datePipe.transform(date, 'EEEE, LLLL d yyyy');
}
else if(dateFormat === "GBCOMMERCE_DATEFORMAT_2"){
return this.datePipe.transform(date, 'LLLL d yyyy');
}
else if(dateFormat === "GBCOMMERCE_DATEFORMAT_3"){
return this.datePipe.transform(date, 'MM/d/yyyy');
}
else if(dateFormat === "GBCOMMERCE_DATEFORMAT_4"){
return this.datePipe.transform(date, 'MM.d.yyyy');
}
else if(dateFormat === "GBCOMMERCE_DATEFORMAT_5"){
return this.datePipe.transform(date, 'MM-d-yyyy');
}
}
...
// more format methods
}
How come the call this.shopPrefencesService.getDateFormat()
inside the formatDate()
method keeps returning undefined
? What am I missing here?
Thanks in advance for the help.
EDIT: added .html and pipe code.
I'd say this happens because of asynchrony. I'd reckon you call the date pipe long before the REST-call returns the value. And the pipe gets only fired once.
My suggestion is the following.
Make your ShopPreferences an Observable of Type BehaviorSubject and wait for the value to be set in the OrderDetailsComponent. Only if the value is present render the div.
Your ShopManagerService
@Injectable({ providedIn: 'root' })
export class ShopManagerService {
private shopPreferences: BehaviorSubject<ShopPreferences> = new BehaviorSubject<ShopPreferences>(null);
setPreferences(shopPreferences: ShopPreferences): void {
this.shopPreferences.next(shopPreferences);
}
getPreferencesAsObservable(): Observable<ShopPreferences> {
return this.shopPreferences.asObservable();
}
getDateFormat(): Date{
if (this.shopPreferences.getValue() === null || this.shopPreferences.getValue().time_format.date === null) {
return;
}
return this.shopPreferences.getValue().time_format.date;
}
// more data getters
}
Your OrderDetailsComponent
@Component({
selector: 'app-order-details',
templateUrl: './order-details.component.html',
styleUrls: ['./order-details.component.css']
})
export class OrderDetailsComponent {
shopPreferencesArePresent = false;
constructor(private apiManager: ApiManagerService, private shopPreferencesService: ShopManagerService){
this.shopPreferencesService.getPreferencesAsObservable().subscribe( value => {
if (value) {
this.shopPreferencesArePresent = true;
}
});
}
closeDetails(){
/* implement close details functionality */
}
}
And the HTML of your OrderDetailsComponent
<div id="details-container">
<div id="details-header">
<div *ngIf="shopPreferencesArePresent" class="header-text">
<!-- hard coded for now -->
<label>#3172</label>
<label>{{"01/18/2021" | addOrderDateHourFormat:"date"}}</label>
</div>
<div class="close-button" (click)="closeDetails()">
<img src="../../../assets/my-orders/close-icon.svg">
</div>
</div>
<app-order-properties></app-order-properties>
<app-order-channel></app-order-channel>
</div>