将ngModel绑定到自定义指令

Gar*_*rom 8 html javascript angularjs angularjs-directive angularjs-scope

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

一堆视频......

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

然而,我发现每个人都有不同的方式来抚摸这只猫,但它们似乎都没有与我对这个应该如何工作的理解相符.

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

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

所以我创建了一个名为'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>
Run Code Online (Sandbox Code Playgroud)

在指令的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 };
            }
        };
    });
Run Code Online (Sandbox Code Playgroud)

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

经过各种阅读后,我的理解是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
            };
        }
    };
Run Code Online (Sandbox Code Playgroud)

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

基于此,我尝试了以下(小提琴)......

    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
            };
        }
    };
Run Code Online (Sandbox Code Playgroud)

没变 ...

我尝试过其他一些事情,但如果我还没有,我恐怕很快就会失去你的注意力.我得到的最接近的是ONE-WAY绑定做类似下面的事情......甚至在这里你可以看到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
                };
            }
        };
    });
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢

G

更新 - 2014年10月30日

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

到目前为止提供的两种解决方案都消除了解决方案的关键声明......这就是线路......

$(element).inputTransform()
Run Code Online (Sandbox Code Playgroud)

我不想发布包含"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;
    };
Run Code Online (Sandbox Code Playgroud)

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

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

再次感谢Mikko Viitalia和'azium'......

Mik*_*ala 5

指令并不是最简单的概念,文档实际上并不那么好,而且它分散在各种各样的网络中.

我挣扎着compile,pre-compile当我试图编写我的第一个指令时,但到目前为止我从未需要这些功能.这可能是由于我缺乏理解,但仍然......

看看你的例子,我看到有一些基本的东西需要澄清.首先,我将你的指令限制为Element,因为它正在替换HTML中的控件.我使用Attribute例如为现有控件添加功能.

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

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

ngModel : '='        // <div ng-model="data"></div>
otherVar: '@someVar' // <div some-var="data></div> or <some-var="data"></some-var>
Run Code Online (Sandbox Code Playgroud)

 

我把自由带你的第一小提琴metro-input-transform为出发点和Plunker重写.我想在这里解释一下(希望我理解你的权利).

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'
  };
});
Run Code Online (Sandbox Code Playgroud)

指令期望ngModel绑定到和watermark显示ngModel何时没有值(文本输入为空).在里面link我介绍了clear()在指令中使用的函数来重置ngModel.重置值时,watermark显示.我已将HTML部分分成单独的文件metro-template.html.

Metro输入HTML模板

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

在这里,我们绑定ngModel到输入和分配placeholder.显示[X]的按钮绑定到clear()方法.

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

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>
Run Code Online (Sandbox Code Playgroud)

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

<metro-input name="productName" 
             ng-model="data.productName" 
             watermark="product name">
</metro-input>
Run Code Online (Sandbox Code Playgroud)

另一个输入有detect-mouse-over指令应用于它,仅限A于显示以显示A和之间的用法/差异E.当鼠标移出/移出鼠标时,鼠标检测指令使输入改变背景颜色.

<input detect-mouse-over
       type="text" 
       ng-model="data.productName">
Run Code Online (Sandbox Code Playgroud)

.

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'); 
      });
    }
  };
});
Run Code Online (Sandbox Code Playgroud)

它也与ng-model控件之间的镜像更改相同.

在您的示例中,您还有一个productService为上面的输入控件提供了值.我把它重写为

产品服务

app.service('productService', function () {
  return {
    get: function () {
      return { productName: 'initial value from service' };
    }
  };
});
Run Code Online (Sandbox Code Playgroud)

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

调节器

app.controller('Ctrl', function ($scope, productService) {
  $scope.data = productService.get();
});
Run Code Online (Sandbox Code Playgroud)

 

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

imgur

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

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