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

Commit c250ebd

Browse files
committed
Fixed #380 - hideExpression is now called on field's model change; optimized number of field model watchers (one per object)
1 parent 05a0fff commit c250ebd

File tree

4 files changed

+100
-11
lines changed

4 files changed

+100
-11
lines changed

src/directives/formly-field.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
1616
return {
1717
restrict: 'AE',
1818
transclude: true,
19+
require: '?^formlyForm',
1920
scope: {
2021
options: '=',
2122
model: '=',
@@ -51,7 +52,6 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
5152
setDefaultValue();
5253
setInitialValue();
5354
runExpressions();
54-
addModelWatcher($scope, $scope.options);
5555
addValidationMessages($scope.options);
5656
invokeControllers($scope, $scope.options, fieldType);
5757

@@ -146,13 +146,6 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
146146
});
147147
}
148148

149-
// initialization functions
150-
function addModelWatcher(scope, options) {
151-
if (options.model) {
152-
scope.$watch('options.model', runExpressions, true);
153-
}
154-
}
155-
156149
function resetModel() {
157150
$scope.model[$scope.options.key] = $scope.options.initialValue;
158151
if ($scope.options.formControl) {
@@ -212,12 +205,17 @@ function formlyField($http, $q, $compile, $templateCache, $interpolate, formlyCo
212205

213206

214207
// link function
215-
function fieldLink(scope, el) {
208+
function fieldLink(scope, el, attrs, formlyFormCtrl) {
216209
if (scope.options.fieldGroup) {
217210
setFieldGroupTemplate();
218211
return;
219212
}
220213

214+
// watch the field model (if exists) if there is no parent formly-form directive (that would watch it instead)
215+
if (!formlyFormCtrl && scope.options.model) {
216+
scope.$watch('options.model', () => scope.options.runExpressions(), true);
217+
}
218+
221219
addAttributes();
222220
addClasses();
223221

src/directives/formly-field.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,24 @@ describe('formly-field', function() {
13591359
expect(() => compileAndDigest()).to.throw();
13601360
});
13611361

1362+
it(`should watch the model when it's not the direct child of a formly-form`, () => {
1363+
scope.fields = [
1364+
getNewField({key: 'foo', model: {}})
1365+
];
1366+
1367+
compileAndDigest('<div formly-field class="formly-field" options="fields[0]" model="fields[0].model"></div>');
1368+
$timeout.flush();
1369+
1370+
const expressionPropertySpy = sinon.spy();
1371+
field.expressionProperties = {'data.dummy': expressionPropertySpy};
1372+
1373+
field.model.foo = 'hello';
1374+
scope.$digest();
1375+
$timeout.flush();
1376+
1377+
expect(expressionPropertySpy).to.have.been.calledOnce;
1378+
});
1379+
13621380
});
13631381

13641382
describe(`fieldGroup`, () => {

src/directives/formly-form.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
120120
angular.forEach($scope.fields, function runFieldExpressionProperties(field, index) {
121121
/*jshint -W030 */
122122
const model = field.model || $scope.model;
123-
field.runExpressions && field.runExpressions(model);
123+
field.runExpressions && field.runExpressions();
124124
if (field.hideExpression) { // can't use hide with expressionProperties reliably
125125
const val = model[field.key];
126126
field.hide = evalCloseToFormlyExpression(field.hideExpression, val, field, index);
@@ -139,7 +139,8 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
139139
}
140140
}
141141

142-
angular.forEach($scope.fields, initModel); // initializes the model property if set to 'formState'
142+
setupModels();
143+
143144
angular.forEach($scope.fields, attachKey); // attaches a key based on the index if a key isn't specified
144145
angular.forEach($scope.fields, setupWatchers); // setup watchers for all fields
145146
}
@@ -179,6 +180,20 @@ function formlyForm(formlyUsability, formlyWarn, $parse, formlyConfig, $interpol
179180
});
180181
}
181182

183+
function setupModels() {
184+
// a set of field models that are already watched (the $scope.model will have its own watcher)
185+
const watchedModels = [$scope.model];
186+
187+
angular.forEach($scope.fields, (field) => {
188+
initModel(field);
189+
190+
if (field.model && watchedModels.indexOf(field.model) === -1) {
191+
$scope.$watch(() => field.model, onModelOrFormStateChange, true);
192+
watchedModels.push(field.model);
193+
}
194+
});
195+
}
196+
182197
function initModel(field) {
183198
if (angular.isString(field.model)) {
184199
const expression = field.model;

src/directives/formly-form.test.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,64 @@ describe('formly-form', () => {
375375

376376
});
377377

378+
describe('an instance of model', () => {
379+
const spy1 = sinon.spy();
380+
const spy2 = sinon.spy();
381+
382+
beforeEach(() => {
383+
scope.model = {};
384+
scope.fieldModel1 = {};
385+
386+
scope.fields = [
387+
{template: input, key: 'foo', model: scope.fieldModel1},
388+
{template: input, key: 'bar', model: scope.fieldModel1},
389+
{template: input, key: 'zoo', model: scope.fieldModel1}
390+
];
391+
});
392+
393+
it('should be assigned with only one watcher', () => {
394+
compileAndDigest();
395+
$timeout.flush();
396+
397+
scope.fields[0].expressionProperties = {'data.dummy': spy1};
398+
scope.fields[1].expressionProperties = {'data.dummy': spy2};
399+
400+
scope.fieldModel1.foo = 'value';
401+
scope.$digest();
402+
$timeout.flush();
403+
404+
expect(spy1).to.have.been.calledOnce;
405+
expect(spy2).to.have.been.calledOnce;
406+
});
407+
});
408+
409+
describe('hideExpression', () => {
410+
beforeEach(() => {
411+
scope.model = {};
412+
scope.fieldModel = {};
413+
414+
scope.fields = [
415+
{template: input, key: 'foo', model: scope.fieldModel},
416+
{template: input, key: 'bar', model: scope.fieldModel, hideExpression: () => !!scope.fieldModel.foo}
417+
];
418+
});
419+
420+
it('should be called and resolve to true when field model changes', () => {
421+
compileAndDigest();
422+
expect(scope.fields[1].hide).be.false;
423+
scope.fields[0].formControl.$setViewValue('value');
424+
expect(scope.fields[1].hide).be.true;
425+
});
426+
427+
it('should be called and resolve to false when field model changes', () => {
428+
scope.fieldModel.foo = 'value';
429+
compileAndDigest();
430+
expect(scope.fields[1].hide).be.true;
431+
scope.fields[0].formControl.$setViewValue('');
432+
expect(scope.fields[1].hide).be.false;
433+
});
434+
});
435+
378436
describe(`options`, () => {
379437
beforeEach(() => {
380438
scope.model = {

0 commit comments

Comments
 (0)