@@ -878,6 +878,139 @@ NgModelController.prototype = {
878
878
*/
879
879
$overrideModelOptions : function ( options ) {
880
880
this . $options = this . $options . createChild ( options ) ;
881
+ } ,
882
+
883
+ /**
884
+ * @ngdoc method
885
+ *
886
+ * @name ngModel.NgModelController#$processModelValue
887
+
888
+ * @description
889
+ *
890
+ * Runs the model -> view pipeline on the current
891
+ * {@link ngModel.NgModelController#$modelValue $modelValue}.
892
+ *
893
+ * The following actions are performed by this method:
894
+ *
895
+ * - the `$modelValue` is run through the {@link ngModel.NgModelController#$formatters $formatters}
896
+ * and the result is set to the {@link ngModel.NgModelController#$viewValue $viewValue}
897
+ * - the `ng-empty` or `ng-not-empty` class is set on the element
898
+ * - if the `$viewValue` has changed:
899
+ * - {@link ngModel.NgModelController#$render $render} is called on the control
900
+ * - the {@link ngModel.NgModelController#$validators $validators} are run and
901
+ * the validation status is set.
902
+ *
903
+ * This method is called by ngModel internally when the bound scope value changes.
904
+ * Application developers usually do not have to call this function themselves.
905
+ *
906
+ * This function can be used when the `$viewValue` or the rendered DOM value are not correctly
907
+ * formatted and the `$modelValue` must be run through the `$formatters` again.
908
+ *
909
+ * For example, consider a text input with an autocomplete list (for fruit), where the items are
910
+ * objects with a name and an id.
911
+ * A user enters `ap` and then selects `Apricot` from the list.
912
+ * Based on this, the autocomplete widget will call `$setViewValue({name: 'Apricot', id: 443})`,
913
+ * but the rendered value will still be `ap`.
914
+ * The widget can then call `ctrl.$processModelValue()` to run the model -> view
915
+ * pipeline again, which formats the object to the string `Apricot`,
916
+ * then updates the `$viewValue`, and finally renders it in the DOM.
917
+ *
918
+ * @example
919
+ * <example module="inputExample" name="ng-model-process">
920
+ <file name="app.js">
921
+ angular.module('inputExample', [])
922
+ .directive('processModel', function() {
923
+ return {
924
+ require: 'ngModel',
925
+ link: function(scope, element, attrs, ngModel) {
926
+
927
+ ngModel.$formatters.push(function(value) {
928
+ if (angular.isObject(value) && value.name) {
929
+ return value.name;
930
+ }
931
+
932
+ return value;
933
+ });
934
+
935
+ ngModel.$parsers.push(function(value) {
936
+ if (angular.isString(value)) {
937
+ return scope.items.find(function(item) {
938
+ return item.name === value;
939
+ }) || value;
940
+ }
941
+
942
+ return value;
943
+ });
944
+
945
+ scope.items = [
946
+ {name: 'Apricot', id: 443},
947
+ {name: 'Clementine', id: 972},
948
+ {name: 'Durian', id: 169},
949
+ {name: 'Jackfruit', id: 982},
950
+ {name: 'Strawberry', id: 863}
951
+ ];
952
+
953
+ scope.select = function(item) {
954
+ ngModel.$setViewValue(item);
955
+ ngModel.$processModelValue();
956
+ };
957
+ }
958
+ };
959
+ });
960
+ </file>
961
+ <file name="index.html">
962
+ <div style="display: flex;">
963
+ <div style="margin-right: 30px;">
964
+ Search Fruit:
965
+ <input ng-model="val" process-model />
966
+
967
+ <ul>
968
+ <li ng-repeat="item in items | filter:val"><button ng-click="select(item)">{{item.name}}</li>
969
+ </ul>
970
+ </div>
971
+ <div>
972
+ Model:<br>
973
+ <pre>{{val | json}}</pre>
974
+ </div>
975
+ </div>
976
+ </file>
977
+ * </example>
978
+ *
979
+ */
980
+ $processModelValue : function ( ) {
981
+ var viewValue = this . $$format ( ) ;
982
+
983
+ if ( this . $viewValue !== viewValue ) {
984
+ this . $$updateEmptyClasses ( viewValue ) ;
985
+ this . $viewValue = this . $$lastCommittedViewValue = viewValue ;
986
+ this . $render ( ) ;
987
+ // It is possible that model and view value have been updated during render
988
+ this . $$runValidators ( this . $modelValue , this . $viewValue , noop ) ;
989
+ }
990
+ } ,
991
+
992
+ /**
993
+ * This method is called internally to run the $formatters on the $modelValue
994
+ */
995
+ $$format : function ( ) {
996
+ var formatters = this . $formatters ,
997
+ idx = formatters . length ;
998
+
999
+ var viewValue = this . $modelValue ;
1000
+ while ( idx -- ) {
1001
+ viewValue = formatters [ idx ] ( viewValue ) ;
1002
+ }
1003
+
1004
+ return viewValue ;
1005
+ } ,
1006
+
1007
+ /**
1008
+ * This method is called internally when the bound scope value changes.
1009
+ */
1010
+ $$setModelValue : function ( modelValue ) {
1011
+ this . $modelValue = this . $$rawModelValue = modelValue ;
1012
+ this . $$parserValid = undefined ;
1013
+ this . $processModelValue ( ) ;
881
1014
}
882
1015
} ;
883
1016
@@ -894,30 +1027,14 @@ function setupModelWatcher(ctrl) {
894
1027
var modelValue = ctrl . $$ngModelGet ( scope ) ;
895
1028
896
1029
// if scope model value and ngModel value are out of sync
897
- // TODO(perf): why not move this to the action fn?
1030
+ // This cannot be moved to the action function, because it would not catch the
1031
+ // case where the model is changed in the ngChange function or the model setter
898
1032
if ( modelValue !== ctrl . $modelValue &&
899
- // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
900
- // eslint-disable-next-line no-self-compare
901
- ( ctrl . $modelValue === ctrl . $modelValue || modelValue === modelValue )
1033
+ // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
1034
+ // eslint-disable-next-line no-self-compare
1035
+ ( ctrl . $modelValue === ctrl . $modelValue || modelValue === modelValue )
902
1036
) {
903
- ctrl . $modelValue = ctrl . $$rawModelValue = modelValue ;
904
- ctrl . $$parserValid = undefined ;
905
-
906
- var formatters = ctrl . $formatters ,
907
- idx = formatters . length ;
908
-
909
- var viewValue = modelValue ;
910
- while ( idx -- ) {
911
- viewValue = formatters [ idx ] ( viewValue ) ;
912
- }
913
- if ( ctrl . $viewValue !== viewValue ) {
914
- ctrl . $$updateEmptyClasses ( viewValue ) ;
915
- ctrl . $viewValue = ctrl . $$lastCommittedViewValue = viewValue ;
916
- ctrl . $render ( ) ;
917
-
918
- // It is possible that model and view value have been updated during render
919
- ctrl . $$runValidators ( ctrl . $modelValue , ctrl . $viewValue , noop ) ;
920
- }
1037
+ ctrl . $$setModelValue ( modelValue ) ;
921
1038
}
922
1039
923
1040
return modelValue ;
0 commit comments