javascript 将 ngModel 绑定到自定义指令

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26537960/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 06:06:43  来源:igfitidea点击:

Binding ngModel to a custom directive

javascripthtmlangularjsangularjs-directiveangularjs-scope

提问by Gary O. Stenstrom

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 和其他论坛(要遵循的链接),希望有一些东西会沉没......我认为我遇到的问题是我想了解这些为什么/如何工作,这样我就不会剪切/粘贴某人else 的解决方案到我的代码中,但是当其他东西突然出现时不得不再次询问,因为我不知道我粘贴的代码在做什么。

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 CSSlibrary. I thought I would start with a simple text-box. yep ... just a simpletext 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 CSS库构建一个表单。我想我会从一个简单的文本框开始。是的...只是一个简单的文本框。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). While this example wasn't exactlywhat 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 ...

我读到为了在 AngularJS 中利用 Metro UI 行为,我需要将它包装在自定义指令中(AngularJS ng-repeat中的自定义数据指令)。虽然这个例子不正是我一直在寻找它似乎很容易解释什么,我需要做的。只需调用在指令的 LINK 函数中应用行为的函数,并将指令属性添加到输入元素...

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. 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)

在指令的 LINK 函数中,我只是调用了应用我正在寻找的行为的方法。我知道这比它需要的要冗长一些,但我正在努力学习它,所以我正在尽我所能地逐步完成它。...(有关完整代码,请参阅此小提琴

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.

所以这部分奏效了。它创建了 Metro 外观和相关行为,但是 ... ngModel 未绑定到元素。所以这开始了一个漫长的旅程,比如隔离作用域、分解各种编译、控制器、预链接、后链接功能,至少两种不同的持久化 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. So I moved the inputTransform() call to the COMPILE function ... (fiddle)

经过各种阅读后,我的理解是 DOM 操作应该发生在 COMPILE 函数中,以便任何 DOM 转换都可用于编译和摘要过程的链接阶段。所以我将 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. So I discovered the concept of "isolate scope" ...

没有运气......同样的事情......不绑定到ngModel。所以我发现了“隔离范围”的概念......

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)

我尝试了很多其他的东西,但如果我还没有的话,恐怕我很快就会失去你的注意力。我得到的最接近的是单向绑定,执行如下所示的操作......即使在这里您也可以看到 ngModel 引用的提取是完全不可接受的。(小提琴

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 askingas 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.

我筋疲力尽,完全不知道还有什么可以尝试的。我知道这是我对 AngularJS 如何/为什么以这种方式工作的无知和缺乏理解的问题。但是我读过的每一篇文章都让我提出的问题和得到的答案一样多,或者让我掉进了一个兔子洞,在那里我比开始时迷失了更多。除了在现场面对面的研讨会上投入 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

更新 - 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.

我对这个问题很感兴趣,但想坚持到底。我需要并想学习这个。此外,我真的很想对人们为此付出的努力表示感谢,虽然他们提出了一些解决方案,这最终可能是最好的方法,但他们都避开了这个问题,即我正在尝试使用Metro UI CSS 库提供的行为。如果可能,我宁愿不必重写它们。

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. 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 referenceof 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 fiddlethat I have made some attempts at reconnecting the ng-model to the new DOM elements with no success.

因此,我已将该指令作为属性应用,因为它背后的 Metro 代码想要克隆文本框(如果它是元素指令,则不会这样做),然后删除原始输入元素。然后它创建新的 DOM 元素并将克隆的输入元素包装在新创建的 DIV 容器中。我认为问题在于……当原始元素被克隆并从 DOM 中删除时,绑定被破坏了。有道理,如果“ng-model”属性赋值绑定到一个引用的文本框。所以我最初的期望是,因为“ng-model”属性与元素的其余部分一起被克隆,在指令的编译事件/函数/阶段中,引用将(重新)建立到新的创建的输入元素。情况显然并非如此。您可以在这个更新的小提琴中看到,我已经尝试将 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' ...

采纳答案by Mikko Viitala

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-compileand 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...

当我尝试编写我的第一个指令时,我遇到了compile,pre-compile等问题,但迄今为止我从未需要这些函数。这可能是因为我缺乏理解,但仍然......

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.

看看你的例子,我发现有一些基本的事情需要澄清。首先,我E会将您的指令限制为lement,因为它正在替换 HTML 中的控件。我会使用Attribute 例如为现有控件添加功能。

There is a (mandatory) naming convention where you use dashed naming in HTML and camel casing inside your JavaScript. So something-coolbecomes 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.

有一个(强制性的)命名约定,您可以在 HTML 中使用虚线命名并在 JavaScript 中使用驼峰式大小写。于是something-cool变成somethingCool。当您将变量“绑定”到指令的作用域时,您的操作方式存在重大差异。使用=您绑定到变量,使用@到变量评估(字符串)值。所以首先允许“双向绑定”,但后者当然不允许。您还可以&用于绑定到父作用域的表达式/函数。

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

如果您使用例如plain,=那么指令的范围需要在您的HTML 中使用相同的名称。如果您希望使用不同的名称,则在=. 一个例子

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 Fiddleof metro-input-transformas starting point and rewrite it in Plunker. I'm trying to explain it here (and hope I understood you right).

我冒昧地将您的第一个 Fiddleofmetro-input-transform作为起点并在 Plunker 中重写它。我试图在这里解释它(希望我理解你的意思)。

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 ngModelto bind to and watermarkto show when ngModel has no value (text input is empty). Inside linkI've introduced clear()function that is used within directive to reset ngModel. When value is reset, watermarkis show. I have separated the HTML parts into a separate file, metro-template.html.

当 ngModel 没有值(文本输入为空)时,指令期望ngModel绑定并watermark显示。在里面,link我介绍了clear()在指令中使用的函数 reset ngModel。当值被重置时,watermark显示。我已将 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 ngModelto input and assign placeholder. Button showing [X] is bound to clear()method.

这里我们绑定ngModel到 input 和 assign placeholder。显示 [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. This will be replaced with directive's HTML template.

所以在上面的例子中,metro 指令的用法如下。这将替换为指令的 HTML 模板。

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

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

另一个输入已对其detect-mouse-over应用指令,仅限于Attribute 以显示A和之间的用法/差异E。鼠标检测指令使输入在鼠标移过/移出时更改背景颜色。

<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-modelto mirror changes between controls.

它也有相同ng-model的镜像控件之间的变化。

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

在您的示例中,您还有一个productService为上述输入控件提供值的。我把它改写为

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 Ctrlis really simplistic. Important part here is that you remember to inject all services and such into your controller. In this case angular's $scopeand our own productService.

所以get()函数只是获得了硬编码的值,但它仍然展示了服务的使用。控制器,命名Ctrl真的很简单。这里的重要部分是您记得将所有服务等注入您的控制器。在这种情况下,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.

更改任何输入中的值都会更改两者的值。下面的输入有“mouseover”所以它是灰色的,mouseout 会再次变成白色。按 [X] 清除值并使占位符可见。

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

这是 plunker 的链接再次http://plnkr.co/edit/GGGxp0

回答by azium

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. http://jsfiddle.net/f0sph1vp/7/

好的,我不确定您从 Metro UI 中获得了哪些其他优势,但这里有一个简单的小提琴,根本不需要您的指令来捕获您在第一个对我有用的小提琴中的内容。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. It kind of seems like you want the clicking of the x button to assign the value of the first input to the second. Which makes a lot more sense.

您发布的第二个小提琴http://jsfiddle.net/gary_stenstrom/xcx2y8uk/64/对我来说很奇怪,因为您似乎不希望第二个输入框与第一个输入框的模型相同。似乎您希望单击 x 按钮将第一个输入的值分配给第二个。这更有意义。

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