Angular 16中如何用装饰器监听Observable触发执行方法?
Angular 16 实现@DoOnLogin/@DoOnLogout装饰器方案
核心思路
利用Reflect元数据标记需要绑定的方法,结合Angular的依赖注入与生命周期钩子,在组件/服务初始化时自动获取AuthService并订阅对应Observable,触发标记方法;销毁时自动取消订阅避免内存泄漏。
步骤1:定义装饰器工厂
通过Reflect.metadata将需要监听登录/登出的方法名存入类的元数据中:
import 'reflect-metadata'; // 元数据唯一标识键 const DO_ON_LOGIN_KEY = Symbol('DoOnLogin'); const DO_ON_LOGOUT_KEY = Symbol('DoOnLogout'); // 登录事件装饰器 export function DoOnLogin() { return function (target: any, propertyKey: string) { // 获取已标记的方法列表,无则初始化空数组 const existingMethods = Reflect.getMetadata(DO_ON_LOGIN_KEY, target.constructor) || []; Reflect.defineMetadata(DO_ON_LOGIN_KEY, [...existingMethods, propertyKey], target.constructor); }; } // 登出事件装饰器 export function DoOnLogout() { return function (target: any, propertyKey: string) { const existingMethods = Reflect.getMetadata(DO_ON_LOGOUT_KEY, target.constructor) || []; Reflect.defineMetadata(DO_ON_LOGOUT_KEY, [...existingMethods, propertyKey], target.constructor); }; }
步骤2:创建订阅处理基类
实现一个抽象基类,封装OnInit和OnDestroy钩子的订阅逻辑,组件/服务只需继承该类即可自动绑定事件:
import { Inject, OnInit, OnDestroy } from '@angular/core'; import { AuthService } from './auth.service'; import { Subscription } from 'rxjs'; export abstract class AuthListenerBase implements OnInit, OnDestroy { private subscriptions: Subscription[] = []; constructor(@Inject(AuthService) private authService: AuthService) {} ngOnInit(): void { const constructor = this.constructor as any; // 绑定登录触发方法 const loginMethods = Reflect.getMetadata(DO_ON_LOGIN_KEY, constructor) || []; if (loginMethods.length) { const sub = this.authService.login$.subscribe(() => { loginMethods.forEach((methodName: string) => { this[methodName](); }); }); this.subscriptions.push(sub); } // 绑定登出触发方法 const logoutMethods = Reflect.getMetadata(DO_ON_LOGOUT_KEY, constructor) || []; if (logoutMethods.length) { const sub = this.authService.logout$.subscribe(() => { logoutMethods.forEach((methodName: string) => { this[methodName](); }); }); this.subscriptions.push(sub); } } ngOnDestroy(): void { // 组件销毁时清空所有订阅,防止内存泄漏 this.subscriptions.forEach(sub => sub.unsubscribe()); } }
步骤3:在组件/服务中使用装饰器
组件或服务继承AuthListenerBase,给需要触发的方法添加对应装饰器即可:
import { Component } from '@angular/core'; import { AuthListenerBase, DoOnLogin, DoOnLogout } from './auth-listener.base'; @Component({ selector: 'app-profile', template: '<p>Profile Component</p>' }) export class ProfileComponent extends AuthListenerBase { @DoOnLogin() handleLogin() { console.log('用户已登录,执行Profile组件登录逻辑'); // 例如加载用户信息、更新页面状态等 } @DoOnLogout() handleLogout() { console.log('用户已登出,执行Profile组件登出逻辑'); // 例如清空用户数据、重置页面状态等 } }
替代方案:无基类的DI扩展实现
若不想让组件继承基类,可创建一个独立服务,在组件构造器中手动调用绑定逻辑:
import { Injectable } from '@angular/core'; import { AuthService } from './auth.service'; import { Subscription } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthListenerService { bindAuthEvents(target: any): Subscription[] { const subscriptions: Subscription[] = []; const constructor = target.constructor as any; const loginMethods = Reflect.getMetadata(DO_ON_LOGIN_KEY, constructor) || []; if (loginMethods.length) { const sub = this.authService.login$.subscribe(() => { loginMethods.forEach((method: string) => target[method]()); }); subscriptions.push(sub); } const logoutMethods = Reflect.getMetadata(DO_ON_LOGOUT_KEY, constructor) || []; if (logoutMethods.length) { const sub = this.authService.logout$.subscribe(() => { logoutMethods.forEach((method: string) => target[method]()); }); subscriptions.push(sub); } return subscriptions; } }
在组件中使用:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { DoOnLogin, DoOnLogout } from './auth.decorators'; import { AuthListenerService } from './auth-listener.service'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-dashboard', template: '<p>Dashboard Component</p>' }) export class DashboardComponent implements OnInit, OnDestroy { private subscriptions: Subscription[] = []; constructor(private authListenerService: AuthListenerService) {} ngOnInit(): void { this.subscriptions = this.authListenerService.bindAuthEvents(this); } ngOnDestroy(): void { this.subscriptions.forEach(sub => sub.unsubscribe()); } @DoOnLogin() onLogin() { console.log('Dashboard:用户登录,加载仪表盘数据'); } @DoOnLogout() onLogout() { console.log('Dashboard:用户登出,清空仪表盘缓存'); } }
扩展说明
- 传递Observable参数:若需要接收
login$发射的用户数据,只需修改订阅逻辑,将参数传入方法:this.authService.login$.subscribe((userInfo) => { loginMethods.forEach((methodName: string) => { this[methodName](userInfo); }); }); - AuthService注入校验:确保
AuthService已通过providedIn: 'root'或模块声明正确提供,避免注入失败。
内容的提问来源于stack exchange,提问作者Jason
相关产品推荐
相关产品推荐

