Skip to content

Commit b0e2ebd

Browse files
committed
feat(query): added support for querying by var bindings
1 parent cd21df3 commit b0e2ebd

File tree

12 files changed

+411
-189
lines changed

12 files changed

+411
-189
lines changed

modules/angular2/src/core/annotations_impl/di.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {CONST, Type, stringify, isPresent} from 'angular2/src/facade/lang';
1+
import {CONST, Type, stringify, isPresent, StringWrapper, isString} from 'angular2/src/facade/lang';
22
import {DependencyAnnotation} from 'angular2/src/di/annotations_impl';
33
import {resolveForwardRef} from 'angular2/di';
44

@@ -55,12 +55,17 @@ export class Attribute extends DependencyAnnotation {
5555
@CONST()
5656
export class Query extends DependencyAnnotation {
5757
descendants: boolean;
58-
constructor(private _selector:Type, {descendants = false}: {descendants?: boolean} = {}) {
58+
constructor(private _selector: Type | string,
59+
{descendants = false}: {descendants?: boolean} = {}) {
5960
super();
6061
this.descendants = descendants;
6162
}
6263

6364
get selector() { return resolveForwardRef(this._selector); }
6465

66+
get isVarBindingQuery(): boolean { return isString(this.selector); }
67+
68+
get varBindings(): List<string> { return StringWrapper.split(this.selector, new RegExp(",")); }
69+
6570
toString() { return `@Query(${stringify(this.selector)})`; }
6671
}

modules/angular2/src/core/compiler/element_binder.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export class ElementBinder {
1313

1414
constructor(public index: int, public parent: ElementBinder, public distanceToParent: int,
1515
public protoElementInjector: eiModule.ProtoElementInjector,
16-
public directiveVariableBindings: Map<string, number>,
1716
public componentDirective: DirectiveBinding) {
1817
if (isBlank(index)) {
1918
throw new BaseException('null index not allowed.');

modules/angular2/src/core/compiler/element_injector.ts

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -393,15 +393,17 @@ export class ProtoElementInjector {
393393
_strategy: _ProtoElementInjectorStrategy;
394394

395395
static create(parent: ProtoElementInjector, index: number, bindings: List<ResolvedBinding>,
396-
firstBindingIsComponent: boolean, distanceToParent: number) {
396+
firstBindingIsComponent: boolean, distanceToParent: number,
397+
directiveVariableBindings: Map<string, number>) {
397398
var bd = [];
398399

399400
ProtoElementInjector._createDirectiveBindingData(bindings, bd, firstBindingIsComponent);
400401
if (firstBindingIsComponent) {
401402
ProtoElementInjector._createViewInjectorBindingData(bindings, bd);
402403
}
403404
ProtoElementInjector._createHostInjectorBindingData(bindings, bd, firstBindingIsComponent);
404-
return new ProtoElementInjector(parent, index, bd, distanceToParent, firstBindingIsComponent);
405+
return new ProtoElementInjector(parent, index, bd, distanceToParent, firstBindingIsComponent,
406+
directiveVariableBindings);
405407
}
406408

407409
private static _createDirectiveBindingData(dirBindings: List<ResolvedBinding>,
@@ -450,7 +452,8 @@ export class ProtoElementInjector {
450452
}
451453

452454
constructor(public parent: ProtoElementInjector, public index: int, bd: List<BindingData>,
453-
public distanceToParent: number, public _firstBindingIsComponent: boolean) {
455+
public distanceToParent: number, public _firstBindingIsComponent: boolean,
456+
public directiveVariableBindings: Map<string, number>) {
454457
var length = bd.length;
455458
this.eventEmitterAccessors = ListWrapper.createFixedSize(length);
456459
this.hostActionAccessors = ListWrapper.createFixedSize(length);
@@ -693,12 +696,15 @@ export class ElementInjector extends TreeNode<ElementInjector> {
693696
}
694697

695698
onAllChangesDone(): void {
696-
if (isPresent(this._query0) && this._query0.originator === this)
699+
if (isPresent(this._query0) && this._query0.originator === this) {
697700
this._query0.list.fireCallbacks();
698-
if (isPresent(this._query1) && this._query1.originator === this)
701+
}
702+
if (isPresent(this._query1) && this._query1.originator === this) {
699703
this._query1.list.fireCallbacks();
700-
if (isPresent(this._query2) && this._query2.originator === this)
704+
}
705+
if (isPresent(this._query2) && this._query2.originator === this) {
701706
this._query2.list.fireCallbacks();
707+
}
702708
}
703709

704710
hydrate(injector: Injector, host: ElementInjector, preBuiltObjects: PreBuiltObjects): void {
@@ -716,9 +722,22 @@ export class ElementInjector extends TreeNode<ElementInjector> {
716722
this._checkShadowDomAppInjector(this._shadowDomAppInjector);
717723

718724
this._strategy.hydrate();
725+
726+
this._addVarBindingsToQueries();
727+
719728
this.hydrated = true;
720729
}
721730

731+
hasVariableBinding(name: string): boolean {
732+
var vb = this._proto.directiveVariableBindings;
733+
return isPresent(vb) && MapWrapper.contains(vb, name);
734+
}
735+
736+
getVariableBinding(name: string): any {
737+
var index = MapWrapper.get(this._proto.directiveVariableBindings, name);
738+
return isPresent(index) ? this.getDirectiveAtIndex(<number>index) : this.getElementRef();
739+
}
740+
722741
private _createShadowDomAppInjector(componentDirective: DirectiveBinding,
723742
appInjector: Injector): Injector {
724743
if (!ListWrapper.isEmpty(componentDirective.resolvedAppInjectables)) {
@@ -752,6 +771,10 @@ export class ElementInjector extends TreeNode<ElementInjector> {
752771
return this._proto.hostActionAccessors;
753772
}
754773

774+
getDirectiveVariableBindings(): Map<string, number> {
775+
return this._proto.directiveVariableBindings;
776+
}
777+
755778
getComponent(): any { return this._strategy.getComponent(); }
756779

757780
getElementRef(): ElementRef {
@@ -884,6 +907,23 @@ export class ElementInjector extends TreeNode<ElementInjector> {
884907
}
885908
}
886909

910+
private _addVarBindingsToQueries(): void {
911+
this._addVarBindingsToQuery(this._query0);
912+
this._addVarBindingsToQuery(this._query1);
913+
this._addVarBindingsToQuery(this._query2);
914+
}
915+
916+
private _addVarBindingsToQuery(queryRef: QueryRef): void {
917+
if (isBlank(queryRef) || !queryRef.query.isVarBindingQuery) return;
918+
919+
var vb = queryRef.query.varBindings;
920+
for (var i = 0; i < vb.length; ++i) {
921+
if (this.hasVariableBinding(vb[i])) {
922+
queryRef.list.add(this.getVariableBinding(vb[i]));
923+
}
924+
}
925+
}
926+
887927
private _createQueryRef(query: Query): void {
888928
var queryList = new QueryList<any>();
889929
if (isBlank(this._query0)) {
@@ -1454,13 +1494,32 @@ class QueryRef {
14541494

14551495
visit(inj: ElementInjector, aggregator: any[]): void {
14561496
if (isBlank(inj) || !inj._hasQuery(this)) return;
1457-
if (inj.hasDirective(this.query.selector)) {
1458-
aggregator.push(inj.get(this.query.selector));
1497+
1498+
if (this.query.isVarBindingQuery) {
1499+
this._aggregateVariableBindings(inj, aggregator);
1500+
} else {
1501+
this._aggregateDirective(inj, aggregator);
14591502
}
1503+
14601504
var child = inj._head;
14611505
while (isPresent(child)) {
14621506
this.visit(child, aggregator);
14631507
child = child._next;
14641508
}
14651509
}
1510+
1511+
private _aggregateVariableBindings(inj: ElementInjector, aggregator: List<any>): void {
1512+
var vb = this.query.varBindings;
1513+
for (var i = 0; i < vb.length; ++i) {
1514+
if (inj.hasVariableBinding(vb[i])) {
1515+
aggregator.push(inj.getVariableBinding(vb[i]));
1516+
}
1517+
}
1518+
}
1519+
1520+
private _aggregateDirective(inj: ElementInjector, aggregator: List<any>): void {
1521+
if (inj.hasDirective(this.query.selector)) {
1522+
aggregator.push(inj.get(this.query.selector));
1523+
}
1524+
}
14661525
}

modules/angular2/src/core/compiler/proto_view_factory.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,13 @@ function _createProtoElementInjector(binderIndex, parentPeiWithDistance, renderE
336336
// so that, when hydrating, $implicit can be set to the element.
337337
var hasVariables = MapWrapper.size(renderElementBinder.variableBindings) > 0;
338338
if (directiveBindings.length > 0 || hasVariables) {
339-
protoElementInjector = ProtoElementInjector.create(
340-
parentPeiWithDistance.protoElementInjector, binderIndex, directiveBindings,
341-
isPresent(componentDirectiveBinding), parentPeiWithDistance.distance);
339+
var directiveVariableBindings =
340+
createDirectiveVariableBindings(renderElementBinder, directiveBindings);
341+
342+
protoElementInjector =
343+
ProtoElementInjector.create(parentPeiWithDistance.protoElementInjector, binderIndex,
344+
directiveBindings, isPresent(componentDirectiveBinding),
345+
parentPeiWithDistance.distance, directiveVariableBindings);
342346
protoElementInjector.attributes = renderElementBinder.readAttributes;
343347
}
344348
return protoElementInjector;
@@ -351,12 +355,8 @@ function _createElementBinder(protoView, boundElementIndex, renderElementBinder,
351355
if (renderElementBinder.parentIndex !== -1) {
352356
parent = protoView.elementBinders[renderElementBinder.parentIndex];
353357
}
354-
355-
var directiveVariableBindings =
356-
createDirectiveVariableBindings(renderElementBinder, directiveBindings);
357-
var elBinder =
358-
protoView.bindElement(parent, renderElementBinder.distanceToParent, protoElementInjector,
359-
directiveVariableBindings, componentDirectiveBinding);
358+
var elBinder = protoView.bindElement(parent, renderElementBinder.distanceToParent,
359+
protoElementInjector, componentDirectiveBinding);
360360
protoView.bindEvent(renderElementBinder.eventBindings, boundElementIndex, -1);
361361
// variables
362362
// The view's locals needs to have a full set of variable names at construction time
@@ -371,7 +371,7 @@ function _createElementBinder(protoView, boundElementIndex, renderElementBinder,
371371

372372
export function createDirectiveVariableBindings(
373373
renderElementBinder: renderApi.ElementBinder,
374-
directiveBindings: List<DirectiveBinding>): Map<String, number> {
374+
directiveBindings: List<DirectiveBinding>): Map<string, number> {
375375
var directiveVariableBindings = MapWrapper.create();
376376
MapWrapper.forEach(renderElementBinder.variableBindings, (templateName, exportAs) => {
377377
var dirIndex = _findDirectiveIndexByExportAs(renderElementBinder, directiveBindings, exportAs);

modules/angular2/src/core/compiler/view.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,9 @@ export class AppProtoView {
177177

178178
bindElement(parent: ElementBinder, distanceToParent: int,
179179
protoElementInjector: ProtoElementInjector,
180-
directiveVariableBindings: Map<string, number>,
181180
componentDirective: DirectiveBinding = null): ElementBinder {
182-
var elBinder =
183-
new ElementBinder(this.elementBinders.length, parent, distanceToParent,
184-
protoElementInjector, directiveVariableBindings, componentDirective);
181+
var elBinder = new ElementBinder(this.elementBinders.length, parent, distanceToParent,
182+
protoElementInjector, componentDirective);
185183

186184
this.elementBinders.push(elBinder);
187185
return elBinder;

modules/angular2/src/core/compiler/view_manager_utils.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,28 +149,30 @@ export class AppViewManagerUtils {
149149

150150
var binders = view.proto.elementBinders;
151151
for (var i = 0; i < binders.length; ++i) {
152-
var binder = binders[i];
153152
var elementInjector = view.elementInjectors[i];
154153

155154
if (isPresent(elementInjector)) {
156155
elementInjector.hydrate(appInjector, hostElementInjector, view.preBuiltObjects[i]);
156+
this._populateViewLocals(view, elementInjector);
157157
this._setUpEventEmitters(view, elementInjector, i);
158158
this._setUpHostActions(view, elementInjector, i);
159-
160-
if (isPresent(binder.directiveVariableBindings)) {
161-
MapWrapper.forEach(binder.directiveVariableBindings, (directiveIndex, name) => {
162-
if (isBlank(directiveIndex)) {
163-
view.locals.set(name, elementInjector.getElementRef().domElement);
164-
} else {
165-
view.locals.set(name, elementInjector.getDirectiveAtIndex(directiveIndex));
166-
}
167-
});
168-
}
169159
}
170160
}
171161
view.changeDetector.hydrate(view.context, view.locals, view);
172162
}
173163

164+
_populateViewLocals(view: viewModule.AppView, elementInjector: eli.ElementInjector): void {
165+
if (isPresent(elementInjector.getDirectiveVariableBindings())) {
166+
MapWrapper.forEach(elementInjector.getDirectiveVariableBindings(), (directiveIndex, name) => {
167+
if (isBlank(directiveIndex)) {
168+
view.locals.set(name, elementInjector.getElementRef().domElement);
169+
} else {
170+
view.locals.set(name, elementInjector.getDirectiveAtIndex(directiveIndex));
171+
}
172+
});
173+
}
174+
}
175+
174176
_getOrCreateViewContainer(parentView: viewModule.AppView, boundElementIndex: number) {
175177
var viewContainer = parentView.viewContainers[boundElementIndex];
176178
if (isBlank(viewContainer)) {

modules/angular2/test/core/compiler/compiler_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,11 +481,11 @@ function createProtoView(elementBinders = null) {
481481

482482
function createComponentElementBinder(directiveResolver, type) {
483483
var binding = createDirectiveBinding(directiveResolver, type);
484-
return new ElementBinder(0, null, 0, null, null, binding);
484+
return new ElementBinder(0, null, 0, null, binding);
485485
}
486486

487487
function createViewportElementBinder(nestedProtoView) {
488-
var elBinder = new ElementBinder(0, null, 0, null, null, null);
488+
var elBinder = new ElementBinder(0, null, 0, null, null);
489489
elBinder.nestedProtoView = nestedProtoView;
490490
return elBinder;
491491
}

modules/angular2/test/core/compiler/element_injector_spec.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ class NeedsQuery {
163163
constructor(@Query(CountingDirective) query: QueryList<CountingDirective>) { this.query = query; }
164164
}
165165

166+
@Injectable()
167+
class NeedsQueryByVarBindings {
168+
query: QueryList<any>;
169+
constructor(@Query("one,two") query: QueryList<any>) { this.query = query; }
170+
}
171+
166172
@Injectable()
167173
class NeedsElementRef {
168174
elementRef;
@@ -229,13 +235,13 @@ export function main() {
229235
dynamicBindings.push(bind(i).toValue(i));
230236
}
231237

232-
function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false) {
238+
function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false, dirVariableBindings = null) {
233239
var directiveBinding = ListWrapper.map(bindings, b => {
234240
if (b instanceof DirectiveBinding) return b;
235241
if (b instanceof Binding) return DirectiveBinding.createFromBinding(b, null);
236242
return DirectiveBinding.createFromType(b, null);
237243
});
238-
return ProtoElementInjector.create(parent, index, directiveBinding, hasShadowRoot, distance);
244+
return ProtoElementInjector.create(parent, index, directiveBinding, hasShadowRoot, distance, dirVariableBindings);
239245
}
240246

241247
function humanize(tree: TreeNode<any>, names: List<List<any>>) {
@@ -248,10 +254,10 @@ export function main() {
248254
}
249255

250256
function injector(bindings, lightDomAppInjector = null, isComponent: boolean = false,
251-
preBuiltObjects = null, attributes = null) {
257+
preBuiltObjects = null, attributes = null, dirVariableBindings = null) {
252258
if (isBlank(lightDomAppInjector)) lightDomAppInjector = appInjector;
253259

254-
var proto = createPei(null, 0, bindings, 0, isComponent);
260+
var proto = createPei(null, 0, bindings, 0, isComponent, dirVariableBindings);
255261
proto.attributes = attributes;
256262

257263
var inj = proto.instantiate(null);
@@ -942,7 +948,7 @@ export function main() {
942948
});
943949
});
944950

945-
describe('directive queries', () => {
951+
describe('queries', () => {
946952
var preBuildObjects = defaultPreBuiltObjects;
947953
beforeEach(() => { _constructionCount = 0; });
948954

@@ -963,9 +969,39 @@ export function main() {
963969

964970
it('should contain directives on the same injector', () => {
965971
var inj = injector(ListWrapper.concat([NeedsQuery, CountingDirective], extraBindings), null,
966-
false, preBuildObjects);
972+
false, preBuildObjects);
967973

968974
expectDirectives(inj.get(NeedsQuery).query, CountingDirective, [0]);
975+
})
976+
977+
it('should contain the element when no directives are bound to the var binding', () => {
978+
var dirs = [NeedsQueryByVarBindings];
979+
980+
var dirVariableBindings = MapWrapper.createFromStringMap({
981+
"one": null // element
982+
});
983+
984+
var inj = injector(ListWrapper.concat(dirs, extraBindings), null,
985+
false, preBuildObjects, null, dirVariableBindings);
986+
987+
expect(inj.get(NeedsQueryByVarBindings).query.first).toBeAnInstanceOf(ElementRef);
988+
});
989+
990+
it('should contain directives on the same injector when querying by variable bindings' +
991+
'in the order of var bindings specified in the query', () => {
992+
var dirs = [NeedsQueryByVarBindings, NeedsDirective, SimpleDirective];
993+
994+
var dirVariableBindings = MapWrapper.createFromStringMap({
995+
"one": 2, // 2 is the index of SimpleDirective
996+
"two": 1 // 1 is the index of NeedsDirective
997+
});
998+
999+
var inj = injector(ListWrapper.concat(dirs, extraBindings), null,
1000+
false, preBuildObjects, null, dirVariableBindings);
1001+
1002+
// NeedsQueryByVarBindings queries "one,two", so SimpleDirective should be before NeedsDirective
1003+
expect(inj.get(NeedsQueryByVarBindings).query.first).toBeAnInstanceOf(SimpleDirective);
1004+
expect(inj.get(NeedsQueryByVarBindings).query.last).toBeAnInstanceOf(NeedsDirective);
9691005
});
9701006

9711007
// Dart's restriction on static types in (a is A) makes this feature hard to implement.

0 commit comments

Comments
 (0)