I'm trying to build a SubscriptionGuard in Angular. This Guard should check whether the user has paid or not for the subscription.
I'm having a weird issue. I'll show the code and will explain this after.
subscription.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { UsersService } from '../services/users.service';
@Injectable({
providedIn: 'root'
})
export class SubscriptionGuard implements CanActivate {
constructor(
private usersService: UsersService,
private router: Router
) { }
async canActivate(route: any, state: RouterStateSnapshot): Promise<UrlTree> {
const status = await this.usersService.userSubscriptionStatus();
let tree = this.router.parseUrl(state.url);
// User should update payment methods.
if (
status === 'past_due' ||
status === 'unpaid' ||
status === 'incomplete'
) {
tree = this.router.parseUrl('/subscription');
tree.queryParams.subscriptionReturnUrl = state.url;
}
// User should create a new subscription.
if (
status === 'canceled' ||
status === 'incomplete_expired' ||
status === null
) {
tree = this.router.parseUrl('/subscription');
tree.queryParams.subReturnUrl = state.url;
tree.queryParams.firstSub = status === null;
}
return tree;
}
}
users.service.ts
// [ ... ]
async userSubscriptionStatus(): Promise<USS_Status | null> {
const uid = (await this.getCurrentFire())?.uid;
if (!uid) return null;
return new Promise((resolve, reject) => {
this.db.colWithIds$<UserStripeSubscription>(
`users/${uid}/subscriptions`,
(ref: any) => ref.orderBy('created', 'desc')
)
.pipe(take(1))
.subscribe((subs: UserStripeSubscription[]) => {
let timestamp: number | null = null;
let status: string | null = null;
subs.forEach(sub => {
if (!sub.status) return;
if (!timestamp) {
timestamp = sub.created.seconds;
status = sub.status;
return;
}
if (timestamp <= sub.created.seconds) {
timestamp = sub.created.seconds;
status = sub.status;
return;
}
return;
});
console.log('status =>', status);
resolve(status);
});
});
}
// [ ... ]
As you can see, the guard relies on the method userSubscriptionStatus()
in users.service.ts
.
In a first testing phase I was thinking that .pipe(take(1))
wasn't working for some weird reason, but with a closer debugging I noticed that the problem was in fact that SubscriptionGuard was calling continuously the method in UsersService.
I tried everything but I don't know how to fix this. This is what I'm getting in the console:
https://i.sstatic.net/l3p2I.png
Can someone help? I really don't know...
[UPDATE #1] I updated the code. Now it's looking like this:
subscription.guard.ts
import { USS_Status } from './../models/user-stripe-subscription.model';
import { Injectable } from '@angular/core';
import { CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { UsersService } from '../services/users.service';
@Injectable({
providedIn: 'root'
})
export class SubscriptionGuard implements CanActivate {
private status: USS_Status | null | undefined = undefined;
constructor(
private usersService: UsersService,
private router: Router
) { }
canActivate(route: any, state: RouterStateSnapshot): Promise<UrlTree> {
return new Promise<UrlTree>((resolve, reject) => {
const method = () => {
let tree = this.router.parseUrl(state.url);
if (
this.status === 'past_due' ||
this.status === 'unpaid' ||
this.status === 'incomplete'
) {
tree = this.router.parseUrl('/subscription');
tree.queryParams.subscriptionReturnUrl = state.url;
}
else if (
this.status === 'canceled' ||
this.status === 'incomplete_expired' ||
this.status === null
) {
tree = this.router.parseUrl('/subscription');
tree.queryParams.subReturnUrl = state.url;
tree.queryParams.firstSub = this.status === null;
console.log('...........................................');
console.log('this.status === null', this.status === null, this.status);
console.log('this.status === canceled', this.status === 'canceled', this.status);
console.log('this.status === incomplete_expired', this.status === 'incomplete_expired', this.status);
}
resolve(tree);
};
if (this.status === undefined)
this.usersService.userSubscriptionStatus().then((status) => {
this.status = status;
method();
console.log('Guard status is =>', status);
});
else method();
});
}
}
users.service.ts
// [...]
async userSubscriptionStatus(): Promise<USS_Status | null> {
const uid = (await this.getCurrentFire())?.uid;
if (!uid) return null;
return new Promise((resolve, reject) => {
this.db.colWithIds$<UserStripeSubscription>(
`users/${uid}/subscriptions`,
(ref: any) => ref.orderBy('created', 'desc')
)
.pipe(take(1))
.subscribe((subs: UserStripeSubscription[]) => {
let timestamp: number | null = null;
let status: string | null = null;
console.log('SUBS are => ', subs);
subs.forEach(sub => {
if (!sub.status) return;
if (!timestamp) {
timestamp = sub.created.seconds;
status = sub.status;
return;
}
if (timestamp <= sub.created.seconds) {
timestamp = sub.created.seconds;
status = sub.status;
return;
}
return;
});
console.log('status =>', status);
resolve(status);
});
});
}
// [...]
My console now look like this...
........................................... // subscription.guard.ts
'this.status === null' true null // subscription.guard.ts
'this.status === canceled' false null // subscription.guard.ts
'this.status === incomplete_expired' false null // subscription.guard.ts
'Guard status is =>' null // subscription.guard.ts
[WDS] Live Reloading enabled. // ANGULAR
'SUBS are =>' (2) [{…}, {…}] // users.service.ts
'status =>' 'active' // users.service.ts
........................................... // subscription.guard.ts
'this.status === null' true null // subscription.guard.ts
'this.status === canceled' false null // subscription.guard.ts
'this.status === incomplete_expired' false null // subscription.guard.ts
It seems that the service is running only AFTER the guard. I really don't know how to do it...
From console log output it is clear your userService is running first
Updated your code with method
function accepting a parameter statusParam
canActivate(route: any, state: RouterStateSnapshot): Promise<UrlTree> {
return new Promise<UrlTree>((resolve, reject) => {
const method = (statusParam) => {
let tree = this.router.parseUrl(state.url);
if (
statusParam === 'past_due' ||
statusParam === 'unpaid' ||
tstatusParam === 'incomplete'
) {
tree = this.router.parseUrl('/subscription');
tree.queryParams.subscriptionReturnUrl = state.url;
}
else if (
statusParam === 'canceled' ||
statusParam === 'incomplete_expired' ||
statusParam === null
) {
tree = this.router.parseUrl('/subscription');
tree.queryParams.subReturnUrl = state.url;
tree.queryParams.firstSub = statusParam === null;
console.log('...........................................');
console.log('statusParam === null', statusParam === null, statusParam);
console.log('statusParam === canceled', statusParam === 'canceled', statusParam);
console.log('statusParam === incomplete_expired', statusParam === 'incomplete_expired', statusParam);
}
resolve(tree);
};
if (this.status === undefined)
this.usersService.userSubscriptionStatus().then((status) => {
// this.status = status;
console.log('Resolved Guard status is =>', status);
method(status);
console.log('Guard status is =>', status);
});
else method(this.status);
});
}