65.9K
CodeProject 正在变化。 阅读更多。
Home

AngularJS 1.6.x 与 Bootstrap 模态对话框交互

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2018年2月7日

MIT

11分钟阅读

viewsIcon

46626

downloadIcon

598

在本教程中,我将讨论一些可以在不与 ui-bootstrap 集成的情况下使用模态对话框与 AngularJS 配合使用的技术。

引言

两年前,我遇到了这个问题。我需要在 AngularJS 应用程序中打开一个 Bootstrap 模态对话框。不知何故,我无法让 ui-bootstrap 工作。ui-bootstrap 是一个第三方组件,可以与 AngularJS 集成以支持任何与 Bootstrap 相关的行为。有时使用它非常痛苦。经过一些研究,我意识到,无需使用 ui-bootstrap,就可以很容易地为 Bootstrap 模态对话框添加动态行为。

简单总结一下,为了演示我将要涵盖的功能,演示源代码包中捆绑了三个不同的前端应用程序示例。第一个将展示打开模态对话框的方法。第二个在前一个的基础上增加了一个功能,它将展示如何在模态对话框关闭时清除输入字段。最后一个示例将展示如何将输入值从调用控制器传递到模态对话框。

基本架构

示例应用程序使用 Bootstrap 3.3.7 进行 UI 标记。主页面有一个 navbar,它没有任何功能。在 navbar 下面,有一个可以按下的按钮,该按钮会显示模态对话框。该应用程序有两个控制器,一个是主应用程序的。另一个是模态对话框的控制器。模态对话框本身是通过 HTML 文件分隔的。它通过 ngInclude 导入到主页面。第一个难点是如何通过 AngularJS 触发模态对话框显示。

在继续探讨解决第一个问题的方案之前,让我们先看一下主页面。主页面的 HTML 代码在一个名为 _app1.html_ 的文件中,如下所示:

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Angular BootStrap Modal Example 1</title>
      <link href="./assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
      <link href="./assets/app/css/app.css" rel="stylesheet">
         </head>
   <body>
   <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" 
           data-toggle="collapse" data-target="#navbar" 
           aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">Angular Bootstrap Modal</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
          <ul class="nav navbar-nav">
            <li><a href="#">Home</a></li>
          </ul>
        </div>
      </div>
    </nav>
      <div class="container app-topmargin" 
      ng-app="sampleAngularApp" ng-controller="AppController">
         <div class="row">
            <div class="col-xs-12 col-sm-offset-2 col-sm-8 col-md-offset-3 col-md-3">
               <button class="btn btn-primary" ng-click="openDlg()">Open Modal</button>
            </div>
         </div>
         <ng-include src="'./assets/app/templates/sampleModal.html'"></ng-include>
         
      </div>
      <script src="./assets/jquery/js/jquery-2.2.4.min.js"></script>
      <script src="./assets/bootstrap/js/bootstrap.min.js"></script>
      
      <!-- angular js related libraries -->
      <script src="./assets/angular-1.6.7/angular.min.js"></script>
      <script src="./assets/app/js/sampleModalModule.js"></script>
      <script src="./assets/app/js/app.js"></script>
   </body>
</html>

此文件的最关键部分是以下部分:

<div class="container app-topmargin" ng-app="sampleAngularApp" ng-controller="AppController">
   <div class="row">
      <div class="col-xs-12 col-sm-offset-2 col-sm-8 col-md-offset-3 col-md-3">
         <button class="btn btn-primary" ng-click="openDlg()">Open Modal</button>
      </div>
   </div>
   <ng-include src="'./assets/app/templates/sampleModal.html'"></ng-include>
         
</div>

外部 div 元素引用了一个名为 "sampleAngularApp" 的 AngularJS 应用 (ng-app)。它还引用了一个名为 "AppController" 的 AngularJS 控制器。这基本上表明任何 HTML 元素(包括外部 div)都处于名为 "sampleAngularApp" 的 AngularJS 应用程序及其控制器 "AppController" 的控制之下。还有一个按钮元素,它具有 ng-click 事件处理程序。当用户单击此按钮时,此元素会执行所有操作,在 AngularJS 控制器内部,会调用一个名为 openDlg() 的函数来处理此事件。正如您所猜测的那样,这就是触发模态对话框显示的函数。

这是主页面的 JavaScript 代码,如下所示:

var app = angular.module("sampleAngularApp", [ "sampleModalModule" ]);
app.controller("AppController", ["$scope", function ($scope) {
   $scope.openDlg = function () {
      console.log("clicked here...");

      // This is where the magic happens.
      ...
   };
}]);

这只是一个典型的 AngularJS 主应用程序。第一行定义了一个名为 "sampleAngularApp" 的 AngularJS 模块。这基本上就是 ng-app 引用的内容。从第二行开始,它定义了应用程序的控制器 "AppController"。控制器中只有 openDlg() 函数的定义。我在其中放了一个注释,以指示我放置显示模态对话框的代码的位置。

在深入研究显示模态对话框的代码之前。我想向您展示如何导入模态对话框。模态对话框通过 ng-include 元素导入。这是 HTML 代码:

<ng-include src="'./assets/app/templates/sampleModal.html'"></ng-include>

我想指出的是 src 属性。双引号中的内容是单引号括起来的 string。这是正确的语法。如果删除单引号,加载主页面时会出错。

我想说的另一点是 src 属性应该指向模板文件,该文件可以从当前 URL 位置找到。请记住这一点。如果找不到模板,您可能会在开发者控制台中看到错误。

以下是定义模态对话框模板的 HTML 代码:

<div id="modalDlg" class="modal fade" tabindex="-1" 
role="dialog" ng-controller="ModalDlgController">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" 
        data-dismiss="modal" aria-label="Close">
        <span aria-hidden="true">×</span></button>
        <h4 class="modal-title">Sample Modal Dialog</h4>
      </div>
      <div class="modal-body">
         <form>
            <div class="form-group">
               <label for="someValueField">Some Value</label>
               <input type="text" class="form-control" ng-model="someValueField" 
                placeholder="Enter Some Text Value">
            </div>
            <button class="btn btn-default" 
            ng-click="modalButtonClick()">Take Action</button>
         </form>
      </div>
    </div>
  </div>
</div>

此 HTML 布局略有不同。它不是完整的 HTML 页面。您只能看到模态对话框的布局。这没关系,因为它将通过 ng-include 包含到主页面中。其中,还有一个名为 "ModalDlgController" 的 ngController。这是这个新控制器对应的代码:

var module = angular.module("sampleModalModule", []);
module.controller("ModalDlgController", ["$scope", function ($scope) {
   $scope.someValueField = "";
   
   $scope.modalButtonClick = function () {
      console.log("do action on Modal");
      console.log("Current 'someValueField' value is [[" + $scope.someValueField + "]]");
   };
}]);

在此控制器 "ModalDlgController" 中,有一个名为 "someValueField" 的作用域变量。它通过 ng-model 绑定到模板上的输入字段。对这个作用域变量的任何更改都会在显示中反映出来。模板上还有一个可以按下的按钮。此按钮不执行太多操作。控制器中有一个名为 "modelButtonClick()" 的作用域函数。这个函数所做的就是将作用域变量 "someValueField" 的当前值输出到开发者控制台。

现在我们已经具备了示例应用程序的所有组件,我可以向您展示为了显示模态对话框而需要进行的更改。

第一个示例应用程序

下载示例源代码并解压缩后,您将看到三个不同的文件夹。它们各自包含一个可运行的 AngularJS 应用程序。每个应用程序都展示了对前一个工作示例的改进。第一个在 "app1" 文件夹中。在此示例中,我想解决的问题是弹出模态对话框。

根据 Bootstrap 文档。要弹出模态对话框,只需查询模态对话框的元素,然后调用附加的 JavaScript 函数 modal(),并传入参数 "show"。这大致是实现此功能的代码:

$("#modalDlgId").modal("show");

然而,应用程序架构不是 jQuery。它是 AngularJS,它在一定程度上支持 jQuery。AngularJS 拥有自己的 jQuery 实现,称为 jqlite。可以使用 AngularJS 查询模态对话框作为元素,然后在此元素上调用附加的 JavaScript 函数 modal()

还记得我在主控制器 "AppController" 中添加的注释吗?这是我查询模态对话框并将其弹出以供显示的实际代码:

var dlgElem = angular.element("#modalDlg");
if (dlgElem) {
   dlgElem.modal("show");
}

这是完整的 _app.js_:

var app = angular.module("sampleAngularApp", [ "sampleModalModule" ]);
app.controller("AppController", ["$scope", function ($scope) {
   $scope.openDlg = function () {
      console.log("clicked here...");
      
      var dlgElem = angular.element("#modalDlg");
      if (dlgElem) {
         dlgElem.modal("show");
      }
   };
}]);

AngularJS 提供了一些辅助方法。其中一个是 angular.element(),它类似于 jQuery 选择器。在这种情况下,我使用它来查找 ID 为 "modalDlg" 的元素。 "if" 块检查返回的元素是否不为 null。然后我使用该元素调用 modal() 方法。请注意,modal() 函数由 jQuery 附加到该元素。调用它将显示模态对话框。

这是单击按钮并显示模态对话框时的屏幕截图:

App1 screen shot

您可以做一个简单的测试,在弹出模态对话框的文本字段中键入一些内容。然后关闭它。再次打开同一个模态对话框,您会看到您键入的内容仍然在那里。在很多情况下,这是不可接受的行为。正确的行为是,当模态对话框关闭时,它将重置其所有子输入字段。

第二个示例应用程序

下一个目标是找到一种方法,在模态对话框打开或关闭时清理其输入字段。现在我们知道 AngularJS 有一个名为 element() 的辅助方法可以查询页面上的元素,我们可以将相同的原理应用于这个问题。Bootstrap 模态对话框具有可以用于清理的事件回调。

根据 Bootstrap 文档,模态对话框关闭时的回调事件可以按如下方式处理:

$('#exampleModal').on('hide.bs.modal', function (event) {
...
});

好了,解决方案如下:

  • 获取模态对话框的元素。
  • 将上述事件回调附加到此元素。在该回调中,清理模态对话框中的输入字段。

第二个示例在 "app2" 文件夹中。在 "app1" 中进行的更改之上,我为模态对话框添加了关闭时的事件回调。它在 ModalDlgController 中。这是代码:

var module = angular.module("sampleModalModule", []);
module.controller("ModalDlgController", ["$scope", function ($scope) {
   $scope.someValueField = "";
   
   var dlgElem = angular.element("#modalDlg");
   if (dlgElem) {
      dlgElem.on("hide.bs.modal", function() {
         $scope.someValueField = "";
         console.log("reset data model..");
      });
   }
   
   $scope.modalButtonClick = function () {
      console.log("do action on Modal");
      console.log("Current 'someValueField' value is [[" + $scope.someValueField + "]]");
   };
}]);

设置事件回调的代码段是这个:

var dlgElem = angular.element("#modalDlg");
if (dlgElem) {
   dlgElem.on("hide.bs.modal", function() {
      $scope.someValueField = "";
      console.log("reset data model..");
   });
}

思路与上一节相同,我首先使用 angular.element() 查找对话框的元素。然后我使用它调用 on() 方法(也来自 jQuery)来设置事件回调。这很棒的一点是,在事件回调中,我实际上可以将作用域变量设置为空 string。这就是重置对话框中的输入字段的方法。

要进行测试,您可以打开对话框,在对话框的输入字段中输入一些值,然后关闭它。再次打开模态对话框,原始输入值应该已消失。

我想解决的最后一个技术问题是,我必须设法将值(或值)从主应用程序传递到模态对话框。基本上,我想将值从我的主控制器传递给子控制器或非子控制器。结果证明,这也很容易做到。

第三个示例应用程序

我创建了第三个应用程序,以演示将数据从一个 AngularJS 控制器传递到另一个 AngularJS 控制器的方法。在继续之前,我想指出这可能不是最佳解决方案,因为它可能会导致紧耦合,而您又想避免这种情况。

示例代码可以在 "app3" 文件夹中找到。

如何将值从一个控制器传递到另一个控制器?结果表明,使用 AngularJS,这很容易实现。我的做法是先查询元素。一旦找到元素,我就会在其上调用 .scope()。它将返回附加到该元素的 AngularJS 作用域。然后,我可以将任何值传递给此作用域中的作用域变量。这是 _app.js_ 的完整文件:

var app = angular.module("sampleAngularApp", [ "sampleModalModule" ]);
app.controller("AppController", ["$scope", function ($scope) {
   $scope.openDlg = function () {
      console.log("clicked here...");
      
      var dlgElem = angular.element("#modalDlg");
      if (dlgElem) {
         dlgElem.scope().someValueField = "This text is from the caller.";
         dlgElem.modal("show");
      }
   };
}]);

感兴趣的片段是这个:

var dlgElem = angular.element("#modalDlg");
if (dlgElem) {
   dlgElem.scope().someValueField = "This text is from the caller.";
   dlgElem.modal("show");
}

这里所做的是:

  • 查询模态对话框的元素。
  • 检查并确保元素存在。
  • 调用元素的 .scope(),它返回元素附加到的作用域。然后引用作用域变量并为其分配新值。
  • 弹出模态对话框。

如果一切正常,当您进行测试时,您应该会看到从这个主应用程序控制器传递到模态对话框的值。如果您更改了该值,然后关闭模态对话框。再次打开它,您应该会看到值已重置为从主应用程序传递过来的值。

运行演示应用程序

在本教程中,我介绍了解决三个简单技术问题的三种不同解决方案。第一个是在 AngularJS 应用程序中弹出 Bootstrap 模态对话框。第二个展示了如何在模态对话框关闭时清理输入字段,可以轻松修改为在模态对话框打开之前进行清理。最后一个示例展示了如何将数据从一个控制器传递到另一个控制器,以便主应用程序控制器可以将一些数据传递给模态对话框。

下载示例源代码并解压缩后,您将找到三个应用程序文件夹,从 _app1_ 到 _app3_。在其中,您应该会找到一些 _*.sj_ 文件。这些文件应重命名为 _*.js_ 文件。

此外,演示源代码不包含 AngularJS 和 Bootstrap 源代码。请下载它们并自行添加。每个 _app_ 文件夹中的 _app.html_ 文件将向您展示如何操作。如果您遇到任何错误,请检查开发者的控制台以获取详细信息。

我使用 Jetty Web Server 进行测试。我为 CodeProject 撰写了另一篇文章,讨论了 使用 Jetty Web Server 提供静态 Web 内容的方法。为了测试此演示应用程序,我使用了该文章中描述的第二种方法。

操作步骤如下:

  • 下载 Jetty Web Server 并解压缩。
  • 在 Jetty Web Server 的根目录下,找到 _webapps_ 文件夹。
  • 解压缩示例应用程序存档。您会找到一个名为 _angular-bootstrap-modal_ 的文件夹。在此文件夹内,有三个子文件夹,_app1_ 到 _app3_,最后一个是 _WEB-INF_。
  • 将 angular-bootstrap-modal 文件夹复制到 Jetty 的 webapp 文件夹中。
  • 假设您已将 _*.sj_ 文件重命名为 _*.js_。并已添加 AngularJS 和 Bootstrap 代码文件。如果还没有,那么现在就是时候进行这些操作了。
  • 使用命令 java -jar start.jar 运行 Jetty Web Server。

Web 服务器启动后,您可以导航到:

  • https://:8080/angular-bootstrap-modal/app1/app.html
  • https://:8080/angular-bootstrap-modal/app2/app.html
  • https://:8080/angular-bootstrap-modal/app3/app.html

回顾

这三种方法是解决技术问题的最佳方法吗?我认为不是。我劝您在考虑将这些方法应用于解决您当前的具体问题时要三思。弹出模态对话框的第一种方法破坏了封装。调用者组件不应该了解被使用组件的实现细节。但我所做的打破了这条规则。第三种方法在调用者组件和被使用组件之间创建了紧耦合。第二种方法用于清理输入字段稍微好一些,但 UI 和后端 JavaScript 代码之间存在耦合。

这些不是使用 Bootstrap 组件的最佳方式。这就是为什么存在 ui-bootstrap 的原因。我宁愿让 ui-bootstrap 与我的项目配合使用,也不愿使用这些作为替代解决方案。但是,了解它们很有趣。

希望您喜欢本教程。祝您的项目顺利。

历史

  • 2018年2月2日 - 初稿
© . All rights reserved.