结合ngModel到自定义指令 [英] Binding ngModel to a custom directive

查看:152
本文介绍了结合ngModel到自定义指令的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以,我一直在这个问题上了一个星期了,我似乎无法让我的头围绕这整个指令的事情。我看了很多帖子...

一堆视频...

和通过计算器和其他论坛去(链接遵循)希望会有些下沉......我认为我遇到的问题是,我想知道为什么/如何将这些工作,所以,我不切/粘贴别人的解决方案,为我的code,但随后不得不稍后再询问时,别的作物,因为我不知道我的贴code在做什么。

不过,我发现,每个人都有不同的方式对皮肤这只猫,他们都不给我的如何,这是应该工作的理解相匹配。

我所试图做的是使用 Metro UI的CSS 库建设的一种形式。我以为我会用一个简单的文本框开始。是的...只是一个的简单的文本框。地铁UI文本框也有一些不错的内置功能,我想preserve,所以我认为这是开始的好地方。

我看了,为了充分利用Metro UI的行为与AngularJS我需要将它包装在一个自定义指令(自定义数据 - 一个AngularJS NG重复内部指令)。虽然这个例子不是的究竟的东西我一直在寻找它似乎很容易解释什么,我需要做的。只要打电话,在指令的LINK功能适用行为的功能和指令属性添加到输入元素...

所以,我创建了一个名为metroInputTransform指令,并添加它作为一个输入元素的属性。

 < D​​IV数据-NG-控制器=pageOneFormCtrl为页>
    <输入类型=文本ID =txProductName
            数据-NG-模式=page.data.productName
            数据城域输入变换=
            占位=产品名称/>
< / DIV>

在该指令的LINK功能我干脆就叫适用我一直在寻找的行为方式。我知道,这是一个小更详细的比它需要,但我想了解它,所以我通过它加强作为最好的,我可以。 ......(全code看到这个小提琴

  VAR metroDirectives = angular.module('metroDirectives',[]);
    metroDirectives.directive('metroInputTransform',函数($编译){        功能postLink($范围,元件,ATTRS,控制器){            $(元件).inputTransform();
        };        返回{
            优先权:100,
            编译:功能(元素,ATTRS){                返回{postLink};
            }
        };
    });

所以这个工作,部分。它创造了地铁的外观和感觉以及相关的行为,但... ngModel不具约束力的元素。因此,这开始通过概念,如分离范围漫长的旅程,打破了各种编译器,控制器,pre-链接,链接后的功能,至少有两种不同的方式坚持ngModel的......所有这一切都没有工作。

在各种各样的阅读,这是我的理解是,DOM操作应该在编译功能发生,所以任何DOM转换将可用于编译和消化的过程,那么衔接阶段。让我感动的inputTransform()调用编译功能...(小提琴

  {回报
        优先权:100,
        终端:真,//如果我不把这个一切都将执行两次
        编译:功能(元素,ATTRS){            $(元件).inputTransform();            返回{
                pre:$ P $砰砰,
                岗位:postLink
            };
        }
    };

没有运气。同样的事情......不结合ngModel。所以我发现的分离范围的概念...

根据我尝试了以下(小提琴)...

  {回报
        优先权:100,
        范围: {
            ngModel:'='
        },
        终端:真,//如果我不把这个一切都将执行两次
        编译:功能(元素,ATTRS){            $(元件).inputTransform();            返回{
                pre:$ P $砰砰,
                岗位:postLink
            };
        }
    };

没有改变...

我尝试了一些其他的东西,但很害怕,如果我还没有准备好我可能很快就会失去你的关注。我得到的最接近是一个双向绑定做这样的事情......下面,甚至在这里你可以看到ngModel参考的提取是完全不能接受的。 (小提琴

  VAR metroDirectives = angular.module('metroDirectives',[]);
    metroDirectives.directive('metroInputTransform',函数(){        功能postLink($范围,元件,ATTRS,控制器){
            //
            //成功perfomes ONE-WAY结合(我需要双向),但显然非常
            //硬codeD。我想我可以写一个函数pasrsing将做到这一点
            //为无论他们分配给ngModel ......但是疗法emust是btter方式
                $(元素)。在(变,[数据城域网输入变换]',函数(E){
                    $范围。$应用(函数(){
                        $范围['页'] ['数据'] ['产品名称'] = e.currentTarget.value;
                    });
                });
        };        返回{
            优先权:100,
            终端:真,//如果我不把这个在这里编译将执行两次
            编译:功能(元素,ATTRS){                $(元件).inputTransform();                返回{
                    pre:函数($范围,元素,ATTRS,控制器,transcludeFn){},
                    岗位:postLink
                };
            }
        };
    });

我精疲力竭,完全不知道发生了什么留下来试试。我知道这是我的无知和缺乏了解关于如何/为什么AngularJS的工作方式是这样的事。但是每一篇文章我读给我留下的询问的许多问题,如得到的答复还是带我下了兔子洞,我在其中获得更多的失去比我当我开始。短活在人的研讨会,我买不起,我可以问我需要回答的问题下探$ 3000,我在用角一个完整的死胡同。

我将不胜感激,如果任何人都可以提供指导,方向......一个很好的资源......任何可以帮助阐明这个问题特别是一些光,但任何可能帮助我停止转动我的车轮。在平均时间我会继续阅读和重新阅读一切我能找到,希望事情会打破。

感谢

更新 - 二〇一四年十月三十〇日

我SOOOO过这个问题,但要遵循它通过。我需要和想要学习这一点。我也很希望前preSS AP preciation为乡亲们纷纷投入到这一点,虽然他们有presented一些解决方案,这最终可能是最好的方式去努力,他们都避开了问题,这是我尝试使用提供了Metro UI的CSS库的行为。我想preFER不有,如果可能重写他们。

到目前为止提供已经消除了从解决方案......这是该行的关键语句这两种解决方案...

  $(元素).inputTransform()

我不想张贴整个的jQuery插件,它包括了inputTransform的定义,但我切​​出来肉这里包括它...

 函数createInputVal(元素,名字,BUTTONNAME){        VAR包装= $(< D​​IV />中)。addClass(输入控制)addClass(名);
        VAR键= $(<按钮/>中)addClass(BUTTONNAME)。
        VAR克隆= element.clone(真); //克隆原始元素
        变种父= element.parent();        $(克隆).appendTo(包装);
        $(按钮).appendTo(包装);
        $(包装).insertBefore(元);
        $(元素)一个.remove(); //删除原始元件        返回包装;
    };

所以,我已经申请指令作为一个属性,因为它背后的地铁code要克隆文本框(这不会做,如果它是一个元素指令),然后删除原始输入元素。然后创建新的DOM元素并包装克隆输入元素在新创建的DIV容器。美中不足的,我相信是......,当原始元素被克隆,并从DOM中删除的结合正在被打破。是有道理的,如果NG-模式属性分配绑定到 引用文本框。所以,我原本的期望是,自吴模型的属性与元素的其余一起克隆,在该指令的编译事件/功能/相位参考将(重新)建立到新创建input元素。这显然​​不是这样。你可以看到这个更新的小提琴,我在NG-模型重新连接到新的DOM元素做了一些尝试没有成功。

也许这是不可能的......可以肯定的似乎只是重新构建这些东西可能最终会走的更简单的方法。

再次感谢米克Viitalia和azium...


解决方案

指令是不是最简单的概念,在那里和文档真的不是那么好,它的分散各地的interwebs。

我挣扎与编译 pre-编译键,这样,当我试着写我的第一个指令但到目前为止,我从来没有所需要的那些功能。这可能是由于我缺乏了解,但仍...

看着你的例子我看到有需要澄清一些基本的东西。首先,我想因为它替换HTML控件限制你的指令电子字元素。我会使用 A ttribute例如将功能添加到现有的控制。

有一个(强制)命名约定,你用虚线HTML和骆驼套你的JavaScript内命名。因此,的东西爽变成 somethingCool 。当你绑定变量指令的适用范围,有你如何做到这一点的主要区别。使用 = 绑定变量,采用 @ 来计算(字符串)值的变量。因此,首先允许双向绑定,但后者当然不是。您还可以使用&放大器; 绑定到父作用域的前pression /功能

如果您使用如平原 = 然后指令的范围预计在你的HTML相同的名称。如果您想使用不同的名字,那么你以后 = 添加变量名。一个例子

  ngModel:'='//< D​​IV NG-模型=数据>< / DIV>
otherVar:@someVar'//< D​​IV一些-VAR =数据>< / DIV>或<有的-VAR =数据>< /有的-VAR>

 

我把自由采取你的第一小提琴 metro-输入变换为出发点和Plunker 重写。我想在这里解释它(希望我理解你的权利)。

地铁输入指令

  directives.directive('metroInput',函数(){
  返回{
    限制:'E',
    范围: {
      ngModel:'=',
      占位符:@watermark
    },
    链接:功能(范围){
      scope.clear =功能(){
        scope.ngModel = NULL;
      };
    },
    templateUrl:地铁template.html
  };
});

指令预计 ngModel 绑定到和水印显示,当ngModel没有值(文字输入为空) 。在链接我介绍了清()用于指令后复位功能 ngModel 。当值复位,水印是表演。我已经分居了HTML部分到一个单独的文件中,城域template.html。

地铁输入HTML模板

 <输入类型=文本NG模型=ngModel占位符={{占位符}}>
<按钮式=按钮级=BTN-清NG点击=清除()> X< /按钮>

下面,我们结合 ngModel 输入和分配占位符。按钮显示[X]绑定到清()方法。

现在,当我们已经把我们的指令设置,使用它这里的HTML页面。

HTML页

 <身体GT;
  < D​​IV NG控制器=CTRL>
    <节>
      在指令的产品名称文本框
      字段集,并在'控制'&LT文本框; BR>
      字段集都应该是同步的。
    < /节>    < BR>    <&字段集GT;
      <传奇>指令< /传说>
      <标签=产品名称>产品名称< /标签>
      < BR>
      <城域输入名称=产品名称
                   NG-模式=data.productName
                   水印=产品名称>
      < /城域输入>
    < /字段集>    < BR>    <&字段集GT;
      <传奇>控制< /传说>
      <输入检测,鼠标悬停
             TYPE =文本
             NG-模式=data.productName>
    < /字段集>
  < / DIV>
< /身体GT;

因此​​,在地铁指令上面的例子用法如下。这将与指令的HTML模板替换。

 <城域输入名称=产品名称
             NG-模式=data.productName
             水印=产品名称>
< /城域输入>

其它输入具有检测-鼠标悬停适用于它,限制到 A ttribute指令只是为了显示用法/ A 电子之间的差异。鼠标检测指令使输入改变背景颜色,当鼠标移到/出来。

 <输入检测,鼠标悬停
       TYPE =文本
       NG-模式=data.productName>

  directives.directive('detectM​​ouseOver',函数(){
  返回{
    链接:功能(范围,元素,ATTRS){
      element.bind('的mouseenter',函数(){
        element.css('背景色','#eeeeee');
      });
      element.bind('鼠标离开',函数(){
        element.css('背景色','白');
      });
    }
  };
});

它还具有控制之间相同的 NG-模型,以反映变化。

在你的榜样,你也有一个 productService ,所提供的数值上面输入控件。我改写为

产品服务

  app.service('productService',函数(){
  返回{
    得到:函数(){
      返回{产品名称:从服务初始值};
    }
  };
});

所以的get()函数只是得到硬codeD值,但它仍然表明使用的服务。控制器,名为控制实在是过于简单。这里重要的是,你还记得所有的服务和这种注入到控制器。在这种情况下角的 $范围和我们自己的 productService

控制器

  app.controller(Ctrl键,功能($范围,productService){
  $ scope.data = productService.get();
});

 

下面上述方案的一个屏幕截图。

在任何两个输入值的变化不断变化的价值。下面输入了鼠标悬停所以它的灰色,鼠标移开将再次打开白色。 pressing [X]清除值,并使得占位符可见。

下面是plunker一次 http://plnkr.co/edit/GGGxp0

So I have been working on this issue for a week now and i cannot seem to get my head around this whole Directive thing. I have read lots of posts ...

a bunch of videos ...

And gone through StackOverflow and other forums (links to follow) hoping something will sink in ... I think that the problem that I am running into is that I want to UNDERSTAND why/how these work so that I am not cut/pasting someone else's solution into my code but then having to ask again later when something else crops up because I don't know what my pasted code is doing.

I am finding however that everyone has a different way to skin this cat and none of them seem to match up with my understanding of HOW this is supposed to work.

What I am attempting to do is build a form using the Metro UI CSS library. I thought I would start with a simple text-box. yep ... just a simple text box. A Metro UI text-box has some nice built in functionality that I wanted to preserve so I thought that was good place to start.

I read that in order to leverage Metro UI behaviors with AngularJS I would need to wrap it in a custom directive (Custom data-directives inside an AngularJS ng-repeat). While this example wasn't exactly what I was looking for it seemed to easily explain what I needed to do. Just call the function that applies the behavior in the LINK function of the directive and add the directive attribute to the input element ...

So I created a directive called 'metroInputTransform" and added it as an attribute to an input element.

<div data-ng-controller="pageOneFormCtrl as page">
    <input  type="text" id="txProductName" 
            data-ng-model="page.data.productName"
            data-metro-input-transform=""
            placeholder="product name" />
</div>

In the LINK function of the directive I simply called the method that applies the behavior I was looking for. I know that this is a little more verbose than it needs to be but I am trying to learn it so I am stepping through it as best as I can. ... (for full code see this fiddle)

var metroDirectives = angular.module('metroDirectives', []);
    metroDirectives.directive('metroInputTransform', function ($compile) {

        function postLink($scope, element, attrs, controller) {

            $(element).inputTransform();
        };

        return {
            priority: 100,
            compile: function (element, attrs) {

                return { postLink };
            }
        };
    });

So this worked, partially. It created the Metro look and feel and associated behavior, but ... ngModel was not binding to the element. So this began a long journey through concepts such as isolate scope, breaking out the various compile, controller, pre-link, post-link functions, at least two different ways of persisting ngModel ... all of which did not work.

After a variety of reading it was my understanding that the DOM manipulation should happen in the COMPILE function so that any DOM transformations would be available for the compile and then linking stages of the digest process. So I moved the inputTransform() call to the COMPILE function ... (fiddle)

    return {
        priority: 100,
        terminal: true,  // if I didn't put this everything would execute twice
        compile: function (element, attrs) {  

            $(element).inputTransform();

            return {
                pre: preLink,
                post: postLink
            };
        }
    };

No Luck ... same thing ... not binding to ngModel. So I discovered the concept of "isolate scope" ...

Based on that I tried the following (fiddle)...

    return {
        priority: 100,
        scope: {
            ngModel : '='
        },
        terminal: true,  // if I didn't put this everything would execute twice
        compile: function (element, attrs) {  

            $(element).inputTransform();

            return {
                pre: preLink,
                post: postLink
            };
        }
    };

No change ...

I tried a number of other things but am afraid I may lose you attention soon if I have not already. The closest I got was ONE-WAY binding doing something like below ... and even here you can see that the extraction of the ngModel reference is utterly unacceptable. (fiddle)

var metroDirectives = angular.module('metroDirectives', []);
    metroDirectives.directive('metroInputTransform', function () {

        function postLink($scope, element, attrs, controller) {
            //
            // Successfully perfomes ONE-WAY binding (I need two-way) but is clearly VERY 
            // hard-coded. I suppose I could write a pasrsing function that would do this
            // for whatever they assign to the ngModel ... but ther emust be a btter way
                $(element).on("change", '[data-metro-input-transform]', function(e) {
                    $scope.$apply(function(){
                        $scope['page']['data']['productName'] = e.currentTarget.value;
                    });
                });
        };

        return {
            priority: 100,
            terminal: true,  // if I didn't put this here the compile would execute twice
            compile: function (element, attrs) {  

                $(element).inputTransform();

                return {
                    pre: function ($scope, element, attrs, controller, transcludeFn) { },
                    post: postLink
                };
            }
        };
    });

I am EXHAUSTED and have absolutely no idea what's left to try. I know that this is a matter of my ignorance and lack of understanding on how/why AngularJS works the way it does. But every article I read leaves me asking as many questions as were answered or takes me down a rabbit hole in which I get more lost than I was when I started. Short of dropping $3000 on live in-person seminars that I cannot afford where I can ask the questions I need answered, I am at a complete dead end with Angular.

I would be most grateful if anyone could provide guidance, direction ... a good resource ... anything that can help shed some light on this issue in particular, but anything that might help me stop spinning my wheels. In the mean-time I will continue to read and re-read everything I can find and hopefully something will break.

Thanks

G

UPDATE - 10/30/2014

I am soooo over this issue but want to follow it through. I need and want to learn this. Also I really want to express appreciation for the effort that folks have put into this and while they have presented some solutions, which ultimately may be the best way to go, they have both skirted the issue, which is that I am attempting to use the behaviors provided with the Metro UI CSS library. I would prefer to not have to rewrite them if possible.

Both solutions provided so far have eliminated the key statement from the solution ... which is the line ...

$(element).inputTransform()

I don't want to post the entire jQuery widget that comprises the "inputTransform" definition, but I cut the meat of it out and included it here ...

    function createInputVal(element, name, buttonName) {

        var wrapper = $("<div/>").addClass("input-control").addClass(name);
        var button = $("<button/>").addClass(buttonName);
        var clone = element.clone(true); // clone the original element
        var parent = element.parent();

        $(clone).appendTo(wrapper);
        $(button).appendTo(wrapper);
        $(wrapper).insertBefore(element);
        $(element).remove(); // delete the original element

        return wrapper;
    };

So, I have applied the directive as an attribute because the Metro code behind it wants to CLONE the text-box (which would not do if it was an element directive) and then REMOVES the original input element. It then creates the new DOM elements and wraps the cloned input element in the newly created DIV container. The catch, I believe is ... that the binding is being broken when the original element is being cloned and removed from the DOM. Makes sense, if the "ng-model" attribute assignment is bound to a reference of the text-box. So the expectation that I originally had was, since the "ng-model" attribute was cloned along with the rest of the element, that in the compile event/function/phase of the directive the reference would be(re)established to the newly created input element. This apparently was not the case. You can see in this updated fiddle that I have made some attempts at reconnecting the ng-model to the new DOM elements with no success.

Perhaps this is impossible ... it certainly seems that just re-building these things may ultimately be the easier way to go.

Thanks again Mikko Viitalia and 'azium' ...

解决方案

Directives are not the easiest concepts out there and documentation is really not that good and it's scattered around the interwebs.

I struggled with compile, pre-compile and such when I tried to write my first directives but to date I have never needed those functions. It might be due to my lack of understanding but still...

Looking at your examples I see there's some basic things that needs clarification. First of all, I'd restrict your directive to Element since it's replacing the control in HTML. I'd use Attribute e.g. to add functionality to existing control.

There is a (mandatory) naming convention where you use dashed naming in HTML and camel casing inside your JavaScript. So something-cool becomes somethingCool. When you "bind" variables to directive's scope, there's a major difference on how you do it. Using = you bind to variable, using @ to variables evaluated (string) value. So first allows the "two-way binding" but latter of course, not. You can also use & to bind to parent scope's expression/function.

If you use e.g. plain = then directive's scope expects same name in your HTML. If you wish to use different name, then you add variable name after the =. An example

ngModel : '='        // <div ng-model="data"></div>
otherVar: '@someVar' // <div some-var="data></div> or <some-var="data"></some-var>

 

I took liberty to take your first Fiddle of metro-input-transform as starting point and rewrite it in Plunker. I'm trying to explain it here (and hope I understood you right).

Metro input directive

directives.directive('metroInput', function () {
  return {
    restrict: 'E',
    scope: {
      ngModel: '=',
      placeholder: '@watermark'
    },
    link: function (scope) {
      scope.clear = function () {
        scope.ngModel = null; 
      };
    },
    templateUrl: 'metro-template.html'
  };
});

Directive expects ngModel to bind to and watermark to show when ngModel has no value (text input is empty). Inside link I've introduced clear() function that is used within directive to reset ngModel. When value is reset, watermark is show. I have separated the HTML parts into a separate file, metro-template.html.

Metro input HTML template

<input type="text" ng-model="ngModel" placeholder="{{ placeholder }}">
<button type="button" class="btn-clear" ng-click="clear()">x</button>

Here we bind ngModel to input and assign placeholder. Button showing [X] is bound to clear() method.

Now when we have our directive set up, here's the HTML page using it.

HTML page

<body>
  <div ng-controller="Ctrl">
    <section>
      The 'Product name' textbox in the 'Directive' 
      fieldset and the textbox in the 'Controls'<br>
      fieldset should all be in sync. 
    </section>

    <br>

    <fieldset>
      <legend>Directive</legend>
      <label for="productName">Product name</label>
      <br>
      <metro-input name="productName" 
                   ng-model="data.productName"
                   watermark="product name">
      </metro-input>
    </fieldset>

    <br>

    <fieldset>
      <legend>Control</legend>
      <input detect-mouse-over
             type="text" 
             ng-model="data.productName">
    </fieldset>
  </div>
</body>

So in above example usage of metro directive is as follows. This will be replaced with directive's HTML template.

<metro-input name="productName" 
             ng-model="data.productName" 
             watermark="product name">
</metro-input>

The other input has detect-mouse-over directive applied to it, restricted to Attribute just to show usages/differences between A and E. Mouse detection directive makes input change background-color when mouse is moved over/out of it.

<input detect-mouse-over
       type="text" 
       ng-model="data.productName">

.

directives.directive('detectMouseOver', function () { 
  return {
    link: function (scope, element, attrs) {
      element.bind('mouseenter', function () {
        element.css('background-color', '#eeeeee');
      });
      element.bind('mouseleave', function () {
        element.css('background-color', 'white'); 
      });
    }
  };
});

It also has same ng-model to mirror changes between controls.

In your example you also had a productService that provided the value to above input controls. I rewrote it as

Product service

app.service('productService', function () {
  return {
    get: function () {
      return { productName: 'initial value from service' };
    }
  };
});

So get() function just gets the hard coded value but it still demonstrates use of services. Controller, named Ctrl is really simplistic. Important part here is that you remember to inject all services and such into your controller. In this case angular's $scope and our own productService.

Controller

app.controller('Ctrl', function ($scope, productService) {
  $scope.data = productService.get();
});

 

Here a screen capture of above solution.

Changing value in any of the inputs changes value of both. Input below has "mouseover" so it's greyish, mouseout would turn it white again. Pressing [X] clears the value and makes placeholder visible.

Here's the link to plunker once more http://plnkr.co/edit/GGGxp0

这篇关于结合ngModel到自定义指令的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆