问题
I've been trying to make a tab view and thought content projection might be a good approach.I learned it from this article. I thought that I could make it dynamic by just inputting a given array of components, and they would be displayed as the page for the selected tab.
Here is my attempt at doing this:
@Component({
selector: 'my-app',
template:`
<h1>Experiments</h1>
<button class="add-button" (click)="add()">Add</button>`
})
export class App {
components:Array<any> = [PrepSetupTab,FinalizeTab]
constructor(private cdr: ChangeDetectorRef,
private compFR: ComponentFactoryResolver,
private viewContainer: ViewContainerRef ){}
add():void{
var transTabRefs: Array<any>= []
this.components.forEach(tabComponent=>{
let compFactory = this.compFR.resolveComponentFactory(tabComponent);
let compRef = this.viewContainer.createComponent(compFactory);
let tabFactory = this.compFR.resolveComponentFactory(Tab);
let transcludedTabRef = this.viewContainer.createComponent(tabFactory,this.viewContainer.length - 1, undefined, [[compRef.location.nativeElement]]);
transTabRefs.push(transcludedTabRef.location.nativeElement);
})
let tabsFactory = this.compFR.resolveComponentFactory(Tabs); // notice this is the tabs not tab
this.viewContainer.createComponent(tabsFactory,0,undefined,[transTabRefs]);
}
}
Side note, add() as a click handler of button isn't intended functionality other to avoid this bug: "EXCEPTION: Expression has changed after it was checked"
. I get this bug if I put it in any life cycle hooks.Although that's a challenge to deal with later though.
So basically I am creating the each Component in the array, and then i'm sticking them in as ng-content for each created Tab Component. Then taking each Tab component and sticking it in ng-content for Tabs Component.
The problem is Tabs Component doesn't ever find the contentChildren of the dynamically created Tab children. Here is the code for the Tabs Component where the the content children are undefined.
@Component({
selector: 'tabs',
template:`
<ul class="nav nav-tabs">
<li *ngFor="let tab of tabs" (click)="selectTab(tab)" [class.active]="tab.active">
<a>{{tab.title}}</a>
</li>
</ul>
<ng-content></ng-content>
`
})
export class Tabs implements AfterContentInit {
@ContentChildren(Tab) tabs: QueryList<Tab>;
// contentChildren are set
ngAfterContentInit() {
// get all active tabs
let activeTabs = this.tabs.filter((tab)=>tab.active);
// if there is no active tab set, activate the first
if(activeTabs.length === 0) {
this.selectTab(this.tabs.first);
}
}
selectTab(tab: Tab){
// deactivate all tabs
this.tabs.toArray().forEach(tab => tab.active = false);
// activate the tab the user has clicked on.
tab.active = true;
}
}
It seems to be pretty clear to me that the Tab Component is created at a different time then when I need to access them as content children from the Tabs Component. I've also tried using the ChangeDetectionRef.changeDetect() but that doesn't help.
Maybe doing this all through content projection isn't easiest way or best so I am open to suggestions. Here is the plunk, thanks!
回答1:
@ContentChildren
won't work for components created dynamically.
Why?
That's because candidates for projection must be known at compile time.
Here is the function that is used to calculate ContentChildren
function calcQueryValues(view, startIndex, endIndex, queryDef, values) {
for (var /** @type {?} */ i = startIndex; i <= endIndex; i++) {
var /** @type {?} */ nodeDef = view.def.nodes[i];
var /** @type {?} */ valueType = nodeDef.matchedQueries[queryDef.id];
if (valueType != null) {
values.push(getQueryValue(view, nodeDef, valueType));
}
It uses nodes that are declared in viewDefinition
for component.
When you created tabs
component via createComponent and passed projectable nodes manually you didn't change viewDefinition
factory for tabs component and hence angular won't be aware about dynamic nodes.
A possible solution could be manually initializing your tabs
You can observe it in this Plunkr
回答2:
Take a look on the example I give here dynamically add elements to DOM, maybe you find something helpful there for your problem. At this point in your plnkr the tabs array is empty.
来源:https://stackoverflow.com/questions/44832735/angular-2-contentchidren-of-parent-component-are-undefined-when-dynamically-cre