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

Commit 4ac23c0

Browse files
Narretzpetebacondarwin
authored andcommitted
feat($compile): add one-way binding to the isolate scope definition
This change allows the developer to bind an isolate scope / controller property to an expression, using a `<` binding, in such a way that if the value of the expression changes, the scope/controller property is updated but not the converse. The binding is implemented as a single simple watch, which can also provide performance benefits over two way bindings. Closes #13928 Closes #13854 Closes #12835 Closes #13900
1 parent 507cf31 commit 4ac23c0

File tree

2 files changed

+380
-12
lines changed

2 files changed

+380
-12
lines changed

src/ng/compile.js

+44-3
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,30 @@
189189
* equality check is done by value (using the {@link angular.equals} function). It's also possible
190190
* to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
191191
* `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional).
192+
*
193+
* * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
194+
* expression passed via the attribute `attr`. The expression is evaluated in the context of the
195+
* parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the
196+
* local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`.
197+
*
198+
* For example, given `<my-component my-attr="parentModel">` and directive definition of
199+
* `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
200+
* value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
201+
* in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however
202+
* two caveats:
203+
* 1. one-way binding does not copy the value from the parent to the isolate scope, it simply
204+
* sets the same value. That means if your bound value is an object, changes to its properties
205+
* in the isolated scope will be reflected in the parent scope (because both reference the same object).
206+
* 2. one-way binding watches changes to the **identity** of the parent value. That means the
207+
* {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference
208+
* to the value has changed. In most cases, this should not be of concern, but can be important
209+
* to know if you one-way bind to an object, and then replace that object in the isolated scope.
210+
* If you now change a property of the object in your parent scope, the change will not be
211+
* propagated to the isolated scope, because the identity of the object on the parent scope
212+
* has not changed. Instead you must assign a new object.
213+
*
214+
* One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
215+
* back to the parent. However, it does not make this completely impossible.
192216
*
193217
* * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
194218
* no `attr` name is specified then the attribute name is assumed to be the same as the local name.
@@ -829,7 +853,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
829853
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
830854

831855
function parseIsolateBindings(scope, directiveName, isController) {
832-
var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
856+
var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/;
833857

834858
var bindings = {};
835859

@@ -2965,7 +2989,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
29652989
optional = definition.optional,
29662990
mode = definition.mode, // @, =, or &
29672991
lastValue,
2968-
parentGet, parentSet, compare;
2992+
parentGet, parentSet, compare, removeWatch;
29692993

29702994
switch (mode) {
29712995

@@ -3026,7 +3050,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
30263050
return lastValue = parentValue;
30273051
};
30283052
parentValueWatch.$stateful = true;
3029-
var removeWatch;
30303053
if (definition.collection) {
30313054
removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
30323055
} else {
@@ -3035,6 +3058,24 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
30353058
removeWatchCollection.push(removeWatch);
30363059
break;
30373060

3061+
case '<':
3062+
if (!hasOwnProperty.call(attrs, attrName)) {
3063+
if (optional) break;
3064+
attrs[attrName] = void 0;
3065+
}
3066+
if (optional && !attrs[attrName]) break;
3067+
3068+
parentGet = $parse(attrs[attrName]);
3069+
3070+
destination[scopeName] = parentGet(scope);
3071+
3072+
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) {
3073+
destination[scopeName] = newParentValue;
3074+
}, parentGet.literal);
3075+
3076+
removeWatchCollection.push(removeWatch);
3077+
break;
3078+
30383079
case '&':
30393080
// Don't assign Object.prototype method to scope
30403081
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;

0 commit comments

Comments
 (0)