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

Ionic Angular模态框关闭触发栈溢出错误求助

问题描述

我用Ionic模态框实现了一个注册新用户的页面组件,需要执行两个异步操作:一是在Firebase中注册用户,二是往数据库写入用户信息。但执行后模态框关闭,页面退回上一页,控制台抛出Uncaught RangeError: Maximum call stack size exceeded错误。单独调用其中一个Promise时功能完全正常,另外有个结构类似但仅调用单个Promise的页面也没有问题。


相关代码

register.page.html

<ion-content>
<ion-fab (click)="setOpen('isModalOpen', true)" vertical="bottom" horizontal="end" slot="fixed">
    <ion-fab-button id="user-modal">
      <ion-icon name="add"></ion-icon>
    </ion-fab-button>
  </ion-fab>

  <ion-modal trigger="user-modal">
    <ng-template>
    <ion-header>
        <ion-toolbar>
          <ion-buttons slot="start">
            <ion-button (click)="cancel()">
              <ion-icon name="arrow-back-outline"></ion-icon>
            </ion-button>
          </ion-buttons>
          <ion-title>Create User</ion-title>
        </ion-toolbar>
      </ion-header>
        <ion-content>
            <ion-item class="new-user">
                <ion-label position="floating">Email:</ion-label>
                <ion-input type="email" [(ngModel)]="email"></ion-input>
            </ion-item>
            <ion-button (click)="sendUser()">Cadastrar</ion-button>
        </ion-content>
    </ng-template>
  </ion-modal>
</ion-content>

register.page.ts

@Component({
  selector: 'register-admin',
  templateUrl: './register-admin.page.html',
  styleUrls: ['./register-admin.page.scss'],
})
export class RegisterPage implements OnInit {

  @ViewChild(IonModal) modal: IonModal;

    constructor(private userService: UserService) {}

    sendUser() {
      this.userService.createUser(user)
        .then(() => {
            this.userService.createUserInfo(user)
                .then(() => {
                    alert('User created!');
                    this.modal.dismiss(null, 'cancel');
                });
          }).catch(err => {
           if (err.message.includes('already')) {
             alert('Already Created');
           } else if (err.message.includes('Password')) {
             alert('Password have to be more than 6 digits.');
           } else {
             alert('Try again Later');
           }
         });
      
}

user.service.ts

createUser(user) {
    return this.fireAuth.createUserWithEmailAndPassword(user.email, user.pass)      
 }

 resetPassword(email: string) {
    return this.fireAuth.sendPasswordResetEmail(email);
 }

 createUserInfo(user: any) {
    return this.db.list('/user').push({
        name: user.name,
        email: user.email,
        admin: user.admin === 'true' ? true : false,
        type: user.type
    });
}

控制台错误信息

Uncaught RangeError: Maximum call stack size exceeded.
    at focusElement (helpers-eed79a2b.js:158:1)
    at focusFirstDescendant (overlays-4fbe89bd.js:4095:17)
    at trapShadowFocus (overlays-4fbe89bd.js:4235:7)
    at trapKeyboardFocus (overlays-4fbe89bd.js:4252:5)
    at HTMLDocument.<anonymous> (overlays-4fbe89bd.js:4261:41)
    at ZoneDelegate.invokeTask (zone.js:434:1)
    at Zone.runTask (zone.js:205:1)
    at ZoneTask.invokeTask [as invoke] (zone.js:516:1)
    at invokeTask (zone.js:1656:1)
    at HTMLDocument.globalZoneAwareCaptureCallback (zone.js:1725:1)

注:错误也可能来自focusLastDescendant。我排查过模态框焦点问题,但单Promise调用时一切正常,不确定是否受项目其他部分影响。


解决方案

这个问题大概率是模态框关闭时的焦点循环触发了递归调用,结合双Promise连续执行的场景,异步操作完成后立即关闭模态框时,Ionic的焦点陷阱逻辑出现了无限递归。可以尝试以下修复方案:

1. 给模态框关闭操作加短暂延迟

让异步操作完全结束后再处理模态框关闭,避免时序冲突:

sendUser() {
  this.userService.createUser(user)
    .then(() => {
        this.userService.createUserInfo(user)
            .then(() => {
                alert('User created!');
                // 加100ms延迟,等待异步上下文清理完成
                setTimeout(() => {
                  this.modal.dismiss(null, 'cancel');
                }, 100);
            });
      }).catch(err => {
       if (err.message.includes('already')) {
         alert('Already Created');
       } else if (err.message.includes('Password')) {
         alert('Password have to be more than 6 digits.');
       } else {
         alert('Try again Later');
       }
     });
}

2. 改用async/await优化Promise嵌套

嵌套Promise可能导致上下文处理异常,用async/await让代码线性化,避免异步嵌套带来的时序问题:

async sendUser() {
  try {
    await this.userService.createUser(user);
    await this.userService.createUserInfo(user);
    alert('User created!');
    setTimeout(() => {
      this.modal.dismiss(null, 'cancel');
    }, 100);
  } catch (err) {
    if (err.message.includes('already')) {
      alert('Already Created');
    } else if (err.message.includes('Password')) {
      alert('Password have to be more than 6 digits.');
    } else {
      alert('Try again Later');
    }
  }
}

3. 移除重复的模态框触发逻辑

你同时使用了(click)事件和trigger属性绑定模态框打开逻辑,可能导致重复的状态变更冲突。可以移除其中一种方式,比如保留trigger绑定:

<ion-fab vertical="bottom" horizontal="end" slot="fixed">
    <ion-fab-button id="user-modal">
      <ion-icon name="add"></ion-icon>
    </ion-fab-button>
  </ion-fab>

  <ion-modal trigger="user-modal">
    <!-- 其余内容不变 -->
  </ion-modal>

4. 手动指定关闭后的焦点元素

在关闭模态框前,手动将焦点移到外部元素,打断焦点陷阱的递归循环:

sendUser() {
  this.userService.createUser(user)
    .then(() => {
        this.userService.createUserInfo(user)
            .then(() => {
                alert('User created!');
                // 关闭前将焦点移到fab按钮
                document.getElementById('user-modal').focus();
                this.modal.dismiss(null, 'cancel');
            });
      }).catch(err => {
       // 错误处理不变
     });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 05:07:02