Dart Documentationangular.coreScope

Scope class

Scope has two responsibilities. 1) to keep track af watches and 2) to keep references to the model so that they are available for data-binding.

@proxy
class Scope implements Map {
 String $id;
 Scope $parent;
 Scope $root;
 num _nextId = 0;

 ExceptionHandler _exceptionHandler;
 Parser _parser;
 Zone _zone;
 num _ttl;
 String _phase;
 Map<String, Object> _properties = {};
 List<Function> _innerAsyncQueue;
 List<Function> _outerAsyncQueue;
 List<_Watch> _watchers = [];
 Map<String, List<Function>> _listeners = {};
 Scope _nextSibling, _prevSibling, _childHead, _childTail;
 bool _isolate = false;
 Profiler _perf;


 Scope(ExceptionHandler this._exceptionHandler, Parser this._parser,
     ScopeDigestTTL ttl, Zone this._zone, Profiler this._perf) {
   _properties[r'this']= this;
   _ttl = ttl.ttl;
   $root = this;
   $id = '_${$root._nextId++}';
   _innerAsyncQueue = [];
   _outerAsyncQueue = [];

   // Set up the zone to auto digest this scope.
   _zone.onTurnDone = $digest;
   _zone.onError = (e, s, ls) => _exceptionHandler(e, s);
 }

 Scope._child(Scope this.$parent, bool this._isolate, Profiler this._perf) {
   _exceptionHandler = $parent._exceptionHandler;
   _parser = $parent._parser;
   _ttl = $parent._ttl;
   _properties[r'this'] = this;
   _zone = $parent._zone;
   $root = $parent.$root;
   $id = '_${$root._nextId++}';
   _innerAsyncQueue = $parent._innerAsyncQueue;
   _outerAsyncQueue = $parent._outerAsyncQueue;

   _prevSibling = $parent._childTail;
   if ($parent._childHead != null) {
     $parent._childTail._nextSibling = this;
     $parent._childTail = this;
   } else {
     $parent._childHead = $parent._childTail = this;
   }
 }

 _identical(a, b) =>
   identical(a, b) ||
   (a is String && b is String && a == b) ||
   (a is num && b is num && a.isNaN && b.isNaN);

 containsKey(String name) => this[name] != null;
 remove(String name) => this._properties.remove(name);
 operator []=(String name, value) => _properties[name] = value;
 operator [](String name) {
   if (name == r'$id') return this.$id;
   if (name == r'$parent') return this.$parent;
   if (name == r'$root') return this.$root;
   var scope = this;
   do {
     if (scope._properties.containsKey(name)) {
       return scope._properties[name];
     } else if (!scope._isolate) {
       scope = scope.$parent;
     } else {
       return null;
     }
   } while(scope != null);
   return null;
 }

 noSuchMethod(Invocation invocation) {
   var name = MirrorSystem.getName(invocation.memberName);
   if (invocation.isGetter) {
     return this[name];
   } else if (invocation.isSetter) {
     var value = invocation.positionalArguments[0];
     name = name.substring(0, name.length - 1);
     this[name] = value;
     return value;
   } else {
     if (this[name] is Function) {
       return this[name]();
     } else {
       super.noSuchMethod(invocation);
     }
   }
 }



 $new([bool isolate = false]) {
   return new Scope._child(this, isolate, _perf);
 }


 $watch(watchExp, [Function listener, String watchStr]) {
   if (watchStr == null) {
     watchStr = watchExp.toString();

     // Keep prod fast
     assert((() {
       watchStr = _source(watchExpr);
       return true;
     })());
   }
   var watcher = new _Watch(_compileToFn(listener), _initWatchVal,
       _compileToFn(watchExp), watchStr);

   // we use unshift since we use a while loop in $digest for speed.
   // the while loop reads in reverse order.
   _watchers.insert(0, watcher);

   return () => _watchers.remove(watcher);
 }

 $watchCollection(obj, listener, [String expression]) {
   var oldValue;
   var newValue;
   num changeDetected = 0;
   Function objGetter = _compileToFn(obj);
   List internalArray = [];
   Map internalMap = {};
   num oldLength = 0;

   var $watchCollectionWatch = (_) {
     newValue = objGetter(this);
     var newLength, key;

     if (newValue is! Map && newValue is! List) {
       if (!_identical(oldValue, newValue)) {
         oldValue = newValue;
         changeDetected++;
       }
     } else if (newValue is List) {
       if (!_identical(oldValue, internalArray)) {
         // we are transitioning from something which was not an array into array.
         oldValue = internalArray;
         oldLength = oldValue.length = 0;
         changeDetected++;
       }

       newLength = newValue.length;

       if (oldLength != newLength) {
         // if lengths do not match we need to trigger change notification
         changeDetected++;
         oldValue.length = oldLength = newLength;
       }
       // copy the items to oldValue and look for changes.
       for (var i = 0; i < newLength; i++) {
         if (!_identical(oldValue[i], newValue[i])) {
           changeDetected++;
           oldValue[i] = newValue[i];
         }
       }
     } else { // Map
       if (!_identical(oldValue, internalMap)) {
         // we are transitioning from something which was not an object into object.
         oldValue = internalMap = {};
         oldLength = 0;
         changeDetected++;
       }
       // copy the items to oldValue and look for changes.
       newLength = 0;
       newValue.forEach((key, value) {
         newLength++;
         if (oldValue.containsKey(key)) {
           if (!_identical(oldValue[key], value)) {
             changeDetected++;
             oldValue[key] = value;
           }
         } else {
           oldLength++;
           oldValue[key] = value;
           changeDetected++;
         }

       });
       if (oldLength > newLength) {
         // we used to have more keys, need to find them and destroy them.
         changeDetected++;
         var keysToRemove = [];
         oldValue.forEach((key, _) {
           if (!newValue.containsKey(key)) {
             oldLength--;
             keysToRemove.add(key);
           }
         });
         keysToRemove.forEach((k) {
           oldValue.remove(k);
         });
       }
     }
     return changeDetected;
   };

   var $watchCollectionAction = (_, __, ___) {
     relaxFnApply(listener, [newValue, oldValue, this]);
   };

   return this.$watch($watchCollectionWatch,
                      $watchCollectionAction,
                      expression == null ? obj : expression);
 }


 /**
  * Add this function to your code if you want to add a $digest
  * and want to assert that the digest will be called on this turn.
  * This method will be deleted when we are comfortable with
  * auto-digesting scope.
  */
 $$verifyDigestWillRun() {
   _zone.assertInTurn();
 }

 $digest() {
   var innerAsyncQueue = _innerAsyncQueue,
       length,
       dirty, _ttlLeft = _ttl;
   List<List<String>> watchLog = [];
   List<_Watch> watchers;
   _Watch watch;
   Scope next, current, target = this;

   _beginPhase('\$digest');
   try {
     do { // "while dirty" loop
       dirty = false;
       current = target;
       //asyncQueue = current._asyncQueue;
       //dump('aQ: ${asyncQueue.length}');

       while(innerAsyncQueue.length > 0) {
         try {
           var workFn = innerAsyncQueue.removeAt(0);
           assert(_perf.startTimer('ng.innerAsync(${_source(workFn)})') != false);
           $root.$eval(workFn);
         } catch (e, s) {
           _exceptionHandler(e, s);
         } finally {
           assert(_perf.stopTimer('ng.innerAsync(${_source(workFn)})') != false);
         }
       }

       assert(_perf.startTimer('ng.dirty_check.${_ttl-_ttlLeft}') != false);
       do { // "traverse the scopes" loop
         if ((watchers = current._watchers) != null) {
           // process our watches
           length = watchers.length;
           while (length-- > 0) {
             try {
               watch = watchers[length];
               var value = watch.get(current);
               var last = watch.last;
               if (!_identical(value, last)) {
                 dirty = true;
                 watch.last = value;
                 assert(_perf.startTimer('ng.fire(${watch.exp})') != false);
                 watch.fn(value, ((last == _initWatchVal) ? value : last), current);
                 assert(_perf.stopTimer('ng.fire(${watch.exp})') != false);
               }
             } catch (e, s) {
               _exceptionHandler(e, s);
             }
           }
         }

         // Insanity Warning: scope depth-first traversal
         // yes, this code is a bit crazy, but it works and we have tests to prove it!
         // this piece should be kept in sync with the traversal in $broadcast
         if (current._childHead == null) {
           if (current == target) {
             next = null;
           } else {
             next = current._nextSibling;
             if (next == null) {
               while(current != target && (next = current._nextSibling) == null) {
                 current = current.$parent;
               }
             }
           }
         } else {
           next = current._childHead;
         }
       } while ((current = next) != null);

       assert(_perf.stopTimer('ng.dirty_check.${_ttl-_ttlLeft}') != false);
       if(dirty && (_ttlLeft--) == 0) {
         throw '$_ttl \$digest() iterations reached. Aborting!\n' +
             'Watchers fired in the last 5 iterations: ${_toJson(watchLog)}';
       }
     } while (dirty || innerAsyncQueue.length > 0);
     while(_outerAsyncQueue.length > 0) {
       try {
         var workFn = _outerAsyncQueue.removeAt(0);
         assert(_perf.startTimer('ng.outerAsync(${_source(workFn)})') != false);
         $root.$eval(workFn);
       } catch (e, s) {
         _exceptionHandler(e, s);
       } finally {
         assert(_perf.stopTimer('ng.outerAsync(${_source(workFn)})') != false);
       }
     }
   } finally {
     _clearPhase();
   }
 }


 $destroy() {
   if ($root == this) return; // we can't remove the root node;

   $broadcast(r'$destroy');

   if ($parent._childHead == this) $parent._childHead = _nextSibling;
   if ($parent._childTail == this) $parent._childTail = _prevSibling;
   if (_prevSibling != null) _prevSibling._nextSibling = _nextSibling;
   if (_nextSibling != null) _nextSibling._prevSibling = _prevSibling;
 }


 $eval(expr, [locals]) {
   return relaxFnArgs(_compileToFn(expr))(this, locals);
 }


 $evalAsync(expr, {outsideDigest: false}) {
   if (outsideDigest) {
     _outerAsyncQueue.add(expr);
   } else {
     _innerAsyncQueue.add(expr);
   }
 }


 $apply([expr]) {
   return _zone.run(() {
     try {
       assert(_perf.startTimer('ng.\$apply(${_source(expr)})') != false);
       return $eval(expr);
     } catch (e, s) {
       _exceptionHandler(e, s);
     } finally {
       assert(_perf.stopTimer('ng.\$apply(${_source(expr)})') != false);
     }
   });
 }


 $on(name, listener) {
   var namedListeners = _listeners[name];
   if (!_listeners.containsKey(name)) {
     _listeners[name] = namedListeners = [];
   }
   namedListeners.add(listener);

   return () {
     namedListeners.remove(listener);
   };
 }


 $emit(name, [List args]) {
   var empty = [],
       namedListeners,
       scope = this,
       event = new ScopeEvent(name, this),
       listenerArgs = [event],
       i;

   if (args != null) {
     listenerArgs.addAll(args);
   }

   do {
     namedListeners = scope._listeners[name];
     if (namedListeners != null) {
       event.currentScope = scope;
       i = 0;
       for (var length = namedListeners.length; i<length; i++) {
         try {
           relaxFnApply(namedListeners[i], listenerArgs);
           if (event.propagationStopped) return event;
         } catch (e, s) {
           _exceptionHandler(e, s);
         }
       }
     }
     //traverse upwards
     scope = scope.$parent;
   } while (scope != null);

   return event;
 }


 $broadcast(String name, [List listenerArgs]) {
   var target = this,
       current = target,
       next = target,
       event = new ScopeEvent(name, this);

   //down while you can, then up and next sibling or up and next sibling until back at root
   if (listenerArgs == null) {
     listenerArgs = [];
   }
   listenerArgs.insert(0, event);
   do {
     current = next;
     event.currentScope = current;
     if (current._listeners.containsKey(name)) {
       current._listeners[name].forEach((listener) {
         try {
           relaxFnApply(listener, listenerArgs);
         } catch(e, s) {
           _exceptionHandler(e, s);
         }
       });
     }

     // Insanity Warning: scope depth-first traversal
     // yes, this code is a bit crazy, but it works and we have tests to prove it!
     // this piece should be kept in sync with the traversal in $broadcast
     if (current._childHead == null) {
       if (current == target) {
         next = null;
       } else {
         next = current._nextSibling;
         if (next == null) {
           while(current != target && (next = current._nextSibling) == null) {
             current = current.$parent;
           }
         }
       }
     } else {
       next = current._childHead;
     }
   } while ((current = next) != null);

   return event;
 }

 _beginPhase(phase) {
   if ($root._phase != null) {
     // TODO(deboer): Remove the []s when dartbug.com/11999 is fixed.
     throw ['${$root._phase} already in progress'];
   }
   assert(_perf.startTimer('ng.phase.${phase}') != false);

   $root._phase = phase;
 }

 _clearPhase() {
   assert(_perf.stopTimer('ng.phase.${$root._phase}') != false);
   $root._phase = null;
 }

 Function _compileToFn(exp) {
   if (exp == null) {
     return () => null;
   } else if (exp is String) {
     return _parser(exp).eval;
   } else if (exp is Function) {
     return exp;
   } else {
     throw 'Expecting String or Function';
   }
 }
}

Implements

Map

Constructors

new Scope(ExceptionHandler _exceptionHandler, Parser _parser, ScopeDigestTTL ttl, Zone _zone, Profiler _perf) #

Creates a Map instance with the default implementation.

docs inherited from Map
Scope(ExceptionHandler this._exceptionHandler, Parser this._parser,
   ScopeDigestTTL ttl, Zone this._zone, Profiler this._perf) {
 _properties[r'this']= this;
 _ttl = ttl.ttl;
 $root = this;
 $id = '_${$root._nextId++}';
 _innerAsyncQueue = [];
 _outerAsyncQueue = [];

 // Set up the zone to auto digest this scope.
 _zone.onTurnDone = $digest;
 _zone.onError = (e, s, ls) => _exceptionHandler(e, s);
}

Properties

String $id #

String $id

Scope $parent #

Scope $parent

Scope $root #

Scope $root

final bool isEmpty #

inherited from Map

Returns true if there is no {key, value} pair in the map.

bool get isEmpty;

final bool isNotEmpty #

inherited from Map

Returns true if there is at least one {key, value} pair in the map.

bool get isNotEmpty;

final Iterable<K> keys #

inherited from Map

The keys of this.

Iterable<K> get keys;

final int length #

inherited from Map

The number of {key, value} pairs in the map.

int get length;

final Iterable<V> values #

inherited from Map

The values of this.

Iterable<V> get values;

Operators

dynamic operator [](String name) #

Returns the value for the given key or null if key is not in the map. Because null values are supported, one should either use containsKey to distinguish between an absent key and a null value, or use the putIfAbsent method.

docs inherited from Map
operator [](String name) {
 if (name == r'$id') return this.$id;
 if (name == r'$parent') return this.$parent;
 if (name == r'$root') return this.$root;
 var scope = this;
 do {
   if (scope._properties.containsKey(name)) {
     return scope._properties[name];
   } else if (!scope._isolate) {
     scope = scope.$parent;
   } else {
     return null;
   }
 } while(scope != null);
 return null;
}

dynamic operator []=(String name, value) #

Associates the key with the given value.

docs inherited from Map
operator []=(String name, value) => _properties[name] = value;

Methods

dynamic $$verifyDigestWillRun() #

Add this function to your code if you want to add a $digest and want to assert that the digest will be called on this turn. This method will be deleted when we are comfortable with auto-digesting scope.

$$verifyDigestWillRun() {
 _zone.assertInTurn();
}

dynamic $apply([expr]) #

$apply([expr]) {
 return _zone.run(() {
   try {
     assert(_perf.startTimer('ng.\$apply(${_source(expr)})') != false);
     return $eval(expr);
   } catch (e, s) {
     _exceptionHandler(e, s);
   } finally {
     assert(_perf.stopTimer('ng.\$apply(${_source(expr)})') != false);
   }
 });
}

dynamic $broadcast(String name, [List listenerArgs]) #

$broadcast(String name, [List listenerArgs]) {
 var target = this,
     current = target,
     next = target,
     event = new ScopeEvent(name, this);

 //down while you can, then up and next sibling or up and next sibling until back at root
 if (listenerArgs == null) {
   listenerArgs = [];
 }
 listenerArgs.insert(0, event);
 do {
   current = next;
   event.currentScope = current;
   if (current._listeners.containsKey(name)) {
     current._listeners[name].forEach((listener) {
       try {
         relaxFnApply(listener, listenerArgs);
       } catch(e, s) {
         _exceptionHandler(e, s);
       }
     });
   }

   // Insanity Warning: scope depth-first traversal
   // yes, this code is a bit crazy, but it works and we have tests to prove it!
   // this piece should be kept in sync with the traversal in $broadcast
   if (current._childHead == null) {
     if (current == target) {
       next = null;
     } else {
       next = current._nextSibling;
       if (next == null) {
         while(current != target && (next = current._nextSibling) == null) {
           current = current.$parent;
         }
       }
     }
   } else {
     next = current._childHead;
   }
 } while ((current = next) != null);

 return event;
}

dynamic $destroy() #

$destroy() {
 if ($root == this) return; // we can't remove the root node;

 $broadcast(r'$destroy');

 if ($parent._childHead == this) $parent._childHead = _nextSibling;
 if ($parent._childTail == this) $parent._childTail = _prevSibling;
 if (_prevSibling != null) _prevSibling._nextSibling = _nextSibling;
 if (_nextSibling != null) _nextSibling._prevSibling = _prevSibling;
}

dynamic $digest() #

$digest() {
 var innerAsyncQueue = _innerAsyncQueue,
     length,
     dirty, _ttlLeft = _ttl;
 List<List<String>> watchLog = [];
 List<_Watch> watchers;
 _Watch watch;
 Scope next, current, target = this;

 _beginPhase('\$digest');
 try {
   do { // "while dirty" loop
     dirty = false;
     current = target;
     //asyncQueue = current._asyncQueue;
     //dump('aQ: ${asyncQueue.length}');

     while(innerAsyncQueue.length > 0) {
       try {
         var workFn = innerAsyncQueue.removeAt(0);
         assert(_perf.startTimer('ng.innerAsync(${_source(workFn)})') != false);
         $root.$eval(workFn);
       } catch (e, s) {
         _exceptionHandler(e, s);
       } finally {
         assert(_perf.stopTimer('ng.innerAsync(${_source(workFn)})') != false);
       }
     }

     assert(_perf.startTimer('ng.dirty_check.${_ttl-_ttlLeft}') != false);
     do { // "traverse the scopes" loop
       if ((watchers = current._watchers) != null) {
         // process our watches
         length = watchers.length;
         while (length-- > 0) {
           try {
             watch = watchers[length];
             var value = watch.get(current);
             var last = watch.last;
             if (!_identical(value, last)) {
               dirty = true;
               watch.last = value;
               assert(_perf.startTimer('ng.fire(${watch.exp})') != false);
               watch.fn(value, ((last == _initWatchVal) ? value : last), current);
               assert(_perf.stopTimer('ng.fire(${watch.exp})') != false);
             }
           } catch (e, s) {
             _exceptionHandler(e, s);
           }
         }
       }

       // Insanity Warning: scope depth-first traversal
       // yes, this code is a bit crazy, but it works and we have tests to prove it!
       // this piece should be kept in sync with the traversal in $broadcast
       if (current._childHead == null) {
         if (current == target) {
           next = null;
         } else {
           next = current._nextSibling;
           if (next == null) {
             while(current != target && (next = current._nextSibling) == null) {
               current = current.$parent;
             }
           }
         }
       } else {
         next = current._childHead;
       }
     } while ((current = next) != null);

     assert(_perf.stopTimer('ng.dirty_check.${_ttl-_ttlLeft}') != false);
     if(dirty && (_ttlLeft--) == 0) {
       throw '$_ttl \$digest() iterations reached. Aborting!\n' +
           'Watchers fired in the last 5 iterations: ${_toJson(watchLog)}';
     }
   } while (dirty || innerAsyncQueue.length > 0);
   while(_outerAsyncQueue.length > 0) {
     try {
       var workFn = _outerAsyncQueue.removeAt(0);
       assert(_perf.startTimer('ng.outerAsync(${_source(workFn)})') != false);
       $root.$eval(workFn);
     } catch (e, s) {
       _exceptionHandler(e, s);
     } finally {
       assert(_perf.stopTimer('ng.outerAsync(${_source(workFn)})') != false);
     }
   }
 } finally {
   _clearPhase();
 }
}

dynamic $emit(name, [List args]) #

$emit(name, [List args]) {
 var empty = [],
     namedListeners,
     scope = this,
     event = new ScopeEvent(name, this),
     listenerArgs = [event],
     i;

 if (args != null) {
   listenerArgs.addAll(args);
 }

 do {
   namedListeners = scope._listeners[name];
   if (namedListeners != null) {
     event.currentScope = scope;
     i = 0;
     for (var length = namedListeners.length; i<length; i++) {
       try {
         relaxFnApply(namedListeners[i], listenerArgs);
         if (event.propagationStopped) return event;
       } catch (e, s) {
         _exceptionHandler(e, s);
       }
     }
   }
   //traverse upwards
   scope = scope.$parent;
 } while (scope != null);

 return event;
}

dynamic $eval(expr, [locals]) #

$eval(expr, [locals]) {
 return relaxFnArgs(_compileToFn(expr))(this, locals);
}

dynamic $evalAsync(expr, {outsideDigest: false}) #

$evalAsync(expr, {outsideDigest: false}) {
 if (outsideDigest) {
   _outerAsyncQueue.add(expr);
 } else {
   _innerAsyncQueue.add(expr);
 }
}

dynamic $new([bool isolate = false]) #

$new([bool isolate = false]) {
 return new Scope._child(this, isolate, _perf);
}

dynamic $on(name, listener) #

$on(name, listener) {
 var namedListeners = _listeners[name];
 if (!_listeners.containsKey(name)) {
   _listeners[name] = namedListeners = [];
 }
 namedListeners.add(listener);

 return () {
   namedListeners.remove(listener);
 };
}

dynamic $watch(watchExp, [Function listener, String watchStr]) #

$watch(watchExp, [Function listener, String watchStr]) {
 if (watchStr == null) {
   watchStr = watchExp.toString();

   // Keep prod fast
   assert((() {
     watchStr = _source(watchExpr);
     return true;
   })());
 }
 var watcher = new _Watch(_compileToFn(listener), _initWatchVal,
     _compileToFn(watchExp), watchStr);

 // we use unshift since we use a while loop in $digest for speed.
 // the while loop reads in reverse order.
 _watchers.insert(0, watcher);

 return () => _watchers.remove(watcher);
}

dynamic $watchCollection(obj, listener, [String expression]) #

$watchCollection(obj, listener, [String expression]) {
 var oldValue;
 var newValue;
 num changeDetected = 0;
 Function objGetter = _compileToFn(obj);
 List internalArray = [];
 Map internalMap = {};
 num oldLength = 0;

 var $watchCollectionWatch = (_) {
   newValue = objGetter(this);
   var newLength, key;

   if (newValue is! Map && newValue is! List) {
     if (!_identical(oldValue, newValue)) {
       oldValue = newValue;
       changeDetected++;
     }
   } else if (newValue is List) {
     if (!_identical(oldValue, internalArray)) {
       // we are transitioning from something which was not an array into array.
       oldValue = internalArray;
       oldLength = oldValue.length = 0;
       changeDetected++;
     }

     newLength = newValue.length;

     if (oldLength != newLength) {
       // if lengths do not match we need to trigger change notification
       changeDetected++;
       oldValue.length = oldLength = newLength;
     }
     // copy the items to oldValue and look for changes.
     for (var i = 0; i < newLength; i++) {
       if (!_identical(oldValue[i], newValue[i])) {
         changeDetected++;
         oldValue[i] = newValue[i];
       }
     }
   } else { // Map
     if (!_identical(oldValue, internalMap)) {
       // we are transitioning from something which was not an object into object.
       oldValue = internalMap = {};
       oldLength = 0;
       changeDetected++;
     }
     // copy the items to oldValue and look for changes.
     newLength = 0;
     newValue.forEach((key, value) {
       newLength++;
       if (oldValue.containsKey(key)) {
         if (!_identical(oldValue[key], value)) {
           changeDetected++;
           oldValue[key] = value;
         }
       } else {
         oldLength++;
         oldValue[key] = value;
         changeDetected++;
       }

     });
     if (oldLength > newLength) {
       // we used to have more keys, need to find them and destroy them.
       changeDetected++;
       var keysToRemove = [];
       oldValue.forEach((key, _) {
         if (!newValue.containsKey(key)) {
           oldLength--;
           keysToRemove.add(key);
         }
       });
       keysToRemove.forEach((k) {
         oldValue.remove(k);
       });
     }
   }
   return changeDetected;
 };

 var $watchCollectionAction = (_, __, ___) {
   relaxFnApply(listener, [newValue, oldValue, this]);
 };

 return this.$watch($watchCollectionWatch,
                    $watchCollectionAction,
                    expression == null ? obj : expression);
}

abstract void addAll(Map<K, V> other) #

inherited from Map

Adds all key-value pairs of other to this map.

If a key of other is already in this map, its value is overwritten.

The operation is equivalent to doing this[key] = value for each key and associated value in other. It iterates over other, which must therefore not change during the iteration.

abstract void clear() #

inherited from Map

Removes all pairs from the map.

dynamic containsKey(String name) #

Returns true if this map contains the given key.

docs inherited from Map
containsKey(String name) => this[name] != null;

abstract bool containsValue(Object value) #

inherited from Map

Returns true if this map contains the given value.

abstract void forEach(void f(K key, V value)) #

inherited from Map

Applies f to each {key, value} pair of the map.

It is an error to add or remove keys from the map during iteration.

dynamic noSuchMethod(Invocation invocation) #

noSuchMethod is invoked when users invoke a non-existant method on an object. The name of the method and the arguments of the invocation are passed to noSuchMethod in an Invocation. If noSuchMethod returns a value, that value becomes the result of the original invocation.

The default behavior of noSuchMethod is to throw a noSuchMethodError.

docs inherited from Object
noSuchMethod(Invocation invocation) {
 var name = MirrorSystem.getName(invocation.memberName);
 if (invocation.isGetter) {
   return this[name];
 } else if (invocation.isSetter) {
   var value = invocation.positionalArguments[0];
   name = name.substring(0, name.length - 1);
   this[name] = value;
   return value;
 } else {
   if (this[name] is Function) {
     return this[name]();
   } else {
     super.noSuchMethod(invocation);
   }
 }
}

abstract V putIfAbsent(K key, V ifAbsent()) #

inherited from Map

If key is not associated to a value, calls ifAbsent and updates the map by mapping key to the value returned by ifAbsent. Returns the value in the map.

Map<String, int> scores = {'Bob': 36};
for (var key in ['Bob', 'Rohan', 'Sophena']) {
  scores.putIfAbsent(key, () => key.length);
}
scores['Bob'];      // 36
scores['Rohan'];    //  5
scores['Sophena'];  //  7

The code that ifAbsent executes must not add or remove keys.

dynamic remove(String name) #

Removes the association for the given key. Returns the value for key in the map or null if key is not in the map. Note that values can be null and a returned null value does not always imply that the key is absent.

docs inherited from Map
remove(String name) => this._properties.remove(name);