在 Angular 中使用指令 iv,减少事件和绑定

分享于 

15分钟阅读

Web开发

  繁體 雙語

介绍

如果子元素包含属性 data-cmd 集合,iv-on-cmd 是允许单个事件处理程序处理包含指令 iv-on-cmd的元素的click事件的Angular 指令。

iv-on-cmd的目标是减少 ng-click 处理程序的数量,并提供一个单一的公共函数来处理所有的子函数。

背景

我发现 Angular 倾向于减少太多的绑定,我已经读过一些文章,指示两千个绑定太多。

在几个项目中,ng-repeat 创建了几百到几千个DOM元素的结果。 这些元素之间有三到三十个绑定。 这远远超过了two的估计速度。

我需要一种方法来做我以前用jQuery做过的事情。 这是在包装器 <div> 上使用一个单击处理程序,告诉我为包装器所有子级执行的命令。 使用 jQuery,我将执行以下操作:

<divclass="main-wrapper"><buttonclass="start-process">Start</button><buttonclass="cancel">cancel</button></div>

上面 是一个极大简化的DOM,但它可以用于这个解释。

通过示例 HTML 上面,我将添加一些jQuery来独立处理每个 <button>:

function startProcess() {
 // Do some kind of process here}function cancel() {
 // Cancel the operation here}
$(document).ready(function() {
 $(".start-process").on("click", startProcessFn);
 $(".cancel").on("click", cancelFn);
});

但是当我有上百个按钮或者它的他DOM元素时,它必须是编写这些 click 处理程序的巨大过程。

通过允许你通过 ng-click 指令设置click处理程序,Angular 有助于改进这一点,如下所示:

<divclass="main-wrapper"data-ng-controller="myCtrl"><buttondata-ng-click="startProcess()">Start</button><buttondata-ng-click="cancel()">cancel</button></div>

然后在控制器代码中,我将这样做:

angular.module("mine").controller("myCtrl", myCtrl);
myCtrl.$inject = ["$scope"];function myCtrl($scope) {
 $scope.startProcess = function() {
 // Do some kind of process here };
 $scope.cancel = function() {
 // Cancel the operation here }
}

但是,如果有几百个 ng-click 指令,这仍然会导致大量绑定发生。

使用代码

我的jQuery解决方案

为了解决jQuery中的许多事件绑定,我创建了一个命令处理程序。 这将使用 $().on() 函数的delegate 版本。 这是通过在父级上设置 $().on() 处理程序并指定将导致代码调用的子级来完成的。 所以用这个 HTML:

<divclass="main-wrapper"><buttondata-cmd="start-process">Start</button><buttondata-cmd="cancel">cancel</button></div>

脚本将如下所示:

function processCmd(event) {
 var $el = $(event.target);
 var cmd = $el.data("cmd");
 console.log("Command was %s", cmd); 
 // Process the commands.switch(cmd) {
 case"start-process":
 // Do somethingbreak;
 case"cancel":
 // Do somethingbreak;
 }
}
$(document).ready(function() {
 $(".main-wrapper").on("click", "[data-cmd]", processCmd);
});

使用这里代码,click 处理程序是委托处理程序。 当用户单击 <button> 时,事件委托给连接到 <div> 标记的事件处理程序。 但仅适用于具有属性 data-cmd的按钮。 现在即使有了几百个按钮我也只有一个事件处理程序。 而且,如果以后添加了按钮,则仍然调用我的事件处理程序。

示例 上面 足够小,我所描述的内容可能没有意义。 但是假设有一些重复的事物,它们之间的唯一差别是索引值或者某种形式的key-value。 以基于web的消息应用为例。 每个消息都有自己的唯一标识符。 如果每个消息有一个读按钮和一个删除按钮,那么每个消息都需要两个事件处理程序。 但是使用 $().on()的委托,我们可以有一个处理所有消息的事件处理程序。

<divclass="mail-shell"><divclass="message"><spanclass="sender">someone@example.com</span><spanclass="subject">Some message subject</span><spanclass="time">3:43 am</span><span><buttondata-cmd="read"data-cmd-data="KE1R-DJ5KW-9SJ21">Read</button></span><span><buttondata-cmd="delete"data-cmd-data="KE1R-DJ5KW-9SJ21">Delete</button></span></div><divclass="message"><spanclass="sender">person@example.com</span><spanclass="subject">Buy something from us</span><spanclass="time">2:49 am</span><span><buttondata-cmd="read"data-cmd-data="K19D-0PWR8-MMK92">Read</button></span><span><buttondata-cmd="delete"data-cmd-data="K19D-0PWR8-MMK92">Delete</button></span></div><divclass="message"><spanclass="sender">bot@example.com</span><spanclass="subject">Buy a Rolex from us</span><spanclass="time">2:31 am</span><span><buttondata-cmd="read"data-cmd-data="LK0P-HN8GT-00LPD">Read</button></span><span><buttondata-cmd="delete"data-cmd-data="LK0P-HN8GT-00LPD">Delete</button></span></div></div>

现在想象一下上面的例子中有数百条消息而不是这。

只要在事件处理程序设置之后,我们就可以处理所有的click 事件,并且只使用一个事件处理程序来处理所有按钮,即使是一个新消息。

function processCmd(event) {
 var $el = $(event.target);
 var cmd = $el.data("cmd");
 var cmdData = $el.data("cmdData");
 switch(cmd) {
 case"read":
 openMessage(cmdData);
 break;
 case"delete":
 deleteMessage(cmdData);
 break;
 }
}
$(document).ready(function() {
 $(".main-wrapper").on("click", "[data-cmd]", processCmd);
});

Angular 指令:iv-on-cmd

我的Angular 指令 iv-on-cmd 使用jQuery的委托功能来简化和减少所需的Angular 代码量。 它为你做了一些behind-the-scenes工作。 它指出命令 cmd 是什么和命令数据 cmdData,并将它的插入到 $event.data 对象中。 然后它将 $event 传递给你的处理程序。

下面的HTML示例在外部 <div> 上有 iv-on-cmd 指令。 这允许一个事件处理程序 processCmd() 处理来自三个子按钮的所有点击事件。

<divdata-ng-controller="myCtrl"data-iv-on-cmd="processCmd($event)"><buttondata-cmd="sayHello">Say Hello</button><buttondata-cmd="speak"data-cmd-data="Hi">Say Hi</button><buttondata-cmd="speak"data-cmd-data="Bye">Say Bye</button></div>

示例控制器 below 提供 processCmd() 函数,每当用户使用 data-cmd 属性单击其中一个按钮时就可以访问该函数。

angular.module("mine").controller("myCtrl", myCtrl);
myCtrl.$inject = ["$scope"];function myCtrl($scope) {
 $scope.processCmd = function($event) {
 $event.stopPropigation();
 $event.preventDefault();
 if ($event.data.cmd === "sayHello") {
 alert("Hello");
 return;
 }
 if ($event.data.cmd === "speak" ) {
 alert("Speaking:" + $event.data.cmdData);
 return;
 }
 }
}

在具有 data-cmd="speak"的按钮中,代码也将使用属性 data-cmd-data。 这里属性值被读取并放置到 $event.data 对象中以及 data-cmd 中的值。

对于这里按钮:

<buttondata-cmd="sayHello">Say Hello</button>

对象 $event.data 将是:

{
 "cmd": "sayHello",
 "cmdData": undefined
}

对于这里按钮:

<buttondata-cmd="speak"data-cmd-data="Hi">Say Hi</button>

对象 $event.data 将是:

{
 "cmd": "speak",
 "cmdData": "Hi"}

你还可以在 data-cmd-data 属性中包含对象。

对于这里按钮:

<buttondata-cmd="buy"data-cmd-data='{"title":"Test Product","price": 3.95}'>Buy Now</button>

对象 $event.data 将是:

{
"cmd":"buy",
"cmdData": {
"title":"Test Product",
"price": 3.95
 }
}
示例:菜单和工具栏

我有一个带有菜单,子菜单和工具栏的项目。 大多数菜单项都是由工具栏元素复制的。 因此用户可以使用菜单或者工具栏按钮执行相同的操作。

这是项目中的菜单和工具栏:

菜单和工具栏

这是带有子菜单打开的菜单:

菜单和工具栏

菜单是使用一个指令创建的,工具栏是使用第二个指令创建的。 但是这两个指令只是将属性 data-cmd 添加到DOM元素中,并没有处理click事件。 ( 除了打开子菜单打开和关闭菜单的菜单。)

菜单和工具栏包含在一个 <div> 中,并且在这个 <div> 上,我添加了如下所示的指令 iv-on-cmd:

<divdata-iv-on-cmd="processCmd($event)"><ulclass="menu"><li><buttondata-ng-click="toggle('batch')">Batch</button><ulclass="sub-menu"data-menu="batch"><li>...</li>. . .
 </ul></li><li><buttondata-ng-click="toggle('image')">Image</button><ulclass="sub-menu"data-menu="image"><li><buttondata-cmd="ruler">Ruler</button></li><li><buttondata-cmd="highlights">Highlights</button></li></ul></li>. . .
 </ul><divclass="toolbar"><buttonclass="toolbar__button"data-cmd="unto"><imgsrc="img/undo.png"></button><buttonclass="toolbar__button"data-cmd="redo"><imgsrc="img/redo.png"></button><spanclass="toolbar__separator"></span><buttonclass="toolbar__button"data-cmd="cut"><imgsrc="img/cut.png"></button><buttonclass="toolbar__button"data-cmd="copy"><imgsrc="img/copy.png"></button><buttonclass="toolbar__button"data-cmd="paste"><imgsrc="img/paste.png"></button><spanclass="toolbar__separator"></span>. . .
 </div></div>

控制器提供了一个单独的函数 processCmd(),它可以处理每个命令。

angular.module("mine").factory("myService", myService);function myService() {
 return {
 "undo": undoFn, 
 "redo": redoFn, 
 "cut": cutFn, 
 "copy": copyFn, 
 "paste": pasteFn 
 }
 function undoFn() {
 // Do something }
 function redoFn() {
 // Do something }
 function cutFn() {
 // Do something }
 function copyFn() {
 // Do something }
 function pasteFn() {
 // Do something }
}
angular.module("mine").controller("myCtrl", myCtrl);
myCtrl.$inject = ["$scope", "myService"];function myCtrl($scope, myService) {
 $scope.processCmd = function($event) {
 $event.stopPropigation();
 $event.preventDefault();
 var cmd = $event.data.cmd;
 if (myService.hasOwnProperty(cmd)) { // See is the service supports the command myService[cmd]($event.data.cmdData); // $event.data.cmd will default to undefined }
 else {
 // Display an error or throw an exception// The cmd is not supported in the service  }
 }
}

这是一个非常简单的例子。 但这表明我们可以生成简单的HTML来提供 data-cmd 属性。 然后,使用单个命令处理程序,我们可以处理这些命令。 在本例中,我还将处理命令的工作移到了服务中。 虽然你可能需要执行异步操作,或者从服务调用返回数据,这会改变编写代码的方式。

Points of Interest

对jQuery的需求而不是 jqLite

这里指令要求在加载 Angular 之前加载 jQuery。 因为jqLite不支持 $().on() 函数的委托模式,所以它不能与 Angular 中找到的jqLite一起工作。

jQuery版本 1.7或者 GREATER 是必需的,因为这些支持 $().on() 函数的委托模式。

<scriptsrc="jquery_min.js"></script><scriptsrc="angular_min.js"></script>

源代码

源代码可以作为Github项目的一部分使用: 角工具项目

你还可以直接转到 iv-on-cmd指令


angular  DIR  BIN  RED  event  Directive  
相关文章