Reputation: 1252
I am trying to set up an Angular2 component that automatically focuses an input element that is inserted via content projection.
The solution I am using is based off of this answer. I have an additional requirement that the input element might be nested inside another component. However, I am finding that the ContentChild
query is not able to detect elements buried deep inside ng-content
tags.
@Component({
selector: 'inner-feature',
template: "<input auto-focus>",
directives: [AutoFocus]
})
export class InnerFeature { }
@Component({
selector: 'feature',
template: `
<div [class.hide]="!show">
<ng-content></ng-content>
</div>
`
})
export class Feature {
@ContentChild(AutoFocus)
private _autoFocus: AutoFocus;
private _show: boolean = false;
@Input()
get show() {
return this._show;
}
set show(show: boolean) {
this._show = show;
if (show && this._autoFocus) {
setTimeout(() => this._autoFocus.focus());
}
}
}
@Component({
selector: 'my-app',
template: `
<div>
<button (click)="toggleFeature()">Toggle</button>
<feature [show]="showFeature">
<inner-feature></inner-feature>
</feature>
</div>
`,
directives: [Feature, InnerFeature]
})
export class App {
showFeature: boolean = false;
toggleFeature() {
this.showFeature = !this.showFeature;
}
}
The _autoFocus
property never gets populated. Contrast that with the case where the auto-focus
directive is not nested inside another component and it works fine. Is there a way to make this work?
(I have not pasted the code for AutoFocus
since it's not crucial to this example.)
See Plunker for a demo.
UPDATED code above to fix a missing directive.
Upvotes: 8
Views: 9546
Reputation: 1778
Use ContentChildren
with descendants
set to true
@ContentChildren(AutoFocus, { descendants: true })
Upvotes: 9
Reputation: 8165
Actually i want to invest more time in this problem to find a better solution, but for now i came up with the following which might help you already:
First you have to expose the AutoFocus
inside your InnerFeatures
(and you forgot to add AutoFocus
to your array of directives
) using @ViewChild
. This could look like this:
@Component({
selector: 'inner-feature',
template: "<input auto-focus>",
directives: [AutoFocus]
})
export class InnerFeature {
@ViewChild(AutoFocus)
autoFocus:AutoFocus;
}
Then in your parent component Feature
you could use @ContentChildren
which returns a QueryList
of the bound Component (in your case InnerFeature
).
In your show
method (or for example in or after ngAfterContentInit
) you can then access this list of InnerFeatures
:
export class Feature implements OnInit {
@ContentChild(AutoFocus)
private _autoFocus: AutoFocus;
@ContentChildren(InnerFeature)
private _innerFeatures: QueryList<InnerFeature>;
private _show: boolean = false;
@Input()
get show() {
return this._show;
}
set show(show: boolean) {
this._show = show;
if (show) {
setTimeout(() => {
if (this._autoFocus) {
this._autoFocus.focus();
}
if (this._innerFeatures) {
this._innerFeatures.map((innerFeature) => {
innerFeature.autoFocus.focus();
});
}
});
}
}
ngAfterContentInit() {
console.log(this._autoFocus);
console.log(this._innerFeatures);
}
}
I modified your plunker, so you can test it in action.
Might not be as dynamic as you probably want, but well, i hope it helps anyway.
I will try to come up with a better approach , if there won't be a better answer after the England vs. Island match ;)
Update: I updated my code, cause it threw errors when accessing _results
which is private. Use map()
instead for the QueryList
.
Upvotes: 2