Skip to content
This repository was archived by the owner on Apr 30, 2018. It is now read-only.

Commit a983c18

Browse files
author
Kent C. Dodds
committed
Merge pull request #599 from kwypchlo/cheap-model-watcher
adds manualModelWatcher option
2 parents 5aa126e + 8167abd commit a983c18

File tree

4 files changed

+413
-25
lines changed

4 files changed

+413
-25
lines changed

src/directives/formly-field.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
3535

3636
// @ngInject
3737
function FormlyFieldController($scope, $timeout, $parse, $controller, formlyValidationMessages) {
38-
/* eslint max-statements:[2, 32] */
38+
/* eslint max-statements:[2, 34] */
3939
if ($scope.options.fieldGroup) {
4040
setupFieldGroup()
4141
return
@@ -53,6 +53,7 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
5353
setDefaultValue()
5454
setInitialValue()
5555
runExpressions()
56+
watchExpressions()
5657
addValidationMessages($scope.options)
5758
invokeControllers($scope, $scope.options, fieldType)
5859

@@ -72,6 +73,21 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
7273
}, 0, false)
7374
}
7475

76+
function watchExpressions() {
77+
if ($scope.formOptions.watchAllExpressions) {
78+
const field = $scope.options
79+
const currentValue = valueGetterSetter()
80+
angular.forEach(field.expressionProperties, function watchExpression(expression, prop) {
81+
const setter = $parse(prop).assign
82+
$scope.$watch(function expressionPropertyWatcher() {
83+
return formlyUtil.formlyEval($scope, expression, currentValue, currentValue)
84+
}, function expressionPropertyListener(value) {
85+
setter(field, value)
86+
}, true)
87+
})
88+
}
89+
}
90+
7591
function valueGetterSetter(newVal) {
7692
if (!$scope.model || !$scope.options.key) {
7793
return undefined

src/directives/formly-form.js

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -112,28 +112,35 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
112112
setupFields()
113113

114114
// watch the model and evaluate watch expressions that depend on it.
115-
$scope.$watch('model', onModelOrFormStateChange, true)
115+
if (!$scope.options.manualModelWatcher) {
116+
$scope.$watch('model', onModelOrFormStateChange, true)
117+
} else if (angular.isFunction($scope.options.manualModelWatcher)) {
118+
$scope.$watch($scope.options.manualModelWatcher, onModelOrFormStateChange, true)
119+
}
120+
116121
if ($scope.options.formState) {
117122
$scope.$watch('options.formState', onModelOrFormStateChange, true)
118123
}
119124

120125
function onModelOrFormStateChange() {
121-
angular.forEach($scope.fields, function runFieldExpressionProperties(field, index) {
122-
const model = field.model || $scope.model
123-
const promise = field.runExpressions && field.runExpressions()
124-
if (field.hideExpression) { // can't use hide with expressionProperties reliably
125-
const val = model[field.key]
126-
field.hide = evalCloseToFormlyExpression(field.hideExpression, val, field, index)
127-
}
128-
if (field.extras && field.extras.validateOnModelChange && field.formControl) {
129-
const validate = field.formControl.$validate
130-
if (promise) {
131-
promise.then(validate)
132-
} else {
133-
validate()
134-
}
126+
angular.forEach($scope.fields, runFieldExpressionProperties)
127+
}
128+
129+
function runFieldExpressionProperties(field, index) {
130+
const model = field.model || $scope.model
131+
const promise = field.runExpressions && field.runExpressions()
132+
if (field.hideExpression) { // can't use hide with expressionProperties reliably
133+
const val = model[field.key]
134+
field.hide = evalCloseToFormlyExpression(field.hideExpression, val, field, index)
135+
}
136+
if (field.extras && field.extras.validateOnModelChange && field.formControl) {
137+
const validate = field.formControl.$validate
138+
if (promise) {
139+
promise.then(validate)
140+
} else {
141+
validate()
135142
}
136-
})
143+
}
137144
}
138145

139146
function setupFields() {
@@ -158,6 +165,10 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
158165

159166
setupModels()
160167

168+
if ($scope.options.watchAllExpressions) {
169+
angular.forEach($scope.fields, setupHideExpressionWatcher)
170+
}
171+
161172
angular.forEach($scope.fields, attachKey) // attaches a key based on the index if a key isn't specified
162173
angular.forEach($scope.fields, setupWatchers) // setup watchers for all fields
163174
}
@@ -217,6 +228,8 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
217228
function setupModels() {
218229
// a set of field models that are already watched (the $scope.model will have its own watcher)
219230
const watchedModels = [$scope.model]
231+
// we will not set up automatic model watchers if manual mode is set
232+
const manualModelWatcher = $scope.options.manualModelWatcher
220233

221234
if ($scope.options.formState) {
222235
// $scope.options.formState will have its own watcher
@@ -226,21 +239,31 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
226239
angular.forEach($scope.fields, (field) => {
227240
const isNewModel = initModel(field)
228241

229-
if (field.model && isNewModel && watchedModels.indexOf(field.model) === -1) {
242+
if (field.model && isNewModel && watchedModels.indexOf(field.model) === -1 && !manualModelWatcher) {
230243
$scope.$watch(() => field.model, onModelOrFormStateChange, true)
231244
watchedModels.push(field.model)
232245
}
233246
})
234247
}
235248

249+
function setupHideExpressionWatcher(field, index) {
250+
if (field.hideExpression) { // can't use hide with expressionProperties reliably
251+
const model = field.model || $scope.model
252+
$scope.$watch(function hideExpressionWatcher() {
253+
const val = model[field.key]
254+
return evalCloseToFormlyExpression(field.hideExpression, val, field, index)
255+
}, (hide) => field.hide = hide, true)
256+
}
257+
}
258+
236259
function initModel(field) {
237260
let isNewModel = true
238261

239262
if (angular.isString(field.model)) {
240263
const expression = field.model
241264
const index = $scope.fields.indexOf(field)
242265

243-
isNewModel = !refrencesCurrentlyWatchedModel(expression)
266+
isNewModel = !referencesCurrentlyWatchedModel(expression)
244267

245268
field.model = evalCloseToFormlyExpression(expression, undefined, field, index)
246269
if (!field.model) {
@@ -254,7 +277,7 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
254277
return isNewModel
255278
}
256279

257-
function refrencesCurrentlyWatchedModel(expression) {
280+
function referencesCurrentlyWatchedModel(expression) {
258281
return ['model', 'formState'].some(item => {
259282
return formlyUtil.startsWith(expression, `${item}.`) || formlyUtil.startsWith(expression, `${item}[`)
260283
})
@@ -275,7 +298,7 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
275298
watchers = [watchers]
276299
}
277300
angular.forEach(watchers, function setupWatcher(watcher) {
278-
if (!angular.isDefined(watcher.listener)) {
301+
if (!angular.isDefined(watcher.listener) && !watcher.runFieldExpressions) {
279302
throw formlyUsability.getFieldError(
280303
'all-field-watchers-must-have-a-listener',
281304
'All field watchers must have a listener', field
@@ -306,13 +329,20 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
306329

307330
function getWatchListener(watcher, field, index) {
308331
let watchListener = watcher.listener
309-
if (angular.isFunction(watchListener)) {
332+
if (angular.isFunction(watchListener) || watcher.runFieldExpressions) {
310333
// wrap the field's watch listener so we can call it with the field as the first arg
311334
// and the stop function as the last arg as a helper
312335
const originalListener = watchListener
313336
watchListener = function formlyWatchListener() {
314-
const args = modifyArgs(watcher, index, ...arguments)
315-
return originalListener(...args)
337+
let value
338+
if (originalListener) {
339+
const args = modifyArgs(watcher, index, ...arguments)
340+
value = originalListener(...args)
341+
}
342+
if (watcher.runFieldExpressions) {
343+
runFieldExpressionProperties(field, index)
344+
}
345+
return value
316346
}
317347
watchListener.displayName = `Formly Watch Listener for field for ${field.key}`
318348
}

0 commit comments

Comments
 (0)