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

如何复用Angular Material Dialog并按点击按钮调用不同认证方法

问题与解决方案:Angular Material Dialog复用实现不同API调用

背景

基于PEAN栈(PostgreSQL - ExpressJS - Angular - NodeJS)开发认证应用,后端有两个ExpressJS文件:edit-profile-email.js和edit-profile-username.js,前端对应edit-profile.component.ts组件。

修改资料流程:

  • 用户输入新邮箱/用户名
  • 弹出Material Dialog让用户输入密码
  • 密码验证通过后发送验证邮件
  • 用户点击邮件链接完成修改确认

此前的Nodemailer问题

修改邮箱时能正常发送验证邮件,但修改用户名时触发No recipients defined错误,即使硬编码收件邮箱也无法解决:

const options = {
  from: process.env.ETHEREAL_EMAIL,
  to: 'test@test.com', // Hardcoded
  subject: 'My app — Confirm your username profile change',
  template: 'edit-profile',
  context: {
    user: getEmail,
    link: editProfileLink,
  },
};

排查后发现是后端两个文件使用了相同的API端点/api/edit-profile-password-confirmation,但逻辑不同(改邮箱发新邮箱,改用户名发当前邮箱)导致冲突。

前端API方法拆分

之前auth.service.ts只有一个通用方法:

editProfilePasswordConfirmation(confirmationPassword?: string) {
  return this.http.post(
    this.authUrl + 'api/edit-profile-password-confirmation',
    { password: confirmationPassword },
    { withCredentials: true }
  );
}

现在拆分为两个独立方法:

editProfilePasswordConfirmationEmail(confirmationPassword?: string) {
  return this.http.post(
    this.authUrl + 'api/edit-profile-password-confirmation-email',
    { password: confirmationPassword },
    { withCredentials: true }
  );
}

editProfilePasswordConfirmationUsername(confirmationPassword?: string) {
  return this.http.post(
    this.authUrl + 'api/edit-profile-password-confirmation-username',
    { password: confirmationPassword },
    { withCredentials: true }
  );
}

核心需求

复用同一个Material Dialog输入密码,根据触发按钮调用对应API:

  • 点击「修改邮箱-保存更改」:调用editProfilePasswordConfirmationEmail
  • 点击「修改用户名-保存更改」:调用editProfilePasswordConfirmationUsername

解决方案

方案1:给Dialog传入回调函数

创建通用密码输入Dialog组件,打开时传入对应的API调用方法作为回调:

通用Dialog组件(password-confirmation-dialog.component.ts)

import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-password-confirmation-dialog',
  template: `
    <h2 mat-dialog-title>确认密码</h2>
    <mat-dialog-content>
      <mat-form-field appearance="fill">
        <mat-label>输入当前密码</mat-label>
        <input matInput type="password" [(ngModel)]="password" required>
      </mat-form-field>
    </mat-dialog-content>
    <mat-dialog-actions align="end">
      <button mat-button (click)="onCancel()">取消</button>
      <button mat-button color="primary" (click)="onConfirm()" [disabled]="!password">确认</button>
    </mat-dialog-actions>
  `,
})
export class PasswordConfirmationDialogComponent {
  password = '';

  constructor(
    public dialogRef: MatDialogRef<PasswordConfirmationDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: { confirmFn: (password: string) => Observable<any> }
  ) {}

  onCancel() {
    this.dialogRef.close();
  }

  onConfirm() {
    this.data.confirmFn(this.password).subscribe({
      next: () => {
        this.dialogRef.close(true);
        // 这里可以加成功提示,比如"验证邮件已发送"
      },
      error: (err) => {
        // 处理错误,比如密码错误提示
        console.error('密码验证失败:', err);
      }
    });
  }
}

在edit-profile.component.ts中调用Dialog

import { Component } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { AuthService } from './auth.service';
import { PasswordConfirmationDialogComponent } from './password-confirmation-dialog.component';

@Component({
  selector: 'app-edit-profile',
  template: `
    <!-- 修改邮箱区域 -->
    <div class="profile-section">
      <input [(ngModel)]="newEmail" placeholder="输入新邮箱">
      <button mat-raised-button (click)="openEmailConfirmationDialog()">保存更改</button>
    </div>

    <!-- 修改用户名区域 -->
    <div class="profile-section">
      <input [(ngModel)]="newUsername" placeholder="输入新用户名">
      <button mat-raised-button (click)="openUsernameConfirmationDialog()">保存更改</button>
    </div>
  `,
  styles: [`.profile-section { margin: 16px 0; }`]
})
export class EditProfileComponent {
  newEmail = '';
  newUsername = '';

  constructor(private dialog: MatDialog, private authService: AuthService) {}

  openEmailConfirmationDialog() {
    this.dialog.open(PasswordConfirmationDialogComponent, {
      data: {
        confirmFn: (password: string) => this.authService.editProfilePasswordConfirmationEmail(password)
      }
    });
  }

  openUsernameConfirmationDialog() {
    this.dialog.open(PasswordConfirmationDialogComponent, {
      data: {
        confirmFn: (password: string) => this.authService.editProfilePasswordConfirmationUsername(password)
      }
    });
  }
}

方案2:传入操作类型,在Dialog中调用对应方法

如果不想传递回调,也可以传入操作类型标识,在Dialog中依赖AuthService调用对应方法:

修改Dialog组件

import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AuthService } from './auth.service';

@Component({
  // 模板同方案1
})
export class PasswordConfirmationDialogComponent {
  password = '';

  constructor(
    public dialogRef: MatDialogRef<PasswordConfirmationDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: { actionType: 'email' | 'username' },
    private authService: AuthService
  ) {}

  onCancel() {
    this.dialogRef.close();
  }

  onConfirm() {
    const request$ = this.data.actionType === 'email'
      ? this.authService.editProfilePasswordConfirmationEmail(this.password)
      : this.authService.editProfilePasswordConfirmationUsername(this.password);

    request$.subscribe({
      next: () => this.dialogRef.close(true),
      error: (err) => console.error('验证失败:', err)
    });
  }
}

在父组件中调用

openEmailConfirmationDialog() {
  this.dialog.open(PasswordConfirmationDialogComponent, {
    data: { actionType: 'email' }
  });
}

openUsernameConfirmationDialog() {
  this.dialog.open(PasswordConfirmationDialogComponent, {
    data: { actionType: 'username' }
  });
}

关于组件复用的说明

完全不需要创建两个Dialog组件,Angular组件化的核心价值就是通过输入数据/回调让同一组件适配不同场景。上面两种方案都是利用Material Dialog的数据传递能力,让通用Dialog根据传入参数执行不同逻辑,完美实现复用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 07:33:20