使用AngularJS和 Web API的全面CRUD操作

分享于 

38分钟阅读

Web开发

  繁體

介绍

上一篇文章展示了如何使用AngularJS和 Web API 访问和显示服务器端分页的数据集。 这里扩展主题并使用 CRUD ( 创建。读取。更新和删除) 数据操作增强示例应用程序。 本文中的新示例应用和讨论主要强调添加。更新和删除数据。 本文介绍的重要功能和示例应用程序包括:

  • 使用模态对话框添加和更新数据项。
  • 内联添加和更新表中的多个数据行。
  • 删除表中的多个数据记录。
  • 提交数据更改后动态刷新显示。
  • 在自定义。inline 和 onblur 样式中输入数据验证。
  • 用于执行命令的活动 Pattern。
  • 将当前页面保留为 Angular SPA内部和外部重定向时出现脏警告。
  • 尽管文章中没有描述 Web API的详细信息,但完全 Web API 应用程序和代码方便了CRUD工作流。

这些主要库和工具在示例应用程序中使用:

  • AngularJS 1.2.6
  • Bootstrap 3.1
  • ngTable
  • ngExDialog
  • Web API 2.2
  • .NET 框架 4.5
  • Entity Framework 6.1
  • SQL Server 2012或者 2014 LocalDB
  • 带 IIS Express的Visual Studio 2013或者 2015

设置和运行示例应用程序

下载的源包含两个分别针对 Web API 和客户端网站的Visual Studio 解决方案。 每个都将用 Visual Studio 实例打开。

以下是设置和运行 SM.Store.WebApi 解决方案的指导:

  • 示例应用程序默认连接到 SQL Server LocalDB 2014. 如果使用 2012版本,则需要在 web.config 文件中启用它的connectionString。

  • 重新构建 SM.Store.WebApi 解决方案,它自动从 NuGet 下载所有已经配置的库。 你需要在本地计算机上有一个活动的互联网连接。

  • 确保以为起始项目,以为单位,按按 这将启动IIS和 Web API 主机站点,在LocalDB实例中自动创建数据库,并使用所有示例数据记录填充表。

  • 将呈现 Web API 项目中的测试页,指示 Web API 数据提供程序已经准备好使用。 你可以让页面最小化。

  • 如果在运行测试客户端网站时无需单步执行 Web API 代码,则可以在命令提示符下执行这里命令行,在执行上述初始设置后,每次都启动IIS表示:

    如果你使用 Visual Studio 2013构建 SM.Store.WebApi 解决方案,请执行以下操作:

    "C:Program FilesIIS Expressiisexpress.exe"/site:SM.Store.Api.Web"

    如果你使用 Visual Studio 2015 ( 将 [WebApiSolutionPath] 替换为本地驱动器的) 构建 SM.Store.WebApi 解决方案:

    "C:Program FilesIIS Expressiisexpress.exe"/site:SM.Store.Api.Web/config:"[WebApiSolutionPath]SM.Store.WebApi.vsconfigapplicationhost.config"

Since SM.Store.Client.Web 解决方案中只有一个项目,项目设置 index.html 作为起始页,你需要从 Visual Studio 打开解决方案,按以运行它。产品列表 page默认显示,并在 table 中显示分页数据项。 在这里页面中使用模态对话框添加和更新数据将在下一节中进行讨论。

选择联系人列表顶部菜单项将打开包含在 table 中的数据记录的页面。 在这里页面上实现了 inline table 编辑功能,该功能将在后面的部分中显示。

在模态对话框中使用数据窗体

在单独的页或者模态对话框中使用数据窗体管理数据非常常见,特别是在显示 table 或者网格中。 这种设计适合更复杂的table 或者网格结构,例如分页。分组或者甚至分层的数据集。 在视图模板可以定制和支持数据的同时,可以轻松地使用先前创建的AngularJS模式对话框 ngExDialog插件,以添加和更新数据。 读者可以从我以前的文章中查看ngExDialog的基本实现和代码的细节,如有兴趣的话。

按产品列表 page existing existing existing id link link,然后从AJAX中获取数据,将现有产品标识值作为参数启动,从而启动已有的数据编辑模式。 点击页面底部的 Add按钮将传递未定义的值,并使用空数据表单打开模式对话框。 打开数据窗体对话框的参数对象包括在关闭模式对话框之前执行 refreshGrid 函数的beforeCloseCallback 属性。

//Called from clicking Product Name link in table.$scope.paging.openProductForm = function (id) {
 $scope.productId = undefined;
 if (id!= undefined) {
 $scope.productId = id;
 }
 exDialog.openPrime({
 scope: $scope,
 template: 'Pages/_product.html',
 controller: 'productController',
 width: '450px',
 beforeCloseCallback: refreshGrid
 });
};

这里显示了更新产品模式对话框的示例。

在打开对话框中编辑和保存现有数据是一个基于单一记录的过程,然后在刷新数据 table 之前重复执行。 这里特性是通过在提交当前记录后清除所有输入字段的前一个数据值来实现的。 用户可以响应确认对话框以持续添加新的数据项或者不。

还可以指定在刷新数据 table 之前使用 maxAddPerLoad vairable的值添加的最大记录数。 当达到预设值值( 默认 10 ) 时,对话框将通知用户将添加的最后一条记录。

that从页面提交数据并从 Web API 调用数据库仍然是一个记录,尽管可以累积重复添加的新数据项并使用单个数据库调用提交新数据记录的array。

添加新产品时,创建 $scope.newProductIds array 以缓存从 Web API 响应返回的产品ID值,以便将新数据项添加到数据库中。 使用这里 array的数据有两个目的。 一个是跟踪添加记录的数量。 另一个是检索一组新增的数据记录,该记录将刷新 table。 然后用户可以清楚地查看哪些新数据行已经被添加。

使用添加的数据行刷新 table 需要获取除常规筛选和分页数据集之外的其他数据集。 进程调用相同的Web API 方法,GetProductList_P() 通过 $scope.n JSON对象中的ewProductIds array。 然后,Web API 控制器中的方法将重定向调用,以便根据 $scope.n ewProductIds array 列表。

AngularJS控制器中的代码:

//For refreshing add-new data. if ($scope.newProductIds.length >0) {
 filterJson.json += ""NewProductIds":" + JSON.stringify($scope.newProductIds) + ","//Reset array. $scope.newProductIds = [];
}else {
 //Build normal filtered, sorted, and paginated filterJson.json string.. . .
}

Web API 控制器中的代码:

[Route("~/api/getproductlist_p")]public ProductListResponse Post_GetProductList([FromBody] GetProductsBySearchRequest request)
{
. . . 
 if (request.NewProductIds!= null && request.NewProductIds.Count >0)
 {
 //For refresh data with newly added products. IList<Models.ProductCM> rtnList = bs.GetProductListNew(request.NewProductIds);
 resp.Products.AddRange(rtnList);
 }
 else {
 //For obtaining regular filtered, sorted, and paginated data list.. . . 
 IList<Models.ProductCM> rtnList = bs.GetProductList(request.searchParameters);
 resp.Products.AddRange(rtnList); 
 }
 return resp;
} 

表格中的内联数据编辑

使用angularjs的两种数据绑定功能,数据 table 上的inline 编辑操作可以更加有效和优雅。 Contrary在rows相同按钮arrays超链接或者图标的数组放置在列中添加。编辑和提交操作相反,示例应用程序的 Contact Contact显示了执行内联添加或者更新多行请求的更精确用户界面。

页具有以下状态设置:

  • Read: 当数据最初加载或者刷新时,这是默认状态。 它还可以是添加状态设置和编辑状态设置之间的中间状态。 below 屏幕截图与前一节设置和运行样例应用程序相同,但再次显示为便于比较,以便与后续的编辑和添加图。

  • :选中复选框选择任何现有数据行将启用行中的所有输入字段。 可以为编辑选择多行。 用户可以编辑和提交字段值,也可以取消更改。

  • 添加: 单击添加按钮,在启用 Add enabled下,每次都会为新数据条目添加一个空行。 在任何数据操作切换到其他状态之前,添加和编辑状态设置都是互斥的,因为任何数据操作都应该被完成或者取消。

下面是与控制和跟踪这些状态设置相关的重要变量或者结构。

  • maxEditableIndex: 将新添加的数据记录与现有数据记录进行分离以进行编辑的位置。 大于这里索引的任何索引号都表示该项是新的数据记录。

  • $scope.checkboxes.items: 复选框项的array。 重点是复选框 array 大小和项位置应该与 contactList array的位置相同。 虽然数据 table 中的复选框字段不是contactList的成员,但两个数组的索引号都是one-to-one链接的。

  • $scope.model.contactList_0: 初始数据加载期间填充的主数据列表的深层副本。 它是用于检查脏状态和恢复数据更改的基本数据记录。 表单的$dirty 属性反映了表单中实际数据记录的更改,比如 for,因这里无法检查主数据列表。

  • $scope.rowDisables: 临时缓存在添加状态中禁用现有行复选框项的整数 array。

  • $scope.addRowCount: 跟踪添加新行的数字。 GREATER 大于 0表示添加状态。

  • $scope.editRowCount: 用于跟踪现有行以进行编辑的数字。 GREATER 大于 0表示编辑状态。

  • $scope.isAddDirty: 如果为 true,则任何数据都已经输入任何新添加的行。

  • $scope.isEditDirty: 如果是 true,任何现有行都将进行任何更改。

联系人列表页面的代码使用定制标志 $scope.isAddDirty$scope.isEditDirty,而不是在 form.$dirty 中构建的AngularJS。 原因是复选框中与实际数据项无关的任何值更改都将使 form.$dirty 成为 true

向 table 中添加新行的代码很简单。 它还包括限制新添加的项行的最大数目的逻辑。 默认的$scope.maxAddNumber = 10 在控制器级别设置。

$scope.addNewContact = function () { 
 //Set max added-row number limit.if ($scope.addRowCount + 1 == $scope.maxAddNumber) {
 exDialog.openMessage({
 scope: $scope,
 title: "Warning",
 icon: "warning",
 message: "The maximum number (" + $scope.maxAddNumber + ") of added rows for one submission is approached." }); 
 } 
 //Add empty row to the bottom of grid.var newContact = {
 ContactID: 0,
 ContactName: '',
 Phone: '',
 Email: '',
 PrimaryType: 0 };
 $scope.model.contactList.push(newContact);
 //Add new item to base array. $scope.model.contactList_0.push(angular.copy(newContact));
 //Add to checkboxes.items.  seqNumber += 1;
 $scope.checkboxes.items[maxEditableIndex + seqNumber] = true;
 //Update addRowCount. $scope.addRowCount += 1; 
};

关于复选框的代码逻辑有些复杂。 它将启用编辑状态,或者取消编辑或者添加状态,设置。 当选中或者取消选中复选框时,$scope.listCheckboxChange() 函数在选择或者unselecting时执行主要操作。

$scope.listCheckboxChange = function (listIndex) { 
 //Click a single checkbox for row.if ($scope.checkboxes.items[listIndex]) {
 //Increase editRowCount when checking the checkbox. $scope.editRowCount += 1; 
 }
 else {
 //Cancel row operation when unchecking the checkbox.if (listIndex > maxEditableIndex) {
 //Add status.if (dataChanged($scope.model.contactList[listIndex],
 $scope.model.contactList_0[listIndex])) { 
 exDialog.openConfirm({
 scope: $scope,
 title: "Cancel Confirmation",
 message: "Are you sure to discard changes and remove this new row?" }).then(function (value) {
 cancelAddRow(listIndex);
 }, function (forCancel) {
 undoCancelRow(listIndex);
 });
 }
 else {
 //Remove added row silently. cancelAddRow(listIndex);
 }
 }
 else {
 //Editing mode.if (dataChanged($scope.model.contactList[listIndex],
 $scope.model.contactList_0[listIndex])) {
 //Popup for cancel. exDialog.openConfirm({
 scope: $scope,
 title: "Cancel Confirmation",
 message: "Are you sure to discard changes and cancel editing for this row?" }).then(function (value) {
 cancelEditRow(listIndex, true);
 }, function (forCancel) {
 undoCancelRow(listIndex);
 });
 }
 else { 
 //Resume display row silently. cancelEditRow(listIndex);
 } 
 }
 } 
 //Sync top checkbox.if ($scope.addRowCount >0 && $scope.editRowCount == 0) 
 //Alway true in Add status. $scope.checkboxes.topChecked = true;
 elseif ($scope.addRowCount == 0 && $scope.editRowCount >0)
 $scope.checkboxes.topChecked =!hasUnChecked();
};

取消编辑的行是通过从基础数据 array 复制对象项并从 $scope.editRowCount 中扣除数字 1来完成的。

var cancelEditRow = function (listIndex, copyBack) {
 if (copyBack) {
 //Copy back data item. $scope.model.contactList[listIndex] = angular.copy($scope.model.contactList_0[listIndex]);
 }
 //Reduce editRowCount. $scope.editRowCount -= 1;
};

取消添加的新行将导致删除行。 但是,删除 array 最后一个位置不存在的行时,将引发一个问题。 如果这样做,剩下的行位置和索引号可以向前移动数据项 array,导致数据项 misallocated。 若要解决这里问题,任何添加的新行都将保留在 array 中,但标记为 undefined。 相应的数据绑定迭代将排除那些行项 Having的undefined 值。

cancelAddRow() 函数中的代码处理正确移除行项目的所有可能情况:

var cancelAddRow = function (listIndex) {
 //Handles array element position shift issue. if (listIndex == $scope.checkboxes.items.length - 1) {
 //It's the last row.//Remove rows including all already undefined rows after the last active (defined) row.for (var i = listIndex; i > maxEditableIndex; i--) {
 //Do contactList_0 first to avoid additional step in watching cycle. $scope.model.contactList_0.splice(i, 1);
 $scope.model.contactList.splice(i, 1);
 $scope.checkboxes.items.splice(i, 1);
 //There is only one add-row.if (i == maxEditableIndex + 1) {
 //Reset addRowCount. $scope.addRowCount = 0;
 //Reset seqNumber. seqNumber = 0;
 }
 else {
 //Reduce $scope.addRowCount. $scope.addRowCount -= 1;
 //Exit loop if next previous row is not undefined.if ($scope.model.contactList[i - 1]!= undefined) {
 break;
 }
 }
 }
 }
 else {
 //It's not the last row, then set the row to undefined. $scope.model.contactList_0[listIndex] = undefined;
 $scope.model.contactList[listIndex] = undefined;
 $scope.checkboxes.items[listIndex] = undefined;
 //Reduce $scope.addRowCount $scope.addRowCount -= 1;
 } 
};

相应的ng-if 检查器被添加到 tr 标记中,以便在 contactList.html 中进行 ng-repeat 操作:

<trng-repeat="item in $data"ng-if="$data[$index]!= undefined">

取消操作可以根据数据工作流的阶段以不同的方式进行处理。

数据或者空项加载,或者输入框集中,但没有输入或者更改数据值的原始阶段。 这种情况的条件是" $scope.addRowCount> 0 and $scope.isAddDirty == false "for添加状态和" $scope.editRowCount> 0 and $scope.isEditDirty == false "对于编辑状态,这里阶段的取消应该无需任何确认进程即可以静默执行。

在任何输入字段中,任何添加或者更改数据值的脏阶段。 这种情况的条件是" $scope.isAddDirty == true"的添加状态和编辑状态的" $scope.isEditDirty == true"。 显示确认对话框,允许用户选择是否进入取消或者回放到上一屏幕上。

你也可以单击取消更改按钮调用 $scope.cancelChanges() 函数或者取消列标题( 顶部复选框) 中的复选框以调用 $scope.topCheckboxChange() 函数,从而一次取消所有工作行的添加或者编辑状态。 在所有工作行中,不管行是否全部选中或者不被选中,前者将取消操作。 后者仅在为当前状态选择所有行时才执行取消操作。 在编辑状态中,在添加状态或者 cancelAllEditRows() 函数中,单击按钮和取消选中顶部复选框将调用 cancelAllAddRows() 函数。 访问者可以从下载的源中查看这些函数的代码细节。

删除表中的数据行

删除数据行始终可以使用 inline 和多行样式执行。 选中选中复选框即可选择任何行,并生成包含所选产品标识值的array。 然后代码将使用 deleteProducts 数据服务调用 Web API 方法。 通常,delete 操作必须先确认,然后才能继续。

$scope.deleteContacts = function () {
 var idsForDelete = [];
 angular.forEach($scope.checkboxes.items, function (item, index) {
 if (item == true) {
 idsForDelete.push($scope.model.contactList[index].ContactID);
 }
 });
 if (idsForDelete.length >0) {
 var temp = "s";
 var temp2 = "s have"if (idsForDelete.length == 1) {
 temp = "";
 temp2 = " has";
 }
 exDialog.openConfirm({
 scope: $scope,
 title: "Delete Confirmation",
 message: "Are you sure to delete selected contact" + temp + "?" }).then(function (value) {
 deleteContacts.post(idsForDelete, function (data) {
 exDialog.openMessage({
 scope: $scope,
 message: "The" + temp2 + " successfully been deleted." });
 //Refresh grid - dummy setting just for triggering data re-load.  $scope.tableParams.count($scope.tableParams.count() + 1);
 }, function (forCancel) {
 exDialog.openMessage($scope, "Error deleting contact data.", "Error", "error");
 });
 });
 }
};

在调用 上面 函数时,删除确认对话框和基础屏幕看起来如下。

输入数据验证

AngularJS提供基于范围和元素验证控制对象,以方便输入验证和 inline 消息显示。 为了避免混淆DOM表单对象和DOM元素对象,我们将它们引用到"范围窗体对象"和"范围元素对象"。 样例应用程序使用了一个自定义验证指令( 最初从 GitHub 下载),但对扩展功能的修改非常巨大。 只要任何输入字段超出焦点,样例应用程序中的验证过程就会触发,因此,尽管指令中还有其他事件类型,比如在dirty和 onsubmit 中也可以使用 onblur 事件类型 validation。 你可以发现 onblur 验证更加自然和用户友好。 在本文中,ngValidator中的代码逻辑不是讨论的主要焦点。 访问者可以在 directives.js 中浏览ngValidator代码,如果有兴趣,。

使用文本。货币数和日期输入字段的验证实现了更新/添加产品模式对话框。 以下是单价字段的属性设置,作为典型示例。

<inputtype="text"class="form-control"id="txtUnitPrice"name="txtUnitPrice"data-ng-model="model.Product.UnitPrice"validate-on="blur"clear-on="focus"requiredrequired-message="'Price is required'"numberinvalid-number-message="'Invalid number'"max-number="10000"max-number-message="'Price cannot exceed $10,000'"message-display-class="replace-label-dent"ng-focus="setDrag(true);setVisited('txtUnitPrice')"ng-blur="setDrag(false)">

modal modal和 inline adding的单记录表单的内联样式中都显示所有错误消息。

dirty颜色的边框也显示了脏字段。 这是通过为 input 元素的父 div 元素设置 has-warning CSS类来实现的。

<divclass="form-group"ng-class="{'has-warning' : productForm.txtUnitPrice.$dirty}">

对于 inline 添加/编辑 table,在为每行设置验证时存在一个主要问题。 AngularJS只能为 table 中的整个列创建单个范围元素对象。 它不支持动态地为与 ng-repeat 迭代的每个输入字段元素生成作用域元素。 分辨率是在选定行时为行中的元素制作arraylist原始作用域元素对象的简单副本。 新生成的范围元素对象需要将行索引号作为对象 NAME的后缀。 样例应用程序使用指令 setNameObeject 进行这里目的。

.directive('setNameObject', function ($timeout) {
 return {
 restrict: 'A', 
 link: function (scope, iElement, iAttrs, ctrls) {
 var name = iElement[0].name;
 var baseName = name.split("_")[0]; 
 var scopeForm = scope[iElement[0].form.name];
 scope.$watch(scopeForm, function () {
 $timeout(function () {
 if (scopeForm[name]!= undefined) {
 //Shallow copy to reference existing object (deep copy doesn't work here). scopeForm[baseName + '_' + scope.$index] = scopeForm[name];
 //Change $name property. scopeForm[baseName + '_' + scope.$index].$name = baseName + '_' + scope.$index;
 }
 });
 }); 
 }
 };
})

当选择多行在 Contact List 页面上启动编辑状态时,将在范围表单对象中添加每个验证字段的多个范围元素对象。 below 屏幕截图显示在 Contact List table ( 参考前一部分中的Edit状态截图中的行编辑数据) 中编辑第一行和第二行时,作用域窗体对象及它的所有子范围元素。 带有" _{{$index}}"后缀的元素对象是静态添加的,对于每个列只有一个,而不替换 {{$index}} 变量值,由AngularJS的原始进程来。 对于每个输入元素,使用 上面 指令代码动态创建带有索引号的高亮显示元素对象作为后缀。 如果没有这些添加的自定义对象,inline table 输入验证就不会生效。

尽管输入验证的onblur Pattern 看起来不错,但是流程逻辑比on或者 onsubmit 模式更复杂。 某些副作用和衍生问题也可以引发。 下面是与 onblur 输入验证相关的问题和解决方案的两。

如果输入框集中 focused focused值无效,触发 ng-click 事件触发事件触发事件 blur button button button error error。 当前焦点输入框的on-blur 事件出现早于 ng-click 事件。 因此,ng-click 事件处理程序中的代码将被禁止执行。 使用 ng-mousedown 事件解决了这个问题。 对于 Visual Studio 或者浏览器脚本调试器,工作流指示 ng-mousedown 事件中的代码在 onblur 事件可以被启动之前执行。 示例应用程序中,ng-mousedown 联系人列表( contactList.html ) page page page page page page update update update dialog dialog dialog dialog page page page button button。

可以通过执行以下步骤来复制问题:

  • 将HTML文件中这些按钮的ng-mousedown 更改为 ng-click
  • 运行应用程序并打开任何输入表单。
  • 在任何输入框中输入无效条目。
  • 点击按钮,这个问题就会被注意到。

通过代码设置焦点在pristine状态验证错误。 这发生在浏览器的最新版本 ms Chrome edge和 Firefox 上,而不是在 IE 上。 使用 Angular 方法集中的输入元素通常使用输入元素标记中的auto-focus 属性设置。 在样例应用程序中使用JavaScript代码设置焦点的惟一地方是重新设置数据表单,以便在添加产品对话框中重复添加新产品项。 这个问题也可以能是由于执行 in ( 如 required ) 所构建的HTML 5的时间问题造成的。 在 productContorller.clearAddForm() 函数中,如果将焦点设置为使用 $timeout 服务调用的匿名函数,则输入元素重置。

//Need DOM operation to auto focus the ProductName field although using DOM code in a controller is not a good practice. //Also need $timeout service for MS-Edge, Chrome, Firefox (but not IE). Otherwise the box will be focused with"required" validation error.$timeout(function () {
 angular.element(document.querySelector('#txtProductName'))[0].focus();
});

要重现这个问题,只需按以下步骤操作:

  • 注释 productContorller.clearAddForm() 中函数和 $timeout 服务的行。
  • 使用 ms edge,Chrome 或者 Firefox 浏览器运行应用程序。
  • 打开添加产品 dialog,添加一个新项目,然后保存。
  • 继续添加其他项目。
  • 将重置窗体,并显示问题。

注意,开发人员需要在每次修改任何html或者JavaScript文件中的代码后清除浏览器缓存页面文件和数据。

主动或者被动命令 Pattern?

主动命令 Pattern 意味着只有在命令执行合法且数据有效的情况下,命令( 如数据提交或者编辑取消) 才可用。 发出命令后不应进行进一步的干预。 另一方面,使用被动 Pattern,命令总是可用的,在启动命令之后,进程将检查执行的有效性。 被动命令的一个简单例子是可以随时点击Delete按钮的delete 命令。 如果单击按钮时没有选择数据项,则会弹出一个对话框,通知用户选择数据项。 但是,对于活动的Pattern,用户可以在单击 Delete Delete按钮之前强制执行必要的规则。 主动 Pattern 应该比被动 Pattern 更加清晰和逻辑清晰。 在示例应用程序中,所有数据提交和取消的按钮都是用活动的Pattern 实现的。 在代码中,这些按钮根据AngularJS的ng-disabled 指令有条件地启用或者禁用。 below 代码示例是从 contactList.html 获取的。

  • 仅当状态不是编辑时才启用 Add button。
    ng-disabled="editRowCount> 0 || addRowCount> = maxAddNumber"
  • 仅在编辑状态中选择至少一行时,才会启用删除按钮。
    ng-disabled="(isEditDirty || editRowCount == 0)"
  • 仅当存在任何脏数据且所有数据项均经过验证时,才启用保存更改按钮。
    ng-disabled="(!isEditDirty &&!isAddDirty) || contactForm.$invalid"
  • 只有在原始阶段或者有任何脏数据时,才启用 Cancel Cancel changes。
    ng-disabled="!isEditDirty &&!isAddDirty && editRowCount == 0 && addRowCount == 0"

离开当前页面时出现脏警告

如果存在更改页面的情况,每个数据修改网页通常会通知用户保存已经更改的数据( 如果存在的话)。 但是 AngularJS 拥抱 SPA架构和路由器配置为应用程序内部的switch 页面,离开页面场景时,在内部或者外部发生与应用程序区域有关的情况。 处理警告通知的事件也不同。 我们通常使用这两个事件发送脏警告:

AngularJS的作用域基于 $locationChangeStart: 这个事件可以由任何内部页面切换触发,也可以从任何外部站点重新定向到AngularJS路由的应用程序 URL。 自动关闭打开的ngExDialog实例的代码逻辑是使用该事件处理程序的示例。

本机 JavaScript window.onbeforeunload: 通过将AngularJS应用程序保留为任何外部站点来触发这里事件。

这两个事件处理程序放置在顶级 bodyController 中。 对象变量 $scope.body 及其属性 dirty 可以通过Prototype继承被所有子作用域访问。 下面是 bodyController的完整代码 block:

.controller('bodyController', ['$scope', 'exDialog', '$location', function ($scope, exDialog, $location) {
 //Object variable can be accessed by all child scopes. $scope.body = {};
 //Dirty warning and auto closing Angular dialog within the application. $scope.$on('$locationChangeStart', function (event, newUrl, oldUrl) {
 if (newUrl!= oldUrl)
 {
 //Dirty warning when clicking broswer navigation button or entering router matching URL.if ($scope.body.dirty) {
 //Use browser built-in dialog here. Any HTML template-based Angular dialog is processed after router action that has already reloaded target page. if (window.confirm("Do you really want to discard data changesnand leave the page?")) {
 //Close any Angular dialog if opened.if (exDialog.hasOpenDialog()) {
 exDialog.closeAll();
 }
 //Reset flag. $scope.body.dirty = false;
 }
 else {
 //Cancel leaving action and stay on the page.event.preventDefault();
 } 
 }
 else {
 //Auto close dialog if any is opened.if (exDialog.hasOpenDialog()) {
 exDialog.closeAll();
 }
 } 
 }
 });
 //Dirty warning when redirecting to any external site either by clicking button or entering site URL. window.onbeforeunload = function (event) {
 if ($scope.body.dirty) {
 return"The page will be rediracted to another site but there is unsaved data on this page.";
 } 
 }; 
}])

如果范围窗体的$dirty 属性发生任何更改,并且 $scope.$watch 用于表单 $dirty 更改,则动态更新 $scope.body.dirty的值。 否则,在任何数据提交或者取消之后,都必须将 $scope.body.dirty 设置为 false。 这样做可以避免在父范围中传播脏标志,如果以后离开基页或者父页,就会导致不必要的脏。 重置表单的最简单的all-in-one方法是调用这一行代码:

//$setPristine will reset form, set form and element dirty flags to false, and clean up all items in $error object. //This will also auto set $scope.body.dirty to false to disable dirty warning when using $scope.$watch for form $dirty changes.$scope.productForm.$setPristine();

不满意的是我们不能指定自己的模式对话框样式。 本机 window.confirm 对话框必须在 $locationChangeStart 事件处理程序中使用,因为在路由页切换后处理任何HTML模板。 因这里,向用户提供对话框并获取用户离开或者停留页面的反馈时太晚了。 不同的浏览器可能会以不同样式显示内置确认对话框,但功能相同。 below 是从 Microsoft Edge显示的对话框:

如果代码返回自定义字符串,window.onbeforeunload 事件将始终显示内置对话框。 可以删除 return 代码以防止浏览器弹出对话框,但无法停止页面离开。 如果不希望保留当前页面,则只有 Cancel Cancel button命令才可以保留当前页。 这是市场上任何浏览器的安全性特性,在开发人员的社区里已经讨论过。 当重定向到本示例应用程序的页面中的外部站点时,Microsoft Edge上的对话框显示如下:

摘要

数据修改始终是任何面向数据的应用程序的关键部分。 AngularJS和 Web API 使得基于web的数据CRUD操作更加通用和高效。 本文和样例应用程序为使用AngularJS和 Web API的数据CRUD提供了深入的技术细节。真实的世界实践和问题解决方案。

历史记录

  • Sep 23,2015: 原始帖子。
  • Nov: 2015: 添加了使用命令提示启动用 Visual Studio 2015编译的IIS站点的说明。 还修复了在 AngularJS controller.js. 中添加新联系人记录的问题源代码文件被更新。

COM  WEB  API  angular  Opera  Operation  
相关文章