@@ -878,6 +878,141 @@ 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 of the control should
907
+ * be updated after a user input.
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 includes a formatter that converts the object into the string `Apricot`
916
+ * which is set to the $viewValue and rendered 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: 'Fig', id: 298},
950
+ {name: 'Jackfruit', id: 982},
951
+ {name: 'Kiwi', id: 151},
952
+ {name: 'Strawberry', id: 863}
953
+ ];
954
+
955
+ scope.select = function(item) {
956
+ ngModel.$setViewValue(item);
957
+ ngModel.$processModelValue();
958
+ };
959
+ }
960
+ };
961
+ });
962
+ </file>
963
+ <file name="index.html">
964
+ <div style="display: flex;">
965
+ <div margin-right: 30px;">
966
+ Search Fruit:
967
+ <input ng-model="val" process-model />
968
+
969
+ <ul>
970
+ <li ng-repeat="item in items | filter:val"><button ng-click="select(item)">{{item.name}}</li>
971
+ </ul>
972
+ </div>
973
+ <div>
974
+ Model:<br>
975
+ <pre>{{val | json}}</pre>
976
+ </div>
977
+ </div>
978
+ </file>
979
+ * </example>
980
+ *
981
+ */
982
+ $processModelValue : function ( ) {
983
+ var viewValue = this . $$format ( ) ;
984
+
985
+ if ( this . $viewValue !== viewValue ) {
986
+ this . $$updateEmptyClasses ( viewValue ) ;
987
+ this . $viewValue = this . $$lastCommittedViewValue = viewValue ;
988
+ this . $render ( ) ;
989
+ // It is possible that model and view value have been updated during render
990
+ this . $$runValidators ( this . $modelValue , this . $viewValue , noop ) ;
991
+ }
992
+ } ,
993
+
994
+ /**
995
+ * This method is called internally to run the $formatters on the $modelValue
996
+ */
997
+ $$format : function ( ) {
998
+ var formatters = this . $formatters ,
999
+ idx = formatters . length ;
1000
+
1001
+ var viewValue = this . $modelValue ;
1002
+ while ( idx -- ) {
1003
+ viewValue = formatters [ idx ] ( viewValue ) ;
1004
+ }
1005
+
1006
+ return viewValue ;
1007
+ } ,
1008
+
1009
+ /**
1010
+ * This method is called internally when the bound scope value changes.
1011
+ */
1012
+ $$setModelValue : function ( modelValue ) {
1013
+ this . $modelValue = this . $$rawModelValue = modelValue ;
1014
+ this . $$parserValid = undefined ;
1015
+ this . $processModelValue ( ) ;
881
1016
}
882
1017
} ;
883
1018
@@ -894,30 +1029,14 @@ function setupModelWatcher(ctrl) {
894
1029
var modelValue = ctrl . $$ngModelGet ( scope ) ;
895
1030
896
1031
// if scope model value and ngModel value are out of sync
897
- // TODO(perf): why not move this to the action fn?
1032
+ // This cannot be moved to the action function, because it would not catch the
1033
+ // case where the model is changed in the ngChange function or the model setter
898
1034
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 )
1035
+ // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
1036
+ // eslint-disable-next-line no-self-compare
1037
+ ( ctrl . $modelValue === ctrl . $modelValue || modelValue === modelValue )
902
1038
) {
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
- }
1039
+ ctrl . $$setModelValue ( modelValue ) ;
921
1040
}
922
1041
923
1042
return modelValue ;
0 commit comments