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

feat($compile): add one-way binding to the isolate scope defintion #13928

Closed
wants to merge 6 commits into from
Closed
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
47 changes: 44 additions & 3 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,30 @@
* you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
* `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
*
* * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an
* expression passed via the attribute `attr`. The expression is evaluated in the context of the
* parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the
* local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`.
*
* For example, given `<my-component my-attr="parentModel">` and directive definition of
* `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the
* value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
* in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however
* two caveats:
* 1. one-way binding does not copy the value from the parent to the isolate scope, it simply
* sets the same value. That means if your bound value is an object, changes to its properties
* in the isolated scope will be reflected in the parent scope (because both reference the same object).
* 2. one-way binding watches changes to the **identity** of the parent value. That means the
* {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference
* to the value has changed. In most cases, this should not be of concern, but can be important
* to know if you one-way bind to an object, and then replace that object in the isolated scope.
* If you now change a property of the object in your parent scope, the change will not be
* propagated to the isolated scope, because the identity of the object on the parent scope
* has not changed. Instead you must assign a new object.
*
* One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
* back to the parent. However, it does not make this completely impossible.
*
* * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
* If no `attr` name is specified then the attribute name is assumed to be the same as the
* local name. Given `<widget my-attr="count = count + value">` and widget definition of
Expand Down Expand Up @@ -826,7 +850,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;

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

var bindings = {};

Expand Down Expand Up @@ -2962,7 +2986,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
optional = definition.optional,
mode = definition.mode, // @, =, or &
lastValue,
parentGet, parentSet, compare;
parentGet, parentSet, compare, removeWatch;

switch (mode) {

Expand Down Expand Up @@ -3023,7 +3047,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return lastValue = parentValue;
};
parentValueWatch.$stateful = true;
var removeWatch;
if (definition.collection) {
removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else {
Expand All @@ -3032,6 +3055,24 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
removeWatchCollection.push(removeWatch);
break;

case '<':
if (!hasOwnProperty.call(attrs, attrName)) {
if (optional) break;
attrs[attrName] = void 0;
}
if (optional && !attrs[attrName]) break;

parentGet = $parse(attrs[attrName]);

destination[scopeName] = parentGet(scope);

removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) {
destination[scopeName] = newParentValue;
}, parentGet.literal);

removeWatchCollection.push(removeWatch);
break;

case '&':
// Don't assign Object.prototype method to scope
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
Expand Down
Loading