diff --git a/modules/core/src/annotations/visibility.js b/modules/core/src/annotations/visibility.js new file mode 100644 index 00000000000000..e0683c14a4618a --- /dev/null +++ b/modules/core/src/annotations/visibility.js @@ -0,0 +1,14 @@ +import {CONST} from 'facade/lang'; +import {DependencyAnnotation} from 'di/di'; + +export class Parent extends DependencyAnnotation { + @CONST() + constructor() { + } +} + +export class Ancestor extends DependencyAnnotation { + @CONST() + constructor() { + } +} diff --git a/modules/core/src/compiler/element_injector.js b/modules/core/src/compiler/element_injector.js index 439d0db4975c5c..c2874f0f606829 100644 --- a/modules/core/src/compiler/element_injector.js +++ b/modules/core/src/compiler/element_injector.js @@ -1,4 +1,48 @@ -import {FIELD} from 'facade/lang'; +import {FIELD, isPresent, isBlank, Type, int} from 'facade/lang'; +import {List} from 'facade/collection'; +import {ListWrapper} from 'facade/collection'; +import {Key, Dependency, bind, Binding, NoProviderError, ProviderError} from 'di/di'; +import {Parent, Ancestor} from 'core/annotations/visibility'; + +class TreeNode { + @FIELD('_parent:TreeNode') + @FIELD('_head:TreeNode') + @FIELD('_tail:TreeNode') + @FIELD('_next:TreeNode') + @FIELD('_prev:TreeNode') + constructor(parent:TreeNode) { + this._parent = parent; + this._head = null; + this._tail = null; + this._next = null; + this._prev = null; + if (isPresent(parent)) parent._addChild(this); + } + + _addChild(child:TreeNode) { + if (isPresent(this._tail)) { + this._tail._next = child; + child._prev = this._tail; + this._tail = child; + } else { + this._tail = this._head = child; + } + } + + parent() { + return this._parent; + } + + children() { + var res = []; + var child = this._head; + while (child != null) { + ListWrapper.push(res, child); + child = child._next; + } + return res; + } +} /** @@ -18,35 +62,34 @@ ElementInjector (ElementModule): - 1:1 to DOM structure. PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/ - */ -export class ProtoElementInjector { +export class ProtoElementInjector extends TreeNode { /** parent:ProtoDirectiveInjector; next:ProtoDirectiveInjector; prev:ProtoDirectiveInjector; head:ProtoDirectiveInjector; tail:ProtoDirectiveInjector; - DirectiveInjector cloneingInstance; + DirectiveInjector cloningInstance; KeyMap keyMap; /// Because DI tree is sparse, this shows how far away is the Parent DI parentDistance:int = 1; /// 1 for non-sparse/normal depth. cKey:int; cFactory:Function; cParams:List; - keyId0:int; factory0:Function; params0:List; - keyId1:int; factory1:Function; params1:List; - keyId2:int; factory2:Function; params2:List; - keyId3:int; factory3:Function; params3:List; - keyId4:int; factory4:Function; params4:List; - keyId5:int; factory5:Function; params5:List; - keyId6:int; factory6:Function; params6:List; - keyId7:int; factory7:Function; params7:List; - keyId8:int; factory8:Function; params8:List; - keyId9:int; factory9:Function; params9:List; - - queryKeyId0:int; - queryKeyId1:int; + _keyId0:int; factory0:Function; params0:List; + _keyId1:int; factory1:Function; params1:List; + _keyId2:int; factory2:Function; params2:List; + _keyId3:int; factory3:Function; params3:List; + _keyId4:int; factory4:Function; params4:List; + _keyId5:int; factory5:Function; params5:List; + _keyId6:int; factory6:Function; params6:List; + _keyId7:int; factory7:Function; params7:List; + _keyId8:int; factory8:Function; params8:List; + _keyId9:int; factory9:Function; params9:List; + + query_keyId0:int; + query_keyId1:int; textNodes:List; hasProperties:boolean; @@ -54,17 +97,65 @@ export class ProtoElementInjector { elementInjector:ElementInjector; */ - constructor(parent:ProtoElementInjector) { + @FIELD('_elementInjector:ElementInjector') + @FIELD('_binding0:Binding') + @FIELD('_binding1:Binding') + @FIELD('_binding2:Binding') + @FIELD('_key0:int') + @FIELD('_key1:int') + @FIELD('_key2:int') + constructor(directiveTypes:List, parent:ProtoElementInjector) { + super(parent); + + this._elementInjector = null; + + this._binding0 = null; + this._binding1 = null; + this._binding2 = null; + + this._keyId0 = null; + this._keyId1 = null; + this._keyId2 = null; + + var length = directiveTypes.length; + + if (length > 0) { + this._binding0 = this._createBinding(directiveTypes[0]); + this._keyId0 = this._binding0.key.id; + } + + if (length > 1) { + this._binding1 = this._createBinding(directiveTypes[1]); + this._keyId1 = this._binding1.key.id; + } + + if (length > 2) { + this._binding2 = this._createBinding(directiveTypes[2]); + this._keyId2 = this._binding2.key.id; + } + + // dummy fields to make analyzer happy + this.textNodes = []; this.hasProperties = false; - this.textNodes = null; } - instantiate():ElementInjector { - return new ElementInjector(this); + instantiate({appInjector}={}):ElementInjector { + var p = this._parent; + var parentElementInjector = p == null ? null : p._elementInjector; + this._elementInjector = new ElementInjector({ + proto: this, + parent: parentElementInjector, + appInjector: appInjector + }); + return this._elementInjector; + } + + _createBinding(directiveType:Type) { + return bind(directiveType).toClass(directiveType); } } -export class ElementInjector { +export class ElementInjector extends TreeNode { /* _protoInjector:ProtoElementInjector; injector:Injector; @@ -107,10 +198,86 @@ export class ElementInjector { _query1:Query; */ - @FIELD('final protoInjector:ProtoElementInjector') - constructor(protoInjector:ProtoElementInjector) { - this.protoInjector = protoInjector; + + @FIELD('_proto:ProtoElementInjector') + @FIELD('_appInjector:Injector') + constructor({proto, parent, appInjector}) { + super(parent); + this._proto = proto; + this._appInjector = appInjector; + + this._obj0 = null; + this._obj1 = null; + this._obj2 = null; + } + + instantiateDirectives() { + var p = this._proto; + this._obj0 = this._new(p._binding0); + this._obj1 = this._new(p._binding1); + this._obj2 = this._new(p._binding2); + } + + get(token) { + return this._getByKey(Key.get(token), 0); + } + + _new(binding:Binding) { + if (isBlank(binding)) return null; + + var factory = binding.factory; + var deps = binding.dependencies; + var length = deps.length; + + var d0,d1,d2; + try { + d0 = length > 0 ? this._getByDependency(deps[0]) : null; + d1 = length > 1 ? this._getByDependency(deps[1]) : null; + d2 = length > 2 ? this._getByDependency(deps[2]) : null; + } catch(e) { + if (e instanceof ProviderError) e.addKey(binding.key); + throw e; + } + + var obj; + switch(length) { + case 0: obj = factory(); break; + case 1: obj = factory(d0); break; + case 2: obj = factory(d0, d1); break; + case 3: obj = factory(d0, d1, d2); break; + } + + return obj; + } + + _getByDependency(dep:Dependency) { + return this._getByKey(dep.key, this._depth(dep.properties)); } + _getByKey(key:Key, depth:int) { + var ei = this; + while (ei != null && depth >= 0) { + var obj = ei._getDirectiveByKey(key); + if (isPresent(obj)) return obj; + ei = ei._parent; + } + return this._appInjector.get(key); + } + + //TODO: this can be moved to Proto + _depth(properties):int { + if (properties.length == 0) return 0; + if (properties[0] instanceof Parent) return 1; + if (properties[0] instanceof Ancestor) return 99999; + return 0; + } + + _getDirectiveByKey(key:Key) { + var p = this._proto; + if (p._keyId0 === key.id) return this._obj0; + if (p._keyId1 === key.id) return this._obj1; + if (p._keyId2 === key.id) return this._obj2; + return null; + } } diff --git a/modules/core/test/compiler/element_injector_spec.js b/modules/core/test/compiler/element_injector_spec.js new file mode 100644 index 00000000000000..d612789ae831cd --- /dev/null +++ b/modules/core/test/compiler/element_injector_spec.js @@ -0,0 +1,162 @@ +import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'test_lib/test_lib'; +import {isBlank, FIELD} from 'facade/lang'; +import {ListWrapper, MapWrapper, List} from 'facade/collection'; +import {ProtoElementInjector} from 'core/compiler/element_injector'; +import {Parent, Ancestor} from 'core/annotations/visibility'; +import {Injector, Inject, bind} from 'di/di'; + +class Directive { +} + +class NeedsDirective { + @FIELD("dependency:Directive") + constructor(dependency:Directive){ + this.dependency = dependency; + } +} + +class NeedDirectiveFromParent { + @FIELD("dependency:Directive") + constructor(@Parent() dependency:Directive){ + this.dependency = dependency; + } +} + +class NeedDirectiveFromAncestor { + @FIELD("dependency:Directive") + constructor(@Ancestor() dependency:Directive){ + this.dependency = dependency; + } +} + +class NeedsService { + @FIELD("service:Object") + constructor(@Inject("service") service) { + this.service = service; + } +} + +export function main() { + function humanize(tree, names:List) { + var lookupName = (item) => + ListWrapper.last( + ListWrapper.find(names, (pair) => pair[0] === item)); + + if (tree.children().length == 0) return lookupName(tree); + var children = tree.children().map(m => humanize(m, names)); + return [lookupName(tree), children]; + } + + describe("ElementInjector", function () { + describe("proto injectors", function () { + it("should construct a proto tree", function () { + var p = new ProtoElementInjector([], null); + var c1 = new ProtoElementInjector([], p); + var c2 = new ProtoElementInjector([], p); + + expect(humanize(p, [ + [p, 'parent'], + [c1, 'child1'], + [c2, 'child2'] + ])).toEqual(["parent", ["child1", "child2"]]); + }); + }); + + describe("instantiate", function () { + it("should create an element injector", function () { + var protoParent = new ProtoElementInjector([], null); + var protoChild1 = new ProtoElementInjector([], protoParent); + var protoChild2 = new ProtoElementInjector([], protoParent); + + var p = protoParent.instantiate(); + var c1 = protoChild1.instantiate(); + var c2 = protoChild2.instantiate(); + + expect(humanize(p, [ + [p, 'parent'], + [c1, 'child1'], + [c2, 'child2'] + ])).toEqual(["parent", ["child1", "child2"]]); + }); + }); + + describe("instantiateDirectives", function () { + function injector(bindings, props = null) { + if (isBlank(props)) props = {}; + var proto = new ProtoElementInjector(bindings, null); + var inj = proto.instantiate({appInjector: props["appInjector"]}); + inj.instantiateDirectives(); + return inj; + } + + function parentChildInjectors(parentBindings, childBindings) { + var protoParent = new ProtoElementInjector(parentBindings, null); + var parent = protoParent.instantiate(); + parent.instantiateDirectives(); + + var protoChild = new ProtoElementInjector(childBindings, protoParent); + var child = protoChild.instantiate(); + child.instantiateDirectives(); + + return child; + } + + it("should instantiate directives that have no dependencies", function () { + var inj = injector([Directive]); + expect(inj.get(Directive)).toBeAnInstanceOf(Directive); + }); + + it("should instantiate directives that depend on other directives", function () { + var inj = injector([Directive, NeedsDirective]); + + var d = inj.get(NeedsDirective); + + expect(d).toBeAnInstanceOf(NeedsDirective); + expect(d.dependency).toBeAnInstanceOf(Directive); + }); + + it("should instantiate directives that depend on app services", function () { + var appInjector = new Injector([ + bind("service").toValue("service") + ]); + var inj = injector([NeedsService], {"appInjector": appInjector}); + + var d = inj.get(NeedsService); + expect(d).toBeAnInstanceOf(NeedsService); + expect(d.service).toEqual("service"); + }); + + it("should return app services", function () { + var appInjector = new Injector([ + bind("service").toValue("service") + ]); + var inj = injector([], {"appInjector": appInjector}); + + expect(inj.get('service')).toEqual('service'); + }); + + it("should get directives from parent", function () { + var child = parentChildInjectors([Directive], [NeedDirectiveFromParent]); + + var d = child.get(NeedDirectiveFromParent); + + expect(d).toBeAnInstanceOf(NeedDirectiveFromParent); + expect(d.dependency).toBeAnInstanceOf(Directive); + }); + + it("should get directives from ancestor", function () { + var child = parentChildInjectors([Directive], [NeedDirectiveFromAncestor]); + + var d = child.get(NeedDirectiveFromAncestor); + + expect(d).toBeAnInstanceOf(NeedDirectiveFromAncestor); + expect(d.dependency).toBeAnInstanceOf(Directive); + }); + + it("should throw when no directive found", function () { + expect(() => injector([NeedDirectiveFromParent], {"appInjector": new Injector([])})). + toThrowError('No provider for Directive! (NeedDirectiveFromParent -> Directive)'); + }); + }); + }); +} \ No newline at end of file diff --git a/modules/core/test/compiler/view_spec.js b/modules/core/test/compiler/view_spec.js index cf3d6ab5b6e93b..38852faf37036d 100644 --- a/modules/core/test/compiler/view_spec.js +++ b/modules/core/test/compiler/view_spec.js @@ -25,10 +25,10 @@ export function main() { '' + ''); var module:Module = null; - var sectionPI = new ProtoElementInjector(null); + var sectionPI = new ProtoElementInjector(null, null); sectionPI.textNodes = [0]; - var divPI = new ProtoElementInjector(null); - var spanPI = new ProtoElementInjector(null); + var divPI = new ProtoElementInjector(null, null); + var spanPI = new ProtoElementInjector(null, null); spanPI.hasProperties = true; var protoElementInjector:List = [sectionPI, divPI, spanPI]; var protoWatchGroup:ProtoWatchGroup = null; diff --git a/modules/di/src/annotations.js b/modules/di/src/annotations.js index 5ceecde1dbe8bd..14410db15e6756 100644 --- a/modules/di/src/annotations.js +++ b/modules/di/src/annotations.js @@ -20,3 +20,9 @@ export class InjectLazy { this.token = token; } } + +export class DependencyAnnotation { + @CONST() + constructor() { + } +} \ No newline at end of file diff --git a/modules/di/src/binding.js b/modules/di/src/binding.js index 60d0c7031a24d8..8261bd8696c420 100644 --- a/modules/di/src/binding.js +++ b/modules/di/src/binding.js @@ -7,10 +7,11 @@ export class Dependency { @FIELD('final key:Key') @FIELD('final asPromise:bool') @FIELD('final lazy:bool') - constructor(key:Key, asPromise:boolean, lazy:boolean) { + constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List) { this.key = key; this.asPromise = asPromise; this.lazy = lazy; + this.properties = properties; } } @@ -44,7 +45,7 @@ export class BindingBuilder { toValue(value):Binding { return new Binding( Key.get(this.token), - (_) => value, + () => value, [], false ); @@ -53,7 +54,7 @@ export class BindingBuilder { toFactory(factoryFunction:Function, dependencies:List = null):Binding { return new Binding( Key.get(this.token), - reflector.convertToFactory(factoryFunction), + factoryFunction, this._constructDependencies(factoryFunction, dependencies), false ); @@ -62,7 +63,7 @@ export class BindingBuilder { toAsyncFactory(factoryFunction:Function, dependencies:List = null):Binding { return new Binding( Key.get(this.token), - reflector.convertToFactory(factoryFunction), + factoryFunction, this._constructDependencies(factoryFunction, dependencies), true ); @@ -71,6 +72,6 @@ export class BindingBuilder { _constructDependencies(factoryFunction:Function, dependencies:List) { return isBlank(dependencies) ? reflector.dependencies(factoryFunction) : - ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false)); + ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false, [])); } } diff --git a/modules/di/src/di.js b/modules/di/src/di.js index 942592ba5bd7bc..1f380b60de4b5d 100644 --- a/modules/di/src/di.js +++ b/modules/di/src/di.js @@ -3,3 +3,4 @@ export * from './injector'; export * from './binding'; export * from './key'; export * from './module'; +export * from './exceptions'; diff --git a/modules/di/src/injector.js b/modules/di/src/injector.js index 86ac5db99f10bb..41c5ff41d63e6a 100644 --- a/modules/di/src/injector.js +++ b/modules/di/src/injector.js @@ -5,6 +5,7 @@ import {ProviderError, NoProviderError, InvalidBindingError, import {Type, isPresent, isBlank} from 'facade/lang'; import {Promise, PromiseWrapper} from 'facade/async'; import {Key} from './key'; +import {reflector} from './reflector'; var _constructing = new Object(); @@ -150,7 +151,7 @@ class _SyncInjectorStrategy { _createInstance(key:Key, binding:Binding, deps:List) { try { - var instance = binding.factory(deps); + var instance = reflector.invoke(binding.factory, deps); this.injector._setInstance(key, instance); return instance; } catch (e) { @@ -211,7 +212,7 @@ class _AsyncInjectorStrategy { try { var instance = this.injector._getInstance(key); if (!_isWaiting(instance)) return instance; - return binding.factory(deps); + return reflector.invoke(binding.factory, deps); } catch (e) { this.injector._clear(key); throw new InstantiationError(e, key); diff --git a/modules/di/src/reflector.dart b/modules/di/src/reflector.dart index bb6657726e1cd6..40720b8cf06d35 100644 --- a/modules/di/src/reflector.dart +++ b/modules/di/src/reflector.dart @@ -1,26 +1,49 @@ library facade.di.reflector; import 'dart:mirrors'; -import 'annotations.dart' show Inject, InjectPromise, InjectLazy; +import 'annotations.dart' show Inject, InjectPromise, InjectLazy, DependencyAnnotation; import 'key.dart' show Key; import 'binding.dart' show Dependency; import 'exceptions.dart' show NoAnnotationError; class Reflector { Function factoryFor(Type type) { - return _generateFactory(type); - } - - Function convertToFactory(Function factory) { - return (args) => Function.apply(factory, args); - } - - Function _generateFactory(Type type) { ClassMirror classMirror = reflectType(type); MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; Function create = classMirror.newInstance; Symbol name = ctor.constructorName; - return (args) => create(name, args).reflectee; + int length = ctor.parameters.length; + + switch (length) { + case 0: return () => + create(name, []).reflectee; + case 1: return (a1) => + create(name, [a1]).reflectee; + case 2: return (a1, a2) => + create(name, [a1, a2]).reflectee; + case 3: return (a1, a2, a3) => + create(name, [a1, a2, a3]).reflectee; + case 4: return (a1, a2, a3, a4) => + create(name, [a1, a2, a3, a4]).reflectee; + case 5: return (a1, a2, a3, a4, a5) => + create(name, [a1, a2, a3, a4, a5]).reflectee; + case 6: return (a1, a2, a3, a4, a5, a6) => + create(name, [a1, a2, a3, a4, a5, a6]).reflectee; + case 7: return (a1, a2, a3, a4, a5, a6, a7) => + create(name, [a1, a2, a3, a4, a5, a6, a7]).reflectee; + case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => + create(name, [a1, a2, a3, a4, a5, a6, a7, a8]).reflectee; + case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => + create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9]).reflectee; + case 10: return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) => + create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]).reflectee; + }; + + throw "Factory cannot take more than 10 arguments"; + } + + invoke(Function factory, List args) { + return Function.apply(factory, args); } List dependencies(typeOrFunc) { @@ -38,16 +61,17 @@ class Reflector { var injectLazy = metadata.firstWhere((m) => m is InjectLazy, orElse: () => null); if (inject != null) { - return new Dependency(Key.get(inject.token), false, false); + return new Dependency(Key.get(inject.token), false, false, []); } else if (injectPromise != null) { - return new Dependency(Key.get(injectPromise.token), true, false); + return new Dependency(Key.get(injectPromise.token), true, false, []); } else if (injectLazy != null) { - return new Dependency(Key.get(injectLazy.token), false, true); + return new Dependency(Key.get(injectLazy.token), false, true, []); } else if (p.type.qualifiedName != #dynamic) { - return new Dependency(Key.get(p.type.reflectedType), false, false); + var depProps = metadata.where((m) => m is DependencyAnnotation).toList(); + return new Dependency(Key.get(p.type.reflectedType), false, false, depProps); } else { throw new NoAnnotationError(typeOrFunc); diff --git a/modules/di/src/reflector.es6 b/modules/di/src/reflector.es6 index ad62b904abe68b..4f141dd77087a0 100644 --- a/modules/di/src/reflector.es6 +++ b/modules/di/src/reflector.es6 @@ -1,17 +1,17 @@ import {Type, isPresent} from 'facade/lang'; import {List} from 'facade/collection'; -import {Inject, InjectPromise, InjectLazy} from './annotations'; +import {Inject, InjectPromise, InjectLazy, DependencyAnnotation} from './annotations'; import {Key} from './key'; import {Dependency} from './binding'; import {NoAnnotationError} from './exceptions'; class Reflector { factoryFor(type:Type):Function { - return (args) => new type(...args); + return (...args) => new type(...args); } - convertToFactory(factoryFunction:Function):Function { - return (args) => factoryFunction(...args); + invoke(factory:Function, args:List) { + return factory(...args); } dependencies(typeOrFunc):List { @@ -23,31 +23,35 @@ class Reflector { _extractToken(typeOrFunc, annotations) { var type; + var depProps = []; for (var paramAnnotation of annotations) { if (paramAnnotation instanceof Type) { type = paramAnnotation; } else if (paramAnnotation instanceof Inject) { - return this._createDependency(paramAnnotation.token, false, false); + return this._createDependency(paramAnnotation.token, false, false, []); } else if (paramAnnotation instanceof InjectPromise) { - return this._createDependency(paramAnnotation.token, true, false); + return this._createDependency(paramAnnotation.token, true, false, []); } else if (paramAnnotation instanceof InjectLazy) { - return this._createDependency(paramAnnotation.token, false, true); + return this._createDependency(paramAnnotation.token, false, true, []); + + } else if (paramAnnotation instanceof DependencyAnnotation) { + depProps.push(paramAnnotation); } } if (isPresent(type)) { - return this._createDependency(type, false, false); + return this._createDependency(type, false, false, depProps); } else { throw new NoAnnotationError(typeOrFunc); } } - _createDependency(token, asPromise, lazy):Dependency { - return new Dependency(Key.get(token), asPromise, lazy); + _createDependency(token, asPromise, lazy, depProps):Dependency { + return new Dependency(Key.get(token), asPromise, lazy, depProps); } } diff --git a/modules/di/test/di/reflector_spec.js b/modules/di/test/di/reflector_spec.js new file mode 100644 index 00000000000000..fbfe7dcc220cc4 --- /dev/null +++ b/modules/di/test/di/reflector_spec.js @@ -0,0 +1,32 @@ +import {ddescribe, describe, it, iit, expect} from 'test_lib/test_lib'; +import {Key, Inject, DependencyAnnotation} from 'di/di'; +import {CONST} from 'facade/lang'; +import {reflector, Token} from 'di/reflector'; + +class Parent extends DependencyAnnotation { + @CONST() + constructor() { + } +} + +export function main() { + describe("reflector", function () { + describe("dependencies", function () { + //it('should get token and properties from @Inject', function () { + // function f(@Inject('token', ['prop1', 'prop2']) arg) {} + // + // var dep = reflector.dependencies(f)[0]; + // + // expect(dep.key).toEqual(Key.get('token')); + // expect(dep.properties).toEqual(['prop1', 'prop2']); + //}); + + it('should collect annotations implementing DependencyAnnotation as properties', function () { + function f(@Parent() arg:Function) {} + + var dep = reflector.dependencies(f)[0]; + expect(dep.properties[0]).toBeAnInstanceOf(Parent); + }); + }); + }); +} \ No newline at end of file diff --git a/modules/facade/src/collection.dart b/modules/facade/src/collection.dart index c3d72659d0e194..93d0282b43349c 100644 --- a/modules/facade/src/collection.dart +++ b/modules/facade/src/collection.dart @@ -21,6 +21,7 @@ class ListWrapper { static void set(m, k, v) { m[k] = v; } static contains(m, k) => m.containsKey(k); static map(list, fn) => list.map(fn).toList(); + static find(List list, fn) => list.firstWhere(fn, orElse:() => null); static forEach(list, fn) { list.forEach(fn); } diff --git a/modules/facade/src/collection.es6 b/modules/facade/src/collection.es6 index d1dcdd4cac9e6e..37ef434f2f67f3 100644 --- a/modules/facade/src/collection.es6 +++ b/modules/facade/src/collection.es6 @@ -42,6 +42,11 @@ export class ListWrapper { if (!array || array.length == 0) return null; return array[array.length - 1]; } + static find(list:List, pred:Function) { + for (var i = 0 ; i < list.length; ++i) { + if (pred(list[i])) return list[i]; + } + } static reversed(array) { var a = ListWrapper.clone(array); return a.reverse();