Angular 4中Bootstrap模态框首次点击需双击问题解决方案
我完全懂你这个困扰——首次点购物车图标得双击才弹出模态框,后面又正常,这其实是异步数据加载和Bootstrap模态框初始化时机不匹配搞出来的问题,咱们一步步捋清楚解决它:
问题根源
第一次点击图标时,get_My_Cart会发起异步请求拉购物车数据,但此时mycart还是空的,*ngIf="mycart && mycart.length"直接把整个模态框的DOM元素从页面里移除了。而Bootstrap的data-toggle="modal"和data-target="#exampleModal"是必须依赖DOM元素存在才能生效的,所以第一次点击只是触发了数据请求,根本没触发到模态框。等第二次点击时,数据已经加载完成,模态框DOM渲染出来了,Bootstrap能找到目标元素,自然就正常打开了。
解决方案
这里给你几个可行的方案,按需选就行:
方案1:手动控制模态框显示(最推荐)
去掉图标上的data-toggle和data-target属性,改成在数据加载完成后手动调用Bootstrap模态框的show()方法,精准把控显示时机:
修改图标代码:
<i class="fas fa-cart-plus fa-flip-horizontal text-primary" (click)="get_My_Cart($event)" style="font-size:25px;cursor: pointer;"></i>
修改组件代码:
先通过ViewChild获取模态框元素,再在数据返回后手动触发打开:
import { ViewChild, ElementRef } from '@angular/core'; // 组件类内添加 @ViewChild('exampleModal') exampleModal!: ElementRef; get_My_Cart($event) { this.publicIp.v4().then(ip => { this.CartdataService.get_My_Cart_Products(ip).subscribe( data => { this.mycart = data['Table'] this.my_Cart_Total_Amount = data['Table1'][0]['TOTAL_AMOUNT']; // 数据加载完成后判断是否打开模态框 if(this.mycart && this.mycart.length > 0){ const modalElement = this.exampleModal.nativeElement; const modal = new (window as any).bootstrap.Modal(modalElement); modal.show(); } else { // 购物车为空时的提示,比如弹个toast或者alert alert('您的购物车是空的哦~'); } } ); }); }
修改模态框代码:
给模态框加模板引用变量#exampleModal,把*ngIf移到模态框内部的内容区域,让模态框根元素始终存在:
<div class="modal fade modalstyle" #exampleModal id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header headerHeight text-white " style="background:rgb(0, 0, 0);font-weight:bold"> <h6 class="modal-title" id="exampleModalLabel">My Cart Items</h6> <button #closeModalBtn type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <!-- 只控制内容显示,模态框根元素保留 --> <div *ngIf="mycart && mycart.length" class="modal-body"> <!-- 原有的商品列表、总计等内容 --> <div *ngFor="let products of mycart;let i =index;"> <div class="container col-sm-12"> <div class="row"> <div class="col-sm-4 paddingforimage"> <img [src]="products['ITEM_IMAGE_PATH']"> </div> <div class="text-info col-sm-6"> <span> <h6>{{products?.TOTAL_QTY}} × {{products?.ITEM_PRICE}} ₹</h6> </span> <span> <h6>{{products?.ITEM_DESC}}</h6> </span> <span> <h6>{{products?.TOTAL_AMT}} ₹</h6> </span> </div> <div class="col-sm-1 text-right"> <button type="button" class="close closebtn" aria-label="Close" (click)="detele_My_Cart(products?.ITEM_CODE)"> <span aria-hidden="true" (click)="detele_My_Cart(products?.ITEM_CODE)">×</span> </button> </div> </div> </div> <hr> </div> <div class=" container row col-sm-12"> <div class="col-sm-6"> <strong>SHIPPING</strong> </div> <div class="col-sm-6 text-right">0 ₹</div> <hr> <div class="col-sm-6"> <strong>TOTAL</strong> </div> <div class="col-sm-6 text-right">{{my_Cart_Total_Amount}} ₹</div> </div> <br> <div class="container row col-sm-12" id="wrapper"> <button type="button" class="btn btn-success buttonSize" data-dismiss="modal" routerLink="/cartsummary"> <strong>CHECK OUT</strong> </button> </div> </div> <!-- 购物车为空时的提示内容 --> <div *ngIf="!mycart || mycart.length === 0" class="modal-body text-center py-4"> <p>您的购物车还没有商品~</p> <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button> </div> </div> </div> </div>
方案2:提前加载购物车数据
在组件初始化时(比如ngOnInit)就调用get_My_Cart方法预加载数据,这样页面加载完mycart已经有值了,首次点击时模态框DOM已经存在,Bootstrap的data属性就能正常触发:
ngOnInit(): void { this.get_My_Cart(null); // 根据方法参数调整,比如允许传入null }
不过这个方法的缺点是,如果用户长时间不点购物车,数据可能过期,需要加刷新逻辑;而且一开始购物车为空的话,模态框会被渲染但内容空,得处理空状态显示。
方案3:改用Angular Bootstrap组件(如果项目允许)
如果你的项目能引入ng-bootstrap(Angular官方适配的Bootstrap组件库),它能完美配合Angular的数据绑定,避免原生Bootstrap的DOM依赖问题,模态框显示可以直接通过[(ngbModalOpen)]或者服务控制,从根源上解决首次点击无效的问题。
总结
方案1是最灵活的,既能精准控制模态框显示时机,又能优雅处理购物车为空的情况,推荐优先尝试。核心思路就是别依赖Bootstrap的自动触发,而是等数据准备好后手动打开模态框,同时保持模态框根元素始终存在,只控制内部内容的显示与否。
内容的提问来源于stack exchange,提问作者Nikson

