Combining AngularJS with existing Components(angularjs 中使用已有的控件)

This is a preview-quality chapter of our continuously deployed eBook on AngularJS for .NET developers. You can read more about the project at http://henriquat.re. You can also follow us on twitter (Project:@henriquatreJS, Authors: @ingorammer and @christianweyer)

Combining AngularJS with existing Components

A common question when looking at AngularJS for the first time is about how to combine it with existing JavaScript code and components. For jQuery UI in particular, the team around AngularJS has created the AngularUI project (http://angular-ui.github.com/) which provides an easy way to use jQuery UI with Angular.). For other frameworks - or especially for your custom DOM-manipulating code - the necessary steps might not be as obvious at the first glance.

As you have seen in the previous chapters, AngularJS puts a very strong focus on separation of concerns:

  • Controllers expose model objects and methods to a view (via the use of $scope). They contain view-specific code without any DOM manipulations.
  • Services contain methods which perform view-independent operation. Business-logic code which is used by multiple views and/or in different contexts is right at home here. (Note: some global services might even operate on the DOM level -- for example to show modal dialog boxes or similar. These manipulations should however be independent from of concrete view in your application.)
  • Directives are re-usable components which bind model values to DOM properties and react on DOM events to update model values

Most existing components (especially in the jQuery-world) usually work in a different way. Without Angular, when you wanted to achieve a certain behavior, like displaying a date-picker, you would usually go with a jQuery UI extension (like ‘datepicker‘) and use call similar to $("#someinput").datepicker({...}). This would extend a standard <input> element with an ID of someinput to be turned into a datepicker.

In this command‘s options, you would have usually specified a callback to be invoked whenever the user selects/changes the date in the input box. But you wouldn‘t do this just for a single datepicker -- no, this kind of jQuery UI-extension would be littered throughout your business code for nearly every input control in your HTML. After all, your datepickers need access to the underlying model values to restrict the input to valid ranges and to perform additional validation if necessary. This mix of business code and DOM-manipulating code is sometimes the reason for maintenance headaches of JavaScript developers.

With this in mind, you can see the conundrum: how do you take classic JavaScript code (with this mix of DOM interactions and event handlers with direct manipulation of underlying business objects) and put it into the well defined structure of AngularJS?

So let‘s just see how we‘re going to take a jQuery-based fragment and move it forward to a reusable AngularJS directive. In the following example, you‘ll see a simple text entry box which is treated as a jQuery UI datepicker. When changing the date, the underlying business object will be updated and the value will be shown underneath the input box.

Edit in JSFiddle

This demonstration consists mainly of two parts. The first is the rather straightforward and easy to understand HTML markup:

<div>
    Date Of Birth:
    <input type="text" id="dateOfBirth">
    <br>
    Current user‘s date of birth:
    <span id="dateOfBirthDisplay"></span>
</div>

So far, so good. Now let‘s have a look at the jQuery-based code behind this HTML:

$(function () {
   var user = {
      dateOfBirth: new Date(1970, 0, 1)
   };

   var displayValue = function () {
      $("#dateOfBirthDisplay").text(new Date(user.dateOfBirth).toDateString());
   };

   var processChange = function() {
      user.dateOfBirth = $("#dateOfBirth").datepicker("getDate");
      displayValue();
   };

   $("#dateOfBirth").datepicker({
         inline: true,
         onClose: processChange,
         onSelect: processChange
      }
   );

   displayValue();

   // initial display of value in input-box
   $("#dateOfBirth").datepicker("setDate", user.dateOfBirth);

});

Please note that this fragment, even though it already mixes code from different areas of responsibility (DOM manipulation, data conversion, business object population) is hardly complete: in practice, you‘d quite likely also have to add validation code to check for date formats and ranges. But let‘s keep this simple for now.

Now, if code like this would only occur once throughout your application, its use could be quite acceptable. Unfortunately, without a framework like AngularJS, code like this would be written in multiple throughout your application. In fact, whenever you‘d have a date-entry box, you would quite likely see code like this. As you can imagine: maintenance of a codebase like this might be quite a bit harder than it should be.

As you‘ve seen before, AngularJS allows you to separate the concerns if your code into three different parts: the model, the controller and directives which perform DOM manipulation. So let‘s look at this particular example and how we can transform this into a more suitable - and maintainable - form.

Separating the Concerns

At first, we will change the HTML markup to tell AngularJS which internal application name should be used (as we will register the directives with this application). In addition, we‘ll already add the necessary data-binding information and add the directive my-datepicker instead of the <input>-element which we‘ve used before. (Please note: this particular directive does not yet exist in AngularJS, but we‘ll build it throughout the remainder of this chapter.)

<div ng-app="demo" ng-controller="DemoController">
    Date Of Birth:
    <my-datepicker type="text" ng-model="user.dateOfBirth" />
    <br>
    Current user‘s date of birth:
    <span id="dateOfBirthDisplay">{{user.dateOfBirth}}</span>
</div>

The matching controller simply exposes the user property via its $scope and we have removed all interactions with jQuery UI from this business logic code:

function DemoController($scope) {
   $scope.user = {
      dateOfBirth: new Date(1970, 0, 1)
   }
}

Creating the Directive

To create the link to jQuery UI‘s datepicker-addin, we are introducing the following directive. (Worry not: we‘ll discuss this directive line-by-line just in a few seconds.)

angular.module("demo", []).directive(‘myDatepicker‘, function ($parse) {
   return {
      restrict: "E",
      replace: true,
      transclude: false,
      compile: function (element, attrs) {
         var modelAccessor = $parse(attrs.ngModel);

         var html = "<input type=‘text‘ id=‘" + attrs.id + "‘ >" +
            "</input>";

         var newElem = $(html);
         element.replaceWith(newElem);

         return function (scope, element, attrs, controller) {

            var processChange = function () {
               var date = new Date(element.datepicker("getDate"));

               scope.$apply(function (scope) {
                  // Change bound variable
                  modelAccessor.assign(scope, date);
               });
            };

            element.datepicker({
               inline: true,
               onClose: processChange,
               onSelect: processChange
            });

            scope.$watch(modelAccessor, function (val) {
               var date = new Date(val);
               element.datepicker("setDate", date);
            });

         };

      }
   };
});

Contrary to the example which you‘ve seen in Introduction to Directives, we‘re not simply returning alink function. Instead, we‘re returning a so called compile function for this directive.

The Compile Function

The reason for this is based on the internal logic in which directives are applied by Angular: in the first phase, the compile phase, you can modify the HTML-element which will be added to the DOM at the location of your directive. You can for example emit a completely different HTML element. In the second phase, during linking, you can change the content and behavior of the element after the result of the compilation has been added to the DOM.

The important thing to note is that, if your directive uses a compile function, it is required to return thelink function which should be called at a later time.

So let‘s look at the individual elements of our directive, step by step.

First, we‘re defining a module demo (which we will later reference from the HTML) and we‘re adding a directive called myDatepicker to it. We require a reference to $parse to be injected in the code so that we can later use this to parse model-binding expressions.

angular.module("demo", [])
.directive(‘myDatepicker‘, function ($parse) {

We then indicate that our directive will be used as an HTML-elements (and not as an attribute or CSS-class).

restrict: "E"

We then tell AngularJS that we want to replace the element with the result of our directive but don‘t require automatic transclusion of the content. (You can read more about transclusion at Introduction to Directives).

  replace: true,
  transclude: false,

The remainder of the directive is the compile function. Let‘s first look at the compilation inside this function. At the beginning of this method, we use $parse to parse the ng-model attribute which has been specified in the HTML markup (it contains the target field for data binding).

$parse converts (or compiles) an AngularJS expression into a function. If we for example specify "user.dateOfBirth" as an expression, then $parse will return a function which allows us to retrieve and set this value from the underlying scope. (Please note: as always with AngularJS, the naming convention islowercase-with-dashes in HTML and camelCase in JS, so that the JavaScript field attrs.ngModel will contain the string-contents of the HTML-attribute ng-model).

compile: function (element, attrs) {
    var modelAccessor = $parse(attrs.ngModel);

After this, we use jQueryOnly (or the AngularJS-supplied minimum jQuery equivalent) to create an<input>-element and then replace the existing directive element (which temporarily exists in the HTML) with this new <input>. (And while doing this, we‘re preserving the HTML element‘s ID from the HTML element which defined this directive.)

    var html = "<input type=‘text‘ id=‘" + attrs.id + "‘ >" +
                "</input>";
     var newElem = $(html);
     element.replaceWith(newElem);

The Link Function

Up to this point, we‘ve looked only at the compilation part. What‘s next is that we‘re returning the linkfunction from this compile function:

    return function (scope, element, attrs, controller) {
            /* ... */
    };

In this returned link-function, we‘re doing three things:

  • define a function which will be called as the datepicker‘s onClose and onSelect callbacks.
  • use jQuery UI‘s datepicker() extension to add the desired behavior to the underyling <input>-element
  • watch the model for changes to update the datepicker‘s display whenever the model changes

Let‘s first have a look at the function which will later be used for the datepicker‘s callbacks.

        var processChange = function () {
           var date = new Date(element.datepicker("getDate"));
           scope.$apply(function (scope) {
              modelAccessor.assign(scope, date);
           });
        };

The important parts here happen in the call to scope.$apply. This method is used to change model values in a way which allows AngularJS‘ change tracking to update the underlying HTML elements. (Or more correctly, it triggers the watches which have been registered for the model).

The modelAccessor we‘re using in this method is the return value of the earlier call to $parse(...): its assignmethod is a preprocessed accessor function which sets the configured (according to the ng-model attribute on the HTML directive) field of the corresponding object to the specified value.

To recap: whenever this processChange-function is called, it will extract the current value from the HTML element which underpins the datepicker by using its getDate access-method. It will then apply the changes to the underlying model by using the assign() method of a pre-parsed accessor within an $apply-block.

In the next step, we simply use jQuery UI‘s datepicker extension to get the desired client-side behavior. We pass the processChange- function which we‘ve defined above as the callbacks for onClose and onSelect. In this way processChanges will be called whenever the user changes the datepicker‘s value.

        element.datepicker({
           inline: true,
           onClose: processChange,
           onSelect: processChange
        });

And as the final step, we watch the model for changes so that we can update the UI as required. To do this, we pass the modelAccessor (which we‘ve received as the return value from $parse) to Angular‘s $watch method, which is part of the current scope.

The callback to $watch will be triggered whenever the model value has been changed. (By using $apply or by updating a bound UI control).

        scope.$watch(modelAccessor, function (val) {
           var date = new Date(val);
           element.datepicker("setDate", date);
        });

The HTML

In this way, we have now defined a re-usable directive which works independent from any GUI. In fact, this component could easily be re-used in a completely different AngularJS application.

If you run this initial version of the AngularJS application, you can see the following behavior:

Edit in JSFiddle

At this point, the data-binding works as expected, we have a very clear separation of concerns (there is absolutely no direct binding between your business code in the Controller and jQuery UI‘s datepicker.)

This was a preview-quality chapter of our continuously deployed eBook on AngularJS for .NET developers. If you enjoyed this chapter, you can read more about the project at http://henriquat.re. You can also follow us on twitter (Project: @henriquatreJS, Authors: @ingorammer and @christianweyer)

(source : http://henriquat.re/directives/advanced-directives-combining-angular-with-existing-components-and-jquery/angularAndJquery.html)

时间: 2024-10-01 04:20:05

Combining AngularJS with existing Components(angularjs 中使用已有的控件)的相关文章

【经验】Angularjs 中使用 layDate 日期控件

layDate 控件地址:http://laydate.layui.com/ 前情:原来系统中使用的日期控件是UI bootstrap(地址:https://angular-ui.github.io/bootstrap/)里的.后来因为各种原因,要换掉UI bootstrap中的日期控件,改用layDate日期控件. 解决思路:将layDate的初始化及相关代码定义在指令里. 问题关键点:layDate操作的是Html元素的,怎么实现双向绑定,同步Angularjs模板值和Html的元素值. 指

android在代码中四种设置控件背景颜色的方法(包括RGB)

转载请注明出处: http://blog.csdn.net/fth826595345/article/details/9208771  TextView tText=(TextView) findViewById(R.id.textv_name); //第1种: tText.setTextColor(android.graphics.Color.RED);//系统自带的颜色类 // 第2种: tText.setTextColor(0xffff00ff);//0xffff00ff是int类型的数据

在现有WinForms应用中添加C1Themes主题控件

在本博客中,展示了如何在现有的WinForms应用中添加C1Themes控件支持. 本文使用名为C1dView Samples的应用程序,它包含C1Reports.它是基于C1Ribbon界面设计的C1Report/C1PrintDocument浏览应用程序.该应用最初没有包含主题的支持. C1DView示例下载:C1dView.zip (94.96 kb) (下载次数: 7) 如下描述的简单的步骤,为最终用户提供在程序中选择使用包含于C1Themes控件产品中的若干主题的支持.(请注意:在进行

转载 [WPF][C#]在WPF中内嵌WindowsForm控件-使用WindowsFormsControlLibrary

[WPF][C#]在WPF中内嵌WindowsForm控件-使用WindowsFormsControlLibrary 在[WPF][C#]在WPF中内嵌WindowsForm控件一文中为各位介绍了直接在WPF中使用XAML来嵌入Windows Form控件的作法,不过不是每个人都喜欢写XAML,而且有时候会需要把已经存在的Windows Form应用程序嵌入到WPF中,所以这次就来跟大家介绍怎么使用参考dll档的方式,把dll中的Windows Form加到WPF中. 都说了要使用Windows

winform中,如何控制控件位置随窗体的大小改变而改变

winform中,如何控制控件位置随窗体的大小改变而改变 有如下3种方法: 方法1 [csharp] view plaincopy using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace MarkPrint

winfrom中DataGridView绑定数据控件中DataGridViewCheckBoxColumn怎么选中

for (int i = 0; i < this.dataGridView1.Rows.Count; i++) { this.dataGridView1.Rows[i].Cells["CheckBoxCulums"].Value = this.checkBox1.Checked; } winfrom中DataGridView绑定数据控件中DataGridViewCheckBoxColumn怎么选中,布布扣,bubuko.com

从数据池中捞取的存储过程控件使用完以后必须unprepare

从数据池中捞取的存储过程控件使用完以后必须unprepare,否则会造成输入参数是仍是旧的BUG. 提示:动态创建的存储过程控件无此BUG.此BUG只限于从数据池中捞取的存储过程控件. function TServerMethods1.spOpen(const accountNo, spName: WideString; inParams: OleVariant): OleVariant;var d: TfrmDB; params: TParams; i: Integer; param: TFD

使用VC2008中ATL开发浏览器控件

http://blog.csdn.net/cnjet/article/details/6218355 使用VC2008中ATL开发浏览器控件 2011.03.02 [email protected] 介绍 本文将介绍使用VC2008中的ATL开发一个用于网络部署的cab控件包的过程. 建立ATL项目 打开VS2008,建立一个ATL项目,如下图: 考虑到简介性,选择了"Allow merging of proxy/stub code" 添加ATL控件 (建议先编译一下真个project

可以在Word的mail-merge中使用的条形码控件Data Matrix Font and Encoder

最近发现MDT推出去的系统的有不同问题,其问题就不说了,主要是策略权限被域继承了.比如我们手动安装的很多东东都是未配置壮态,推的就默认为安全壮态了,今天细找了一下,原来把这个关了就可以了. 可以在Word的mail-merge中使用的条形码控件Data Matrix Font and Encoder