Reputation: 3520
I want to do a component reusable, setting multiple children and render one component at time, I have a collection of views got with @ContenChildren, at parent component, something like this:
@ContentChildren(ChildComponent) children: QueryList<ChildComponent>;
//getCurrentChild function
getCurrentChild(){
//index is a position of child
return this.children.toArray()[this.index];
}
and render parent template:
<ng-container *ngTemplateOutlet="getCurrentChild().content"></ng-container>
content is rendered in the child component with @ViewChild:
@ViewChild('currentChild') child;
and the template looks like this:
<ng-template #currentChild>
<ng-content>
</ng-content>
</ng-template>
When I want to implement this structure, I do something like this:
<parent>
<child>
<specific-one></specific-one>
</child>
<child>
<specific-two></specific-two>
</child>
</parent>
Now I have a method in the parent component that is fired on click button and need to call a method in the specific components (specific-one or specific-two depending on currentChild):
export class SpecificOneComponent implements OnInit{
action(){
//this action will be called when parent is clicked
}
}
I've tried calling method trough child reference
but the action doesn't exit. also passing a context and neither. It looks like the way I'm setting the content in parent template is not right.
Any help would be appreciate it.
Upvotes: 1
Views: 1013
Reputation: 11934
Every child component will have its own content, so this means that it would be enough to only get the reference to the ng-template
in which there would be ng-content
.
Also, because you want to call methods from specific components when a certain event occurs on the parent, we are going to use a service in order to be able to notify the specific components.
private _shouldCallAction$ = new Subject();
shouldCallAction$ = this._shouldCallAction$.asObservable();
constructor() { }
triggerAction (uniqueCompId) {
this._shouldCallAction$.next(uniqueCompId);
}
This goes for every component that depends on some events that occur in the parent component.
private shouldCallActionSubscription: Subscription;
uniqueId: number | string;
constructor(private specificService: SpecificService) {
this.uniqueId = randomId();
}
ngOnInit() {
this.shouldCallActionSubscription = this.specificService.shouldCallAction$
.pipe(
filter(id => id === this.uniqueId)
)
.subscribe(() => {
console.log('calling action for specific-one')
});
}
ngOnDestroy () {
this.shouldCallActionSubscription.unsubscribe();
}
<ng-container *ngIf="instanceIdx === crtIdx">
<h3>trigger action of current specific component</h3>
<button (click)="triggerAction()">trigger</button>
</ng-container>
<ng-template #currentChild>
<ng-content></ng-content>
</ng-template>
Here you'll also need to get a reference to the specificComponent
in order to get its unique id.
// Before class
let instances = 0;
@ViewChild('currentChild', { static: true }) tpl: TemplateRef<any>;
@ContentChild('specific', { static: true }) specificComp;
get uniqueSpecificCompId () {
return this.specificComp.uniqueId;
}
constructor (private specificService: SpecificService) {
this.instanceIdx = instances++;
}
triggerAction () {
this.specificService.triggerAction(this.uniqueSpecificCompId);
}
@ViewChildren(ChildOneComponent) children: QueryList<ChildOneComponent>;
@ViewChild('container', { static: true, read: ViewContainerRef }) container: ViewContainerRef;
crtIdx = 0;
ngAfterViewInit () {
this.setNewView();
}
setNewView () {
this.container.clear();
this.container.createEmbeddedView(this.children.toArray()[this.crtIdx].tpl);
}
updateIndex (idx) {
this.crtIdx = idx;
this.setNewView();
}
<app-child [crtIdx]="crtIdx">
<!-- ... -->
<app-specific-two #specific></app-specific-two>
</app-child>
<app-child [crtIdx]="crtIdx">
<!-- ... -->
<app-specific-one #specific></app-specific-one>
</app-child>
<ng-container #container></ng-container>
<h3>Select a child</h3>
<button
*ngFor="let _ of [].constructor(n); let idx = index;"
(click)="updateIndex(idx)"
>
Select {{ idx + 1 }}
</button>
Best of luck!
Upvotes: 2