You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:用户登出,清空仪表盘缓存');
  }
}

扩展说明

  1. 传递Observable参数:若需要接收login$发射的用户数据,只需修改订阅逻辑,将参数传入方法:
    this.authService.login$.subscribe((userInfo) => {
      loginMethods.forEach((methodName: string) => {
        this[methodName](userInfo);
      });
    });
    
  2. AuthService注入校验:确保AuthService已通过providedIn: 'root'或模块声明正确提供,避免注入失败。

内容的提问来源于stack exchange,提问作者Jason

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 21:50:14