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

adds manualModelWatcher option #599

Merged
merged 1 commit into from
Jan 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/directives/formly-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo

// @ngInject
function FormlyFieldController($scope, $timeout, $parse, $controller, formlyValidationMessages) {
/* eslint max-statements:[2, 32] */
/* eslint max-statements:[2, 34] */
if ($scope.options.fieldGroup) {
setupFieldGroup()
return
Expand All @@ -53,6 +53,7 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
setDefaultValue()
setInitialValue()
runExpressions()
watchExpressions()
addValidationMessages($scope.options)
invokeControllers($scope, $scope.options, fieldType)

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

function watchExpressions() {
if ($scope.formOptions.watchAllExpressions) {
const field = $scope.options
const currentValue = valueGetterSetter()
angular.forEach(field.expressionProperties, function watchExpression(expression, prop) {
const setter = $parse(prop).assign
$scope.$watch(function expressionPropertyWatcher() {
return formlyUtil.formlyEval($scope, expression, currentValue, currentValue)
}, function expressionPropertyListener(value) {
setter(field, value)
}, true)
})
}
}

function valueGetterSetter(newVal) {
if (!$scope.model || !$scope.options.key) {
return undefined
Expand Down
76 changes: 53 additions & 23 deletions src/directives/formly-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,28 +112,35 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
setupFields()

// watch the model and evaluate watch expressions that depend on it.
$scope.$watch('model', onModelOrFormStateChange, true)
if (!$scope.options.manualModelWatcher) {
$scope.$watch('model', onModelOrFormStateChange, true)
} else if (angular.isFunction($scope.options.manualModelWatcher)) {
$scope.$watch($scope.options.manualModelWatcher, onModelOrFormStateChange, true)
}

if ($scope.options.formState) {
$scope.$watch('options.formState', onModelOrFormStateChange, true)
}

function onModelOrFormStateChange() {
angular.forEach($scope.fields, function runFieldExpressionProperties(field, index) {
const model = field.model || $scope.model
const promise = field.runExpressions && field.runExpressions()
if (field.hideExpression) { // can't use hide with expressionProperties reliably
const val = model[field.key]
field.hide = evalCloseToFormlyExpression(field.hideExpression, val, field, index)
}
if (field.extras && field.extras.validateOnModelChange && field.formControl) {
const validate = field.formControl.$validate
if (promise) {
promise.then(validate)
} else {
validate()
}
angular.forEach($scope.fields, runFieldExpressionProperties)
}

function runFieldExpressionProperties(field, index) {
const model = field.model || $scope.model
const promise = field.runExpressions && field.runExpressions()
if (field.hideExpression) { // can't use hide with expressionProperties reliably
const val = model[field.key]
field.hide = evalCloseToFormlyExpression(field.hideExpression, val, field, index)
}
if (field.extras && field.extras.validateOnModelChange && field.formControl) {
const validate = field.formControl.$validate
if (promise) {
promise.then(validate)
} else {
validate()
}
})
}
}

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

setupModels()

if ($scope.options.watchAllExpressions) {
angular.forEach($scope.fields, setupHideExpressionWatcher)
}

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

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

if (field.model && isNewModel && watchedModels.indexOf(field.model) === -1) {
if (field.model && isNewModel && watchedModels.indexOf(field.model) === -1 && !manualModelWatcher) {
$scope.$watch(() => field.model, onModelOrFormStateChange, true)
watchedModels.push(field.model)
}
})
}

function setupHideExpressionWatcher(field, index) {
if (field.hideExpression) { // can't use hide with expressionProperties reliably
const model = field.model || $scope.model
$scope.$watch(function hideExpressionWatcher() {
const val = model[field.key]
return evalCloseToFormlyExpression(field.hideExpression, val, field, index)
}, (hide) => field.hide = hide, true)
}
}

function initModel(field) {
let isNewModel = true

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

isNewModel = !refrencesCurrentlyWatchedModel(expression)
isNewModel = !referencesCurrentlyWatchedModel(expression)

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

function refrencesCurrentlyWatchedModel(expression) {
function referencesCurrentlyWatchedModel(expression) {
return ['model', 'formState'].some(item => {
return formlyUtil.startsWith(expression, `${item}.`) || formlyUtil.startsWith(expression, `${item}[`)
})
Expand All @@ -275,7 +298,7 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
watchers = [watchers]
}
angular.forEach(watchers, function setupWatcher(watcher) {
if (!angular.isDefined(watcher.listener)) {
if (!angular.isDefined(watcher.listener) && !watcher.runFieldExpressions) {
throw formlyUsability.getFieldError(
'all-field-watchers-must-have-a-listener',
'All field watchers must have a listener', field
Expand Down Expand Up @@ -306,13 +329,20 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol

function getWatchListener(watcher, field, index) {
let watchListener = watcher.listener
if (angular.isFunction(watchListener)) {
if (angular.isFunction(watchListener) || watcher.runFieldExpressions) {
// wrap the field's watch listener so we can call it with the field as the first arg
// and the stop function as the last arg as a helper
const originalListener = watchListener
watchListener = function formlyWatchListener() {
const args = modifyArgs(watcher, index, ...arguments)
return originalListener(...args)
let value
if (originalListener) {
const args = modifyArgs(watcher, index, ...arguments)
value = originalListener(...args)
}
if (watcher.runFieldExpressions) {
runFieldExpressionProperties(field, index)
}
return value
}
watchListener.displayName = `Formly Watch Listener for field for ${field.key}`
}
Expand Down
Loading