Skip to content

Commit 798905d

Browse files
author
James Davies
committed
fix($parse) - Allow setterFn to bind correctly to promise results
When binding the result of a promise directly to an ng:model directive, getterFn successfully extracts the data from the promise by traversing the '$$v' attribute. However the setterFn was not successfully traversing to the $$v attribute, and instead writing model updates directly on the promise itself. The subsequent apply/digest phase of Angular was then pulling the original value from the promise and applying it back into the viewState, thus simulating a read-only field. In summary, setterFn was writing to the root of the promise, while getterFn was reading from the '$$v' component. Fixed by modifying setterFn to unwrap promises if found and read $$v. If promises have not been resolved yet, bindings will function as they previously did (act as a read-only field). This appears to be more of a side effect than intentional design in the existing codebase, however I kept this behaviour in this patch to minimize the chance of breaking existing projects. Closes angular#1827 Also see: http://stackoverflow.com/questions/16883239/angularjs-ngmodel- field-is-readonly-when-bound-to-q-promise/16883387
1 parent b6a0777 commit 798905d

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

src/ng/parse.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,20 @@ function parser(text, json, $filter, csp){
705705
// Parser helper functions
706706
//////////////////////////////////////////////////
707707

708+
// Unwrap values from a promise
709+
function unwrapPromise(obj){
710+
711+
// If we'v been passed a promise
712+
if( obj.then ){
713+
714+
// Then unwrap the value. If the value has not been returned yet, bind to a throwaway object. This will simulate a read-only field.
715+
return obj.$$v || {}
716+
}
717+
718+
return obj;
719+
}
720+
721+
708722
function setter(obj, path, setValue) {
709723
var element = path.split('.');
710724
for (var i = 0; element.length > 1; i++) {
@@ -714,7 +728,7 @@ function setter(obj, path, setValue) {
714728
propertyObj = {};
715729
obj[key] = propertyObj;
716730
}
717-
obj = propertyObj;
731+
obj = unwrapPromise( propertyObj );
718732
}
719733
obj[element.shift()] = setValue;
720734
return setValue;

test/ng/parseSpec.js

+45
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,51 @@ describe('parser', function() {
679679
expect(scope.$eval('greeting')).toBe('hello!');
680680
});
681681

682+
it('should evaluate a resolved object promise and set its value', inject(function($parse) {
683+
var person = {'name': 'Bill Gates'};
684+
685+
deferred.resolve(person);
686+
scope.person = promise;
687+
688+
var getter = $parse( 'person.name' );
689+
690+
expect(getter(scope)).toBe(undefined);
691+
scope.$digest();
692+
expect(getter(scope)).toBe('Bill Gates');
693+
694+
getter.assign(scope, 'Warren Buffet');
695+
expect(getter(scope)).toBe('Warren Buffet');
696+
}));
697+
698+
it('should evaluate a resolved primitive type promise and set its value', inject(function($parse) {
699+
deferred.resolve("Salut!");
700+
scope.greeting = promise;
701+
702+
var getter = $parse( 'greeting' );
703+
704+
expect(getter(scope)).toBe(undefined);
705+
scope.$digest();
706+
expect(getter(scope)).toBe('Salut!');
707+
708+
getter.assign(scope, 'Bonjour');
709+
expect(getter(scope)).toBe('Bonjour');
710+
}));
711+
712+
it('should evaluate an unresolved promise and *not* set its value', inject(function($parse) {
713+
714+
715+
scope.person = promise;
716+
717+
var getter = $parse( 'person.name' );
718+
719+
expect(getter(scope)).toBe(undefined);
720+
scope.$digest();
721+
expect(getter(scope)).toBe(undefined);
722+
723+
getter.assign(scope, 'Warren Buffet');
724+
expect(getter(scope)).toBe(undefined);
725+
}));
726+
682727

683728
it('should evaluated rejected promise and ignore the rejection reason', function() {
684729
deferred.reject('sorry');

0 commit comments

Comments
 (0)