【问题标题】:How to update AngularJS model from directive linking function?如何从指令链接函数更新 AngularJS 模型?
【发布时间】:2013-08-15 09:16:48
【问题描述】:

我想要实现的是为输入字段添加额外的界面,以便能够通过单击 + 和 - 按钮来增加和减少其中的数值。

(本质上它是 chrome 上的 input[type=number] 字段,但我希望它能够跨浏览器兼容,并且还可以完全控制所有浏览器的呈现)。

查看代码:

<input data-ng-model="session.amountChosen" type="text" min="1" class="form-control input-small" data-number-input>

指令代码:

app.directive('numberInput', function() {
return {
    require: 'ngModel',
    scope: true,
    link: function(scope, elm, attrs, ctrl) {

        var currValue = parseInt(scope.$eval(attrs.ngModel)),
            minValue = attrs.min || 0,
            maxValue = attrs.max || Infinity,
            newValue;

        //puts a wrap around the input and adds + and - buttons
        elm.wrap('<div class="number-input-wrap"></div>').parent().append('<div class="number-input-controls"><a href="#" class="btn btn-xs btn-pluimen">+</a><a href="#" class="btn btn-xs btn-pluimen">-</a></div>');

        //finds the buttons ands binds a click event to them where the model increase/decrease should happen
        elm.parent().find('a').bind('click',function(e){

            if(this.text=='+' && currValue<maxValue) {
                newValue = currValue+1;    
            } else if (this.text=='-' && currValue>minValue) {
                newValue = currValue-1;    
            }

            scope.$apply(function(){
                scope.ngModel = newValue;
            });

            e.preventDefault();
        });


    }
  };

})

这可以通过scope.$eval(attrs.ngModel)获取当前模型值,但是设置新值失败。

事后编辑:这是现在可以工作的代码(以防你不想看到这个问题的解决方案)

app.directive('numberInput', function() {
  return {
    require: 'ngModel',
    scope: true,
    link: function(scope, elm, attrs, ctrl) {

        var minValue = attrs.min || 0,
            maxValue = attrs.max || Infinity;

        elm.wrap('<div class="number-input-wrap"></div>').parent().append('<div class="number-input-controls"><a href="#" class="btn btn-xs btn-pluimen">+</a><a href="#" class="btn btn-xs btn-pluimen">-</a></div>');
        elm.parent().find('a').bind('click',function(e){

            var currValue = parseInt(scope.$eval(attrs.ngModel)),
                newValue = currValue;

            if(this.text=='+' && currValue<maxValue) {
                newValue = currValue+1;    
            } else if (this.text=='-' && currValue>minValue) {
                newValue = currValue-1;    
            }

            scope.$eval(attrs.ngModel + "=" + newValue);
            scope.$apply();            

            e.preventDefault();
        });
    }
  };
})

【问题讨论】:

  • 可能是ctrl.$setViewValue(newValue) ?
  • 使用 ng-click 和模板。
  • Cherniv,不,它没有起到作用,但你的建议帮助我更好地理解了 Angular。谢谢。

标签: angularjs angularjs-directive angularjs-scope


【解决方案1】:

应该使用ngModelController 方法而不是 $eval() 来获取和设置 ng-model 属性的值。

parseInt() 在评估具有数值的属性时不需要,因为 $eval 会将值转换为数字。 $eval 应该用于设置变量 minValue 和 maxValue。

指令不需要创建子作用域。

$apply() 不是必需的,因为 ngModelController 方法(尤其是 $render())会自动更新视图。但是,正如@Harijs 在下面的评论中指出的那样,如果还需要更新应用程序的其他部分,则需要 $apply()。

app.directive('numberInput', function ($parse) {
    return {
        require: 'ngModel',
        link: function (scope, elm, attrs, ctrl) {
            var minValue = scope.$eval(attrs.min) || 0,
                maxValue = scope.$eval(attrs.max) || Infinity;
            elm.wrap('<div class="number-input-wrap"></div>').parent()
                .append('<div class="number-input-controls"><a href="#" class="btn btn-xs btn-pluimen">+</a><a href="#" class="btn btn-xs btn-pluimen">-</a></div>');
            elm.parent().find('a').bind('click', function (e) {
                var currValue = ctrl.$modelValue,
                    newValue;
                if (this.text === '+' && currValue < maxValue) {
                    newValue = currValue + 1;
                } else if (this.text === '-' && currValue > minValue) {
                    newValue = currValue - 1;
                }
                ctrl.$setViewValue(newValue);
                ctrl.$render();
                e.preventDefault();
                scope.$apply(); // needed if other parts of the app need to be updated
            });
        }
    };
});

fiddle

【讨论】:

  • 在我的用例中,这个问题(或不足)是它只更新这个输入字段,而不是整个模型 - 所有其他依赖于此输入的应用程序字段。因此 scope.$apply() 对我来说是必要的。而且您对范围是正确的,但是我不完全明白为什么应该在属性上调用 $eval 。它与 parseInt 一样有效。
  • @HarijsDeksnis,谢谢,我更新了答案以反映您的 cmets。是的,您可以使用 parseInt() 或 $eval。在您的原始代码中,您使用了两者,所以我试图指出您只需要使用一个。在“Aftermath edit”中,您没有使用 minValue 和 maxValue,您应该使用其中之一,否则 minValue 和 maxValue 将是字符串类型。 $eval() 更通用一点,因为它会正确解析布尔值、整数或字符串,并为变量分配正确的类型,而 parseInt() 显然只适用于数值。所以我倾向于支持 $eval()。
  • 现在我学会了欣赏 $eval 对 attrs 的好处——它允许值是一个表达式,例如从其他一些范围变量传递。我试图找到你在哪里使用 $parse,但似乎你注入它只是不合常规,对吧?
  • @HarijsDeksnis,我忘了删除 $parse - 这里不需要它。如果要将范围对象属性作为指令属性传递/指定,请使用 $parse,然后修改指令内的值:stackoverflow.com/a/15725402/215945
  • @MarkRajcok:该解决方案在一种情况下不起作用。手动更改输入文本框中的值,然后按 + 或 -。您会注意到模型没有更新。在此事件之后,它的行为也是字符串而不是数字。
【解决方案2】:

您不想替换 scope.ngModel 变量,而是替换该变量后面的值。当您读取链接函数第一行中的值时,您已经这样做了:

 currValue = parseInt(scope.$eval(attrs.ngModel))
 //                         ^^^^^^^^^^^^^^^^^^^^

如果它是一个普通值,比如myProperty,你可以在作用域上使用它:

 scope[attr.ngModel] = newValue

但是,如果你有一个表达式值,比如container.myProperty,这将不起作用。在这种情况下(这是更通用的类型,您应该瞄准),您必须评估设置为范围的值,如下所示:

scope.$eval(attrs.ngModel + "=" + newValue)

我必须承认,$eval 部分有点难看,就像在带有 eval 吊坠的 JavaScript 中一样,但它确实可以解决问题。请记住,当您希望设置字符串值时,它可能无法以这种方式工作。那么你就必须转义这些值。

希望有所帮助;)

【讨论】:

  • 谢谢,它确实更新了模型值!尽管它不会改变对自身的看法。我必须添加它以使输入字段在视图上更新。 ctrl.$viewValue = newValue; ctrl.$render();但是,我仍然需要弄清楚如何更新我的应用程序中使用此模型值的其他字段。
  • 这比预期的要容易。只需在最后添加 scope.$apply() 即可。
  • 啊,是的,忘了提一下,任何像bind("click") 这样的jQuery 事件监听器都不会触发angular 的digest 循环,所以你必须手动完成。但显然你已经想到了;)
  • 应该使用 ngModelController 方法而不是 $eval (见我的回答)。
【解决方案3】:
.directive('numberInput', function ($parse) {
   return {
      require: 'ngModel',
      link: function (scope, elm, attrs){ 
        elm.bind("click", function() {
          var model = $parse(attrs.ngModel);
          var modelSetter = model.assign;
          scope.$apply(function() {
               modelSetter(scope, model);
          });
     }
  }
})

【讨论】:

  • 谢谢,真的很有帮助!
猜你喜欢
  • 2014-07-05
  • 1970-01-01
  • 1970-01-01
  • 2017-08-31
  • 2014-03-11
  • 1970-01-01
  • 1970-01-01
  • 2015-03-28
  • 1970-01-01
相关资源
最近更新 更多