0

我正在尝试在 Angular 中构建一个 SubscriptionGuard。这个 Guard 应该检查用户是否为订阅付费。

我有一个奇怪的问题。我将展示代码并在之后进行解释。


订阅.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);
      });
  });
}

// [ ... ]

如您所见,守卫依赖userSubscriptionStatus()users.service.ts.

在第一个测试阶段,我认为.pipe(take(1))由于某种奇怪的原因无法正常工作,但经过仔细调试,我注意到问题实际上是 SubscriptionGuard 不断调用 UsersService 中的方法。

我尝试了一切,但我不知道如何解决这个问题。这就是我在控制台中得到的:

https://i.stack.imgur.com/l3p2I.png

有人可以帮忙吗?我真的不知道...

[更新 #1] 我更新了代码。现在它看起来像这样:

订阅.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);
      });
  });
}

// [...]

我的控制台现在看起来像这样......

........................................... // 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

似乎该服务仅在警卫之后才运行。我真的不知道该怎么做...

4

1 回答 1

0

从控制台日志输出中,很明显您的 userService 首先运行

method使用接受参数的函数更新了您的代码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);
        });
    }
于 2021-03-18T08:07:40.903 回答