如何复用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
相关产品推荐
相关产品推荐

