简体   繁体   English

将ngModel绑定到自定义指令

[英]Binding ngModel to a custom directive

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. 并通过StackOverflow和其他论坛(链接跟随)希望有什么东西会沉入......我认为我遇到的问题是我想要理解为什么/如何这些工作,以便我不会切割/粘贴某人别的解决方案进入我的代码,但后来不得不再问一些其他东西,因为我不知道我的粘贴代码在做什么。

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. 我试图做的是使用Metro UI CSS库构建表单。 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. Metro UI文本框有一些很好的内置功能,我想保留,所以我认为这是一个很好的起点。

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 ). 我读到为了利用AngularJS的Metro UI行为,我需要将它包装在自定义指令中( 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 ... 只需调用在指令的LINK函数中应用行为的函数,并将指令属性添加到input元素中......

So I created a directive called 'metroInputTransform" and added it as an attribute to an input element. 所以我创建了一个名为'metroInputTransform'的指令,并将其作为属性添加到输入元素中。

<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. 在指令的LINK函数中,我简单地调用了应用我正在寻找的行为的方法。 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. 它创建了Metro外观和相关行为,但是...... ngModel没有绑定到该元素。 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. 因此,这开始了一个漫长的旅程,通过隔离范围,打破各种编译,控制器,预链接,后链接功能,至少两种不同的方式来持久化ngModel ......所有这些都无效。

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. 经过各种阅读后,我的理解是DOM操作应该在COMPILE函数中进行,这样任何DOM转换都可用于编译,然后链接摘要过程的各个阶段。 So I moved the inputTransform() call to the COMPILE function ... ( fiddle ) 所以我将inputTransform()调用移动到COMPILE函数...( 小提琴

    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. 没有运气......同样的事情......没有绑定到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. 我得到的最接近的是ONE-WAY绑定做类似下面的事情......甚至在这里你可以看到ngModel引用的提取是完全不可接受的。 ( 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. 我知道这是我的无知和对AngularJS如何/为何如此工作的理解缺乏的问题。 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. 如果我无法承担我需要回答的问题而无法负担现场研讨会上的3000美元,那么我对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 G

UPDATE - 10/30/2014 更新 - 2014年10月30日

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. 另外,我真的要感谢人们为此付出的努力,虽然他们提出了一些解决方案,最终可能是最好的方法,但他们都避开了这个问题,即我试图使用Metro UI CSS库提供的行为。 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 ... 我不想发布包含“inputTransform”定义的整个jQuery小部件,但是我把它的内容剪掉了,并把它包含在这里......

    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. 因此,我已将该指令应用为属性,因为它背后的Metro代码想要CLONE文本框(如果它是元素指令则不会这样做)然后删除原始输入元素。 It then creates the new DOM elements and wraps the cloned input element in the newly created DIV container. 然后,它创建新的DOM元素,并将克隆的输入元素包装在新创建的DIV容器中。 The catch, I believe is ... that the binding is being broken when the original element is being cloned and removed from the DOM. 我相信这个问题是......当克隆原始元素并从DOM中删除时,绑定被破坏了。 Makes sense, if the "ng-model" attribute assignment is bound to a reference of the text-box. 如果“ng-model”属性赋值绑定到文本框的引用 ,则有意义。 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. 因此,我最初的期望是,因为“ng-model”属性与元素的其余部分一起被克隆,所以在指令的编译事件/函数/阶段中,引用将被重新建立到新的创建了输入元素。 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. 您可以在这个更新的小提琴中看到,我已经尝试将ng-model重新连接到新的DOM元素但没有成功。

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' ... 再次感谢Mikko Viitalia和'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. 我努力compilepre-compile等等,当我尝试编写我的第一个指令时,但到目前为止我从未需要这些函数。 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 E lement since it's replacing the control in HTML. 首先,我将你的指令限制在E lement,因为它取代了HTML中的控件。 I'd use A ttribute eg to add functionality to existing control. 我使用A ttribute例如为现有控件添加功能。

There is a (mandatory) naming convention where you use dashed naming in HTML and camel casing inside your JavaScript. 有一个(强制)命名约定,您在JavaScript中使用HTML和camel大小写中的虚线命名。 So something-cool becomes somethingCool . 因此something-cool somethingCool变成somethingCool something-cool 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. 使用=绑定到变量,使用@到变量evaluate(string)值。 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 eg plain = then directive's scope expects same name in your HTML. 如果您使用例如plain =那么指令的范围在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 . 我冒昧地将你的第一个 metro-input-transform 小提琴作为起点, 并在Plunker中重写它 I'm trying to explain it here (and hope I understood you right). 我想在这里解释一下(希望我理解你的权利)。

Metro input directive Metro输入指令

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). 指令期望ngModel绑定到watermark以显示ngModel何时没有值(文本输入为空)。 Inside link I've introduced clear() function that is used within directive to reset ngModel . 在内部link我引入了clear()函数,该函数在指令中用于重置ngModel When value is reset, watermark is show. 重置值时,显示watermark I have separated the HTML parts into a separate file, metro-template.html. 我已将HTML部分分成单独的文件metro-template.html。

Metro input HTML template Metro输入HTML模板

<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 . 在这里,我们将ngModel绑定到输入并分配placeholder Button showing [X] is bound to clear() method. 显示[X]的按钮绑定到clear()方法。

Now when we have our directive set up, here's the HTML page using it. 现在,当我们设置指令时,这是使用它的HTML页面。

HTML page HTML页面

<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. 因此在上面的示例中,metro指令的用法如下。 This will be replaced with directive's HTML template. 这将被指令的HTML模板替换。

<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 A ttribute just to show usages/differences between A and E . 另一个输入具有应用于它的detect-mouse-over指令,仅限于A ttribute以显示AE之间A用法/差异。 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. 它也具有相同的ng-model来反映控件之间的变化。

In your example you also had a productService that provided the value to above input controls. 在您的示例中,您还有一个productService ,它为上面的输入控件提供了值。 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. 所以get()函数只获取硬编码值,但它仍然证明了服务的使用。 Controller, named Ctrl is really simplistic. 名为Ctrl控制器非常简单。 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 . 在这种情况下,angular的$scope和我们自己的productService

Controller 调节器

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

Here a screen capture of above solution. 这里是上述解决方案的屏幕截图。

imgur

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. 按[X]清除值并使占位符可见。

Here's the link to plunker once more http://plnkr.co/edit/GGGxp0 这是再次访问plunker的链接http://plnkr.co/edit/GGGxp0

Ok I'm not exactly sure what other advantages from the Metro UI you're getting, but here's a simple fiddle that doesn't need your directive at all to capture what you had in your first fiddle that works for me. 好吧,我不确定你所获得的Metro UI有哪些其他优点,但这里有一个简单的小提琴,根本不需要你的指令来捕捉你在你的第一小提琴中所拥有的东西。 http://jsfiddle.net/f0sph1vp/7/ http://jsfiddle.net/f0sph1vp/7/

<input placeholder="{{page.placeholder}}"
       ng-model="page.data.productName"  
       ng-focus="page.data.productName=''">
<button ng-click="page.data.productName=''">x</button>

The second fiddle you posted, http://jsfiddle.net/gary_stenstrom/xcx2y8uk/64/ , is pretty weird to me, because it doesn't seem like you want the second input box to be the same model as your first one. 您发布的第二个小提琴, http://jsfiddle.net/gary_stenstrom/xcx2y8uk/64/ ,对我来说非常奇怪,因为您似乎不希望第二个输入框与您的第一个输入框相同。 It kind of seems like you want the clicking of the x button to assign the value of the first input to the second. 看起来你想要点击x按钮将第一个输入的值分配给第二个输入。 Which makes a lot more sense. 这更有意义。

<input ng-model="data.first">
<button ng-click="data.second = data.first; data.first=''">X</button
<input ng-model="data.second">

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM