将ngModel绑定到自定义指令

时间:2014-10-23 21:41:58

标签: javascript html angularjs angularjs-directive angularjs-scope

所以我现在已经在这个问题上工作了一个星期,我似乎无法理解整个指令的事情。我看了很多帖子......

一堆视频......

通过StackOverflow和其他论坛(链接跟随)希望有什么东西会沉入......我认为我遇到的问题是我想理解为什么/如何工作以便我不会被切割/将其他人的解决方案粘贴到我的代码中,但之后不得不再次询问其他问题,因为我不知道我的粘贴代码在做什么。

然而,我发现每个人都有不同的方式来抚摸这只猫,但似乎没有一种方法符合我对这应该如何运作的理解。

我尝试做的是使用Metro UI CSS库构建表单。我想我会从一个简单的文本框开始。是的......只是一个简单的文本框。 Metro UI文本框有一些很好的内置功能,我想保留,所以我认为这是一个很好的起点。

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

所以我创建了一个名为' 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>

在指令的LINK函数中,我简单地调用了应用我正在寻找的行为的方法。我知道这比它需要的更冗长,但我正在努力学习它,所以我尽我所能地踩过它。 ...(完整代码见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 };
            }
        };
    });

所以这部分工作了。它创建了Metro外观和相关行为,但是...... ngModel没有绑定到该元素。因此,这开始了一个漫长的旅程,通过隔离范围,打破各种编译,控制器,预链接,后链接功能,至少两种不同的方式来持久化ngModel ......所有这些都无效。

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

没有运气......同样的事情......没有绑定到ngModel。所以我发现了&#34;隔离范围&#34; ...

基于此,我尝试了以下(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
            };
        }
    };

没有变化......

我尝试了很多其他的事情,但如果我还没有,我恐怕很快就会失去你的注意力。我得到的最接近的是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
                };
            }
        };
    });

我很精疲力竭,完全不知道剩下要尝试什么。我知道这是我的无知和对AngularJS如何/为何如此工作的理解缺乏的问题。但是我读过的每篇文章都让我尽可能多的问题,或者把我带到了一个兔子洞,在这个洞里,我比起初时更加迷失。如果我无法承担我需要回答的问题而无法负担现场研讨会上的3000美元,那么我对Angular完全没有结束。

如果有人能提供指导,指导......一个很好的资源......我会非常感激...特别是可以帮助解决这个问题的任何事情,但任何可能帮助我停止旋转车轮的事情。在同一时间里,我将继续阅读并重新阅读我能找到的所有内容,希望有些东西可以破解。

由于

更新 - 2014年10月30日

我对这个问题非常了解,但我想继续关注它。我需要并想要了解这一点。另外,我真的要感谢人们为此付出的努力,虽然他们提出了一些解决方案,最终可能是最好的方法,但他们都避开了这个问题,即我试图使用Metro UI CSS库提供的行为。如果可能的话,我宁愿不必重写它们。

到目前为止提供的两种解决方案都已经从解决方案中删除了关键声明......这就是行......

$(element).inputTransform()

我不想发布包含&#34; inputTransform&#34;的整个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;
    };

所以,我已将该指令应用为属性,因为它背后的Metro代码想要CLONE文本框(如果它是元素指令则不会这样做)然后删除原始输入元素。然后,它创建新的DOM元素,并将克隆的输入元素包装在新创建的DIV容器中。我相信这个问题是......当克隆原始元素并从DOM中删除时,绑定被破坏了。有意义的是,如果&#34; ng-model&#34;属性分配绑定到文本框的 引用 。所以我最初的期望是,因为&#34; ng-model&#34;属性与元素的其余部分一起克隆,在指令的编译事件/函数/阶段中,将(重新)建立到新创建的输入元素。显然事实并非如此。您可以在此更新的fiddle中看到我已尝试将ng-model重新连接到新的DOM元素但没有成功。

也许这是不可能的......似乎只是重新构建这些东西最终可能是更容易的方式。

再次感谢Mikko Viitalia和&#39; azium&#39; ......

2 个答案:

答案 0 :(得分:5)

指令并不是最简单的概念,文档确实不那么好,它散布在互联网周围。

我与compilepre-compile进行了斗争,当我尝试编写我的第一个指令但是到目前为止我从未需要这些函数。这可能是由于我缺乏理解但仍然......

看看你的例子,我看到有些基本的东西需要澄清。首先,我将你的指令限制为E,因为它取代了HTML中的控件。我会使用A ttribute,例如为现有控件添加功能。

有一个(强制)命名约定,您在JavaScript中使用HTML和camel大小写中的虚线命名。因此something-cool变为somethingCool。当您将变量“绑定”到指令的范围时,对您的操作方式有很大的不同。使用=绑定到变量,使用@来计算变量(字符串)值。所以首先允许“双向绑定”,但后者当然不是。您还可以使用&绑定到父作用域的表达式/函数。

如果您使用例如plain =那么指令的范围在HTML中需要相同的名称。如果您希望使用其他名称,则在=之后添加变量名称。一个例子

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

我冒昧地以metro-input-transform directives.directive('metroInput', function () { return { restrict: 'E', scope: { ngModel: '=', placeholder: '@watermark' }, link: function (scope) { scope.clear = function () { scope.ngModel = null; }; }, templateUrl: 'metro-template.html' }; }); 作为起点your first Fiddle。我想在这里解释一下(希望我理解你的权利)。

Metro输入指令

ngModel

指令期望watermark绑定到link以显示ngModel何时没有值(文本输入为空)。在clear()内部,我引入了ngModel函数,该函数在指令中用于重置watermark。重置值后,将显示<input type="text" ng-model="ngModel" placeholder="{{ placeholder }}"> <button type="button" class="btn-clear" ng-click="clear()">x</button> 。我已将HTML部分分成另一个文件metro-template.html。

Metro输入HTML模板

ngModel

此处,我们将placeholder绑定到输入并指定clear()。显示[X]的按钮绑定到<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> 方法。

现在,当我们设置指令时,这是使用它的HTML页面。

HTML页面

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

因此在上面的示例中,metro指令的用法如下。这将被指令的HTML模板替换。

detect-mouse-over

其他输入已应用A指令,仅限A ttribute以显示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'); 
      });
    }
  };
});

ng-model

它也有相同的productService镜像控件之间的变化。

在您的示例中,您还有一个app.service('productService', function () { return { get: function () { return { productName: 'initial value from service' }; } }; }); ,它为上面的输入控件提供了值。我把它重写为

产品服务

get()

所以Ctrl函数只获取硬编码值,但它仍然证明了服务的使用。名为$scope的控制器非常简单。这里的重要部分是你记得将所有服务注入你的控制器。在这种情况下,角度productService和我们自己的app.controller('Ctrl', function ($scope, productService) { $scope.data = productService.get(); });

<强>控制器

{{1}}

这是上述解决方案的屏幕截图。

imgur

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

以下是plunker的链接and rewrite it in Plunker

答案 1 :(得分:1)

好的,我不确定你所获得的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>

你发布的第二个小提琴,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">