Skip to content

Commit 9091390

Browse files
committed
Two-Way Data Binding
1 parent e91580e commit 9091390

File tree

2 files changed

+203
-4
lines changed

2 files changed

+203
-4
lines changed

src/compile.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ function isBooleanAttribute(node, attrName) {
3636
function parseIsolateBindings(scope) {
3737
var bindings = {};
3838
_.forEach(scope, function(definition, scopeName) {
39-
var match = definition.match(/\s*@\s*(\w*)\s*/);
39+
var match = definition.match(/\s*(@|=(\*?))\s*(\w*)\s*/);
4040
bindings[scopeName] = {
41-
mode: '@',
42-
attrName: match[1] || scopeName
41+
mode: match[1][0],
42+
collection: match[2] === '*',
43+
attrName: match[3] || scopeName
4344
};
4445
});
4546
return bindings;
@@ -79,7 +80,7 @@ function $CompileProvider($provide) {
7980
}
8081
};
8182

82-
this.$get = ['$injector', '$rootScope', function($injector, $rootScope) {
83+
this.$get = ['$injector', '$parse', '$rootScope', function($injector, $parse, $rootScope) {
8384

8485
function Attributes(element) {
8586
this.$$element = element;
@@ -380,6 +381,28 @@ function $CompileProvider($provide) {
380381
isolateScope[scopeName] = attrs[attrName];
381382
}
382383
break;
384+
case '=':
385+
var parentGet = $parse(attrs[attrName]);
386+
var lastValue = isolateScope[scopeName] = parentGet(scope);
387+
var parentValueWatch = function() {
388+
var parentValue = parentGet(scope);
389+
if (isolateScope[scopeName] !== parentValue) {
390+
if (parentValue !== lastValue) {
391+
isolateScope[scopeName] = parentValue;
392+
} else {
393+
parentValue = isolateScope[scopeName];
394+
parentGet.assign(scope, parentValue);
395+
}
396+
}
397+
lastValue = parentValue;
398+
return lastValue;
399+
};
400+
if (definition.collection) {
401+
scope.$watchCollection(attrs[attrName], parentValueWatch);
402+
} else {
403+
scope.$watch(parentValueWatch);
404+
}
405+
break;
383406
}
384407
});
385408
}

test/compile_spec.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,182 @@ describe('$compile', function() {
12161216
});
12171217
});
12181218

1219+
it('allows binding expression to isolate scope', function() {
1220+
var givenScope;
1221+
var injector = makeInjectorWithDirectives('myDirective', function() {
1222+
return {
1223+
scope: {
1224+
anAttr: '='
1225+
},
1226+
link: function(scope) {
1227+
givenScope = scope;
1228+
}
1229+
};
1230+
});
1231+
injector.invoke(function($compile, $rootScope) {
1232+
var el = $('<div my-directive an-attr="42"></div>');
1233+
$compile(el)($rootScope);
1234+
1235+
expect(givenScope.anAttr).toBe(42);
1236+
});
1237+
});
1238+
1239+
it('allows aliasing expression attribute on isolate scope', function() {
1240+
var givenScope;
1241+
var injector = makeInjectorWithDirectives('myDirective', function() {
1242+
return {
1243+
scope: {
1244+
myAttr: '=theAttr'
1245+
},
1246+
link: function(scope) {
1247+
givenScope = scope;
1248+
}
1249+
};
1250+
});
1251+
injector.invoke(function($compile, $rootScope) {
1252+
var el = $('<div my-directive the-attr="42"></div>');
1253+
$compile(el)($rootScope);
1254+
1255+
expect(givenScope.myAttr).toBe(42);
1256+
});
1257+
});
1258+
1259+
it('evaluates isolate scope expression on parent scope', function() {
1260+
var givenScope;
1261+
var injector = makeInjectorWithDirectives('myDirective', function() {
1262+
return {
1263+
scope: {
1264+
myAttr: '='
1265+
},
1266+
link: function(scope) {
1267+
givenScope = scope;
1268+
}
1269+
};
1270+
});
1271+
injector.invoke(function($compile, $rootScope) {
1272+
$rootScope.parentAttr = 41;
1273+
var el = $('<div my-directive my-attr="parentAttr + 1"></div>');
1274+
$compile(el)($rootScope);
1275+
1276+
expect(givenScope.myAttr).toBe(42);
1277+
});
1278+
});
1279+
1280+
it('watches isolated scope expressions', function() {
1281+
var givenScope;
1282+
var injector = makeInjectorWithDirectives('myDirective', function() {
1283+
return {
1284+
scope: {
1285+
myAttr: '='
1286+
},
1287+
link: function(scope) {
1288+
givenScope = scope;
1289+
}
1290+
};
1291+
});
1292+
injector.invoke(function($compile, $rootScope) {
1293+
var el = $('<div my-directive my-attr="parentAttr + 1"></div>');
1294+
$compile(el)($rootScope);
1295+
1296+
$rootScope.parentAttr = 41;
1297+
$rootScope.$digest();
1298+
expect(givenScope.myAttr).toBe(42);
1299+
});
1300+
});
1301+
1302+
it('allows assigning to isolated scope expressions', function() {
1303+
var givenScope;
1304+
var injector = makeInjectorWithDirectives('myDirective', function() {
1305+
return {
1306+
scope: {
1307+
myAttr: '='
1308+
},
1309+
link: function(scope) {
1310+
givenScope = scope;
1311+
}
1312+
};
1313+
});
1314+
injector.invoke(function($compile, $rootScope) {
1315+
var el = $('<div my-directive my-attr="parentAttr"></div>');
1316+
$compile(el)($rootScope);
1317+
1318+
givenScope.myAttr = 42;
1319+
$rootScope.$digest();
1320+
expect($rootScope.parentAttr).toBe(42);
1321+
});
1322+
});
1323+
1324+
it('gives parent change precedence when both parent and child change', function() {
1325+
var givenScope;
1326+
var injector = makeInjectorWithDirectives('myDirective', function() {
1327+
return {
1328+
scope: {
1329+
myAttr: '='
1330+
},
1331+
link: function(scope) {
1332+
givenScope = scope;
1333+
}
1334+
};
1335+
});
1336+
injector.invoke(function($compile, $rootScope) {
1337+
var el = $('<div my-directive my-attr="parentAttr"></div>');
1338+
$compile(el)($rootScope);
1339+
1340+
$rootScope.parentAttr = 42;
1341+
givenScope.myAttr = 43;
1342+
$rootScope.$digest();
1343+
expect($rootScope.parentAttr).toBe(42);
1344+
expect(givenScope.myAttr).toBe(42);
1345+
});
1346+
});
1347+
1348+
it('throws when binding array-returning function to isolate scope', function() {
1349+
var givenScope;
1350+
var injector = makeInjectorWithDirectives('myDirective', function() {
1351+
return {
1352+
scope: {
1353+
myAttr: '='
1354+
},
1355+
link: function(scope) {
1356+
givenScope = scope;
1357+
}
1358+
};
1359+
});
1360+
injector.invoke(function($compile, $rootScope) {
1361+
$rootScope.parentFunction = function() {
1362+
return [1, 2, 3];
1363+
};
1364+
var el = $('<div my-directive my-attr="parentFunction()"></div>');
1365+
$compile(el)($rootScope);
1366+
expect(function() {
1367+
$rootScope.$digest();
1368+
}).toThrow();
1369+
});
1370+
});
1371+
1372+
it('can watch isolated scope expressions as collections', function() {
1373+
var givenScope;
1374+
var injector = makeInjectorWithDirectives('myDirective', function() {
1375+
return {
1376+
scope: {
1377+
myAttr: '=*'
1378+
},
1379+
link: function(scope) {
1380+
givenScope = scope;
1381+
}
1382+
};
1383+
});
1384+
injector.invoke(function($compile, $rootScope) {
1385+
$rootScope.parentFunction = function() {
1386+
return [1, 2, 3];
1387+
};
1388+
var el = $('<div my-directive my-attr="parentFunction()"></div>');
1389+
$compile(el)($rootScope);
1390+
$rootScope.$digest();
1391+
expect(givenScope.myAttr).toEqual([1, 2, 3]);
1392+
});
1393+
});
1394+
12191395
});
12201396

12211397
});

0 commit comments

Comments
 (0)