Set model value programmatically in Angular.js

2019-05-21 04:04发布

问题:

I'm an author of angular-input-modified directive.

This directive is used to track model's value and allows to check whether the value was modified and also provides reset() function to change value back to the initial state.

Right now, model's initial value is stored in the ngModelController.masterValue property and ngModelController.reset() function is provided. Please see the implementation.

I'm using the following statement: eval('$scope.' + modelPath + ' = modelCtrl.masterValue;'); in order to revert value back to it's initial state. modelPath here is actually a value of ng-model attribute. This was developed a way back and I don't like this approach, cause ng-model value can be a complex one and also nested scopes will break this functionality.

What is the best way to refactor this statement? How do I update model's value directly through the ngModel controller's interface?

回答1:

The best solution I've found so far is to use the $parse service in order to parse the Angular's expression in the ng-model attribute and retrieve the setter function for it. Then we can change the model's value by calling this setter function with a new value.

Example:

function reset () {
  var modelValueSetter = $parse(attrs.ngModel).assign;
  modelValueSetter($scope, 'Some new value');
}

This works much more reliably than eval().


If you have a better idea please provide another answer or just comment this one. Thank you!



回答2:

[previous answer]

I had trouble with this issue today, and I solved it by triggering and sort of hijacking the $parsers pipeline using a closure.

const hijack = {trigger: false; model: null};
modelCtrl.$parsers.push( val => {
   if (hijack.trigger){
        hijack.trigger = false;
        return hijack.model;
   }
   else { 
      // .. do something else ...
   })

Then for resetting the model you need to trigger the pipeline by changing the $viewValue with modelCtrl.$setViewValue('newViewValue').

const $setModelValue = function(model){
    // trigger the hijack and pass along your new model
    hijack.trigger = true;
    hijack.model = model; 
    // assuming you have some logic in getViewValue to output a viewValue string
    modelCtrl.$setViewValue( getViewValue(model) ); 
    }

By using $setViewValue(), you will trigger the $parsers pipeline. The function I wrote in the first code block will then be executed with val = getViewValue(model), at which point it would try to parse it into something to use for your $modelValue according the logic in there. But at this point, the variable in the closure hijacks the parser and uses it to completely overwrite the current $modelValue.

At this point, val is not used in the $parser, but it will still be the actual value that is displayed in the DOM, so pick a nice one.

Let me know if this approach works for you.

[edit]

It seems that ngModel.$commitViewValue should trigger the $parsers pipeline as well, I tried quickly but couldn't get it to work.