Angular/Typescript 装饰器在 Observable Emit 上执行方法

问题描述 投票:0回答:1

我有一个身份验证服务,如下所示:

export class AuthService {

  private login$$ : BehaviorSubject<void> = new BehaviorSubject<void>(null);
  private logout$$ : BehaviorSubject<void> = new BehaviorSubject<void>(null);

  constructor() {
    // constructor stuff
  }

  get login$() : Observable<void> {
    return login$$.asObservable();
  }

  get logout$() : Observable<void> {
    return logout$$.asObservable();
  }

  doLoginStuff() {
    // various logic, API calls, etc

    // this is probably in a tap() inside some observable's pipe()
    this.login$$.next();
  }

  doLogoutStuff() {
    // various logic, API calls, etc
    // this is probably in a tap() inside some observable's pipe()
    this.logout$$.next();
  }
}

现在,要让组件/服务“监听”login$/logout$,他们必须手动订阅来自 AuthService 的可观察对象:

export class AnotherService {

    constructor(private authService: AuthService) {

      this.authService.login$
        pipe(
           // various operations
        )
        .subscribe()

      // similar block for logout$
    }
}

我希望通过装饰器消除样板。像这样的东西:

    @DoOnLogin()
    onLogin() : void {
      // various logic
    }

    @DoOnLogout()
    onLogout() : void {
      // various logic
    }

棘手的地方似乎是访问/订阅装饰器内的可观察对象。 Angular 和 Typescript 装饰器似乎并不是最好的朋友。

有人做过类似的事情吗?

该应用程序当前在 Angular 16 上运行(如果这有影响的话)。

angular typescript
1个回答
0
投票

关于装饰器的事情是,您需要在

ngOnInit
上调用该函数才能开始工作!但这是一个好主意,因为它减少了代码重复!

重要:

该解决方案有效,但该方法将在

ngOnInit()
上被调用,因此我们需要避免在这种情况下执行!

装饰器看起来像下面这样!

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export function DoOnLogin() {
  return (target: Object, propertyKey: string, descriptor: any) => {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
      if (!this.authService || !this.destroyRef) {
        console.log(
          'authService and destroyRef are mandatory for using this decorator!'
        );
      }
      this.authService.login$
        .pipe(takeUntilDestroyed(this.destroyRef))
        .subscribe((data: any) => {
          if (data) {
            original.apply(this, args);
          }
        });
    };
    return descriptor;
  };
}

export function DoOnLogout() {
  return (target: Object, propertyKey: string, descriptor: any) => {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
      if (!this.authService || !this.destroyRef) {
        console.log(
          'authService and destroyRef are mandatory for using this decorator!'
        );
      }
      this.authService.logout$
        .pipe(takeUntilDestroyed(this.destroyRef))
        .subscribe((data: any) => {
          if (data) {
            original.apply(this, args);
          }
        });
    };
    return descriptor;
  };
}

我们可以访问 this 变量,然后我们订阅 observable,然后当组件或服务被销毁时,我使用

takeUntilDestroyed
取消订阅!

这个装饰器有依赖关系,所以你需要确保无论你在哪里使用装饰器,

authService
destroyRef
(取消订阅所需的)都被导入,当你没有找到依赖关系时,你可以简单地使用
console.error()
通过这样做

if(!this.authService || !this.destroyRef) {
    console.log('authService and destroyRef are mandatory for using this decorator!');
}

完整代码:

TS:

import { Component, DestroyRef, inject } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { AuthService } from './app/auth.service';
import { DoOnLogin, DoOnLogout } from './app/decorators';

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <h1>Hello from {{ name }}!</h1>
    <a target="_blank" href="https://angular.dev/overview">
      Learn more about Angular
    </a>
    <button (click)="login()"> Do Login!</button>
    <button (click)="logout()"> Do Logout!</button>
  `,
})
export class App {
  name = 'Angular';
  authService = inject(AuthService);
  destroyRef = inject(DestroyRef);

  ngOnInit() {
    // required to initiate the subscribe!
    this.actionOnLogin();
    this.actionOnLogout();
  }

  @DoOnLogin()
  actionOnLogin() {
    console.log('performing login actions!');
  }

  @DoOnLogout()
  actionOnLogout() {
    console.log('performing logout actions!');
  }

  login() {
    this.authService.doLoginStuff();
  }

  logout() {
    this.authService.doLogoutStuff();
  }
}

bootstrapApplication(App);

服务:

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private login$$: BehaviorSubject<string> = new BehaviorSubject<string>('');
  private logout$$: BehaviorSubject<string> = new BehaviorSubject<string>('');

  constructor() {
    // constructor stuff
  }

  get login$(): Observable<string> {
    return this.login$$.asObservable();
  }

  get logout$(): Observable<string> {
    return this.logout$$.asObservable();
  }

  doLoginStuff() {
    // various logic, API calls, etc

    // this is probably in a tap() inside some observable's pipe()
    this.login$$.next('login');
  }

  doLogoutStuff() {
    // various logic, API calls, etc
    // this is probably in a tap() inside some observable's pipe()
    this.logout$$.next('logout');
  }
}

Stackblitz 演示

© www.soinside.com 2019 - 2024. All rights reserved.