我有一个身份验证服务,如下所示:
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 上运行(如果这有影响的话)。
关于装饰器的事情是,您需要在
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!');
}
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');
}
}