feat(query): added support for querying by var bindings
This commit is contained in:
@ -481,11 +481,11 @@ function createProtoView(elementBinders = null) {
|
||||
|
||||
function createComponentElementBinder(directiveResolver, type) {
|
||||
var binding = createDirectiveBinding(directiveResolver, type);
|
||||
return new ElementBinder(0, null, 0, null, null, binding);
|
||||
return new ElementBinder(0, null, 0, null, binding);
|
||||
}
|
||||
|
||||
function createViewportElementBinder(nestedProtoView) {
|
||||
var elBinder = new ElementBinder(0, null, 0, null, null, null);
|
||||
var elBinder = new ElementBinder(0, null, 0, null, null);
|
||||
elBinder.nestedProtoView = nestedProtoView;
|
||||
return elBinder;
|
||||
}
|
||||
|
@ -163,6 +163,12 @@ class NeedsQuery {
|
||||
constructor(@Query(CountingDirective) query: QueryList<CountingDirective>) { this.query = query; }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
class NeedsQueryByVarBindings {
|
||||
query: QueryList<any>;
|
||||
constructor(@Query("one,two") query: QueryList<any>) { this.query = query; }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
class NeedsElementRef {
|
||||
elementRef;
|
||||
@ -229,13 +235,13 @@ export function main() {
|
||||
dynamicBindings.push(bind(i).toValue(i));
|
||||
}
|
||||
|
||||
function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false) {
|
||||
function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false, dirVariableBindings = null) {
|
||||
var directiveBinding = ListWrapper.map(bindings, b => {
|
||||
if (b instanceof DirectiveBinding) return b;
|
||||
if (b instanceof Binding) return DirectiveBinding.createFromBinding(b, null);
|
||||
return DirectiveBinding.createFromType(b, null);
|
||||
});
|
||||
return ProtoElementInjector.create(parent, index, directiveBinding, hasShadowRoot, distance);
|
||||
return ProtoElementInjector.create(parent, index, directiveBinding, hasShadowRoot, distance, dirVariableBindings);
|
||||
}
|
||||
|
||||
function humanize(tree: TreeNode<any>, names: List<List<any>>) {
|
||||
@ -248,10 +254,10 @@ export function main() {
|
||||
}
|
||||
|
||||
function injector(bindings, lightDomAppInjector = null, isComponent: boolean = false,
|
||||
preBuiltObjects = null, attributes = null) {
|
||||
preBuiltObjects = null, attributes = null, dirVariableBindings = null) {
|
||||
if (isBlank(lightDomAppInjector)) lightDomAppInjector = appInjector;
|
||||
|
||||
var proto = createPei(null, 0, bindings, 0, isComponent);
|
||||
var proto = createPei(null, 0, bindings, 0, isComponent, dirVariableBindings);
|
||||
proto.attributes = attributes;
|
||||
|
||||
var inj = proto.instantiate(null);
|
||||
@ -942,7 +948,7 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('directive queries', () => {
|
||||
describe('queries', () => {
|
||||
var preBuildObjects = defaultPreBuiltObjects;
|
||||
beforeEach(() => { _constructionCount = 0; });
|
||||
|
||||
@ -963,9 +969,39 @@ export function main() {
|
||||
|
||||
it('should contain directives on the same injector', () => {
|
||||
var inj = injector(ListWrapper.concat([NeedsQuery, CountingDirective], extraBindings), null,
|
||||
false, preBuildObjects);
|
||||
false, preBuildObjects);
|
||||
|
||||
expectDirectives(inj.get(NeedsQuery).query, CountingDirective, [0]);
|
||||
})
|
||||
|
||||
it('should contain the element when no directives are bound to the var binding', () => {
|
||||
var dirs = [NeedsQueryByVarBindings];
|
||||
|
||||
var dirVariableBindings = MapWrapper.createFromStringMap({
|
||||
"one": null // element
|
||||
});
|
||||
|
||||
var inj = injector(ListWrapper.concat(dirs, extraBindings), null,
|
||||
false, preBuildObjects, null, dirVariableBindings);
|
||||
|
||||
expect(inj.get(NeedsQueryByVarBindings).query.first).toBeAnInstanceOf(ElementRef);
|
||||
});
|
||||
|
||||
it('should contain directives on the same injector when querying by variable bindings' +
|
||||
'in the order of var bindings specified in the query', () => {
|
||||
var dirs = [NeedsQueryByVarBindings, NeedsDirective, SimpleDirective];
|
||||
|
||||
var dirVariableBindings = MapWrapper.createFromStringMap({
|
||||
"one": 2, // 2 is the index of SimpleDirective
|
||||
"two": 1 // 1 is the index of NeedsDirective
|
||||
});
|
||||
|
||||
var inj = injector(ListWrapper.concat(dirs, extraBindings), null,
|
||||
false, preBuildObjects, null, dirVariableBindings);
|
||||
|
||||
// NeedsQueryByVarBindings queries "one,two", so SimpleDirective should be before NeedsDirective
|
||||
expect(inj.get(NeedsQueryByVarBindings).query.first).toBeAnInstanceOf(SimpleDirective);
|
||||
expect(inj.get(NeedsQueryByVarBindings).query.last).toBeAnInstanceOf(NeedsDirective);
|
||||
});
|
||||
|
||||
// Dart's restriction on static types in (a is A) makes this feature hard to implement.
|
||||
|
@ -26,154 +26,247 @@ export function main() {
|
||||
BrowserDomAdapter.makeCurrent();
|
||||
describe('Query API', () => {
|
||||
|
||||
it('should contain all direct child directives in the light dom',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<div text="1"></div>' +
|
||||
'<needs-query text="2"><div text="3">' +
|
||||
'<div text="too-deep"></div>' +
|
||||
'</div></needs-query>' +
|
||||
'<div text="4"></div>';
|
||||
describe("querying by directive type", () => {
|
||||
it('should contain all direct child directives in the light dom',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<div text="1"></div>' +
|
||||
'<needs-query text="2"><div text="3">' +
|
||||
'<div text="too-deep"></div>' +
|
||||
'</div></needs-query>' +
|
||||
'<div text="4"></div>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3|');
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should contain all directives in the light dom when descendants flag is used',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<div text="1"></div>' +
|
||||
'<needs-query-desc text="2"><div text="3">' +
|
||||
'<div text="4"></div>' +
|
||||
'</div></needs-query-desc>' +
|
||||
'<div text="5"></div>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3|4|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should contain all directives in the light dom',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<div text="1"></div>' +
|
||||
'<needs-query text="2"><div text="3"></div></needs-query>' +
|
||||
'<div text="4"></div>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
// TODO(rado): The test below should be using descendants: false,
|
||||
// but due to a bug with how injectors are hooked up query considers the
|
||||
// directives to be distances 2 instead of direct children.
|
||||
it('should reflect dynamically inserted directives',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template =
|
||||
'<div text="1"></div>' +
|
||||
'<needs-query-desc text="2"><div *ng-if="shouldShow" [text]="\'3\'"></div></needs-query-desc>' +
|
||||
'<div text="4"></div>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|');
|
||||
|
||||
view.context.shouldShow = true;
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect moved directives',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template =
|
||||
'<div text="1"></div>' +
|
||||
'<needs-query-desc text="2"><div *ng-for="var i of list" [text]="i"></div></needs-query-desc>' +
|
||||
'<div text="4"></div>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
view.detectChanges();
|
||||
|
||||
expect(view.rootNodes).toHaveText('2|1d|2d|3d|');
|
||||
|
||||
view.context.list = ['3d', '2d'];
|
||||
view.detectChanges();
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3d|2d|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should notify query on change',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<needs-query-desc #q>' +
|
||||
'<div text="1"></div>' +
|
||||
'<div *ng-if="shouldShow" text="2"></div>' +
|
||||
'</needs-query-desc>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
var q = view.rawView.locals.get("q");
|
||||
view.detectChanges();
|
||||
|
||||
q.query.onChange(() => {
|
||||
expect(q.query.first.text).toEqual("1");
|
||||
expect(q.query.last.text).toEqual("2");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
view.context.shouldShow = true;
|
||||
view.detectChanges();
|
||||
});
|
||||
}));
|
||||
it('should contain all directives in the light dom when descendants flag is used',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<div text="1"></div>' +
|
||||
'<needs-query-desc text="2"><div text="3">' +
|
||||
'<div text="4"></div>' +
|
||||
'</div></needs-query-desc>' +
|
||||
'<div text="5"></div>';
|
||||
|
||||
it("should notify child's query before notifying parent's query",
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<needs-query-desc #q1>' +
|
||||
'<needs-query-desc #q2>' +
|
||||
'<div text="1"></div>' +
|
||||
'</needs-query-desc>' +
|
||||
'</needs-query-desc>';
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3|4|');
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
var q1 = view.rawView.locals.get("q1");
|
||||
var q2 = view.rawView.locals.get("q2");
|
||||
|
||||
var firedQ2 = false;
|
||||
|
||||
q2.query.onChange(() => { firedQ2 = true; });
|
||||
q1.query.onChange(() => {
|
||||
expect(firedQ2).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
view.detectChanges();
|
||||
});
|
||||
}));
|
||||
it('should contain all directives in the light dom',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<div text="1"></div>' +
|
||||
'<needs-query text="2"><div text="3"></div></needs-query>' +
|
||||
'<div text="4"></div>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
// TODO(rado): The test below should be using descendants: false,
|
||||
// but due to a bug with how injectors are hooked up query considers the
|
||||
// directives to be distances 2 instead of direct children.
|
||||
it('should reflect dynamically inserted directives',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template =
|
||||
'<div text="1"></div>' +
|
||||
'<needs-query-desc text="2"><div *ng-if="shouldShow" [text]="\'3\'"></div></needs-query-desc>' +
|
||||
'<div text="4"></div>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|');
|
||||
|
||||
view.context.shouldShow = true;
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect moved directives',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template =
|
||||
'<div text="1"></div>' +
|
||||
'<needs-query-desc text="2"><div *ng-for="var i of list" [text]="i"></div></needs-query-desc>' +
|
||||
'<div text="4"></div>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
|
||||
expect(view.rootNodes).toHaveText('2|1d|2d|3d|');
|
||||
|
||||
view.context.list = ['3d', '2d'];
|
||||
view.detectChanges();
|
||||
view.detectChanges();
|
||||
expect(view.rootNodes).toHaveText('2|3d|2d|');
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe("onChange", () => {
|
||||
it('should notify query on change',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<needs-query-desc #q>' +
|
||||
'<div text="1"></div>' +
|
||||
'<div *ng-if="shouldShow" text="2"></div>' +
|
||||
'</needs-query-desc>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
var q = view.rawView.locals.get("q");
|
||||
view.detectChanges();
|
||||
|
||||
q.query.onChange(() => {
|
||||
expect(q.query.first.text).toEqual("1");
|
||||
expect(q.query.last.text).toEqual("2");
|
||||
async.done();
|
||||
});
|
||||
|
||||
view.context.shouldShow = true;
|
||||
view.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
it("should notify child's query before notifying parent's query",
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<needs-query-desc #q1>' +
|
||||
'<needs-query-desc #q2>' +
|
||||
'<div text="1"></div>' +
|
||||
'</needs-query-desc>' +
|
||||
'</needs-query-desc>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
var q1 = view.rawView.locals.get("q1");
|
||||
var q2 = view.rawView.locals.get("q2");
|
||||
|
||||
var firedQ2 = false;
|
||||
|
||||
q2.query.onChange(() => { firedQ2 = true; });
|
||||
q1.query.onChange(() => {
|
||||
expect(firedQ2).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
|
||||
view.detectChanges();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe("querying by var binding", () => {
|
||||
it('should contain all the child directives in the light dom with the given var binding',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template =
|
||||
'<needs-query-by-var-binding #q>' +
|
||||
'<div *ng-for="#item of list" [text]="item" #text-label="textDir"></div>' +
|
||||
'</needs-query-by-var-binding>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
var q = view.rawView.locals.get("q");
|
||||
|
||||
view.context.list = ['1d', '2d'];
|
||||
|
||||
view.detectChanges();
|
||||
|
||||
expect(q.query.first.text).toEqual("1d");
|
||||
expect(q.query.last.text).toEqual("2d");
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support querying by multiple var bindings',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<needs-query-by-var-bindings #q>' +
|
||||
'<div text="one" #text-label1="textDir"></div>' +
|
||||
'<div text="two" #text-label2="textDir"></div>' +
|
||||
'</needs-query-by-var-bindings>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
var q = view.rawView.locals.get("q");
|
||||
view.detectChanges();
|
||||
|
||||
expect(q.query.first.text).toEqual("one");
|
||||
expect(q.query.last.text).toEqual("two");
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reflect dynamically inserted directives',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template =
|
||||
'<needs-query-by-var-binding #q>' +
|
||||
'<div *ng-for="#item of list" [text]="item" #text-label="textDir"></div>' +
|
||||
'</needs-query-by-var-binding>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
var q = view.rawView.locals.get("q");
|
||||
|
||||
view.context.list = ['1d', '2d'];
|
||||
|
||||
view.detectChanges();
|
||||
|
||||
view.context.list = ['2d', '1d'];
|
||||
|
||||
view.detectChanges();
|
||||
|
||||
expect(q.query.last.text).toEqual("1d");
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should contain all the elements in the light dom with the given var binding',
|
||||
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
|
||||
var template = '<needs-query-by-var-binding #q>' +
|
||||
'<div template="ng-for: #item of list">' +
|
||||
'<div #text-label>{{item}}</div>' +
|
||||
'</div>' +
|
||||
'</needs-query-by-var-binding>';
|
||||
|
||||
tb.createView(MyComp, {html: template})
|
||||
.then((view) => {
|
||||
var q = view.rawView.locals.get("q");
|
||||
|
||||
view.context.list = ['1d', '2d'];
|
||||
|
||||
view.detectChanges();
|
||||
|
||||
expect(q.query.first.domElement).toHaveText("1d");
|
||||
expect(q.query.last.domElement).toHaveText("2d");
|
||||
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Directive({selector: '[text]', properties: ['text']})
|
||||
@Directive({selector: '[text]', properties: ['text'], exportAs: 'textDir'})
|
||||
@Injectable()
|
||||
class TextDirective {
|
||||
text: string;
|
||||
@ -198,8 +291,38 @@ class NeedsQueryDesc {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'needs-query-by-var-binding'})
|
||||
@View({directives: [], template: '<content>'})
|
||||
@Injectable()
|
||||
class NeedsQueryByLabel {
|
||||
query: QueryList<any>;
|
||||
constructor(@Query("textLabel", {descendants: true}) query: QueryList<any>) {
|
||||
this.query = query;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'needs-query-by-var-bindings'})
|
||||
@View({directives: [], template: '<content>'})
|
||||
@Injectable()
|
||||
class NeedsQueryByTwoLabels {
|
||||
query: QueryList<any>;
|
||||
constructor(@Query("textLabel1,textLabel2", {descendants: true}) query: QueryList<any>) {
|
||||
this.query = query;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'my-comp'})
|
||||
@View({directives: [NeedsQuery, NeedsQueryDesc, TextDirective, NgIf, NgFor]})
|
||||
@View({
|
||||
directives: [
|
||||
NeedsQuery,
|
||||
NeedsQueryDesc,
|
||||
NeedsQueryByLabel,
|
||||
NeedsQueryByTwoLabels,
|
||||
TextDirective,
|
||||
NgIf,
|
||||
NgFor
|
||||
]
|
||||
})
|
||||
@Injectable()
|
||||
class MyComp {
|
||||
shouldShow: boolean;
|
||||
|
@ -58,11 +58,11 @@ export function main() {
|
||||
return DirectiveBinding.createFromType(type, annotation);
|
||||
}
|
||||
|
||||
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null, null); }
|
||||
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null); }
|
||||
|
||||
function createComponentElBinder(nestedProtoView = null) {
|
||||
var binding = createDirectiveBinding(SomeComponent);
|
||||
var binder = new ElementBinder(0, null, 0, null, null, binding);
|
||||
var binder = new ElementBinder(0, null, 0, null, binding);
|
||||
binder.nestedProtoView = nestedProtoView;
|
||||
return binder;
|
||||
}
|
||||
|
@ -48,11 +48,11 @@ export function main() {
|
||||
return DirectiveBinding.createFromType(type, annotation);
|
||||
}
|
||||
|
||||
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null, null); }
|
||||
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null); }
|
||||
|
||||
function createComponentElBinder(nestedProtoView = null) {
|
||||
var binding = createDirectiveBinding(SomeComponent);
|
||||
var binder = new ElementBinder(0, null, 0, null, null, binding);
|
||||
var binder = new ElementBinder(0, null, 0, null, binding);
|
||||
binder.nestedProtoView = nestedProtoView;
|
||||
return binder;
|
||||
}
|
||||
|
Reference in New Issue
Block a user