I have a component that renders several components dynamically, with this template:
<div [saJquiAccordion]="{active: group.value['collapsed']}" *ngFor="let group of filterGroupsTemplate | keysCheckDisplay;"> <div> <h4>{{group.key | i18n}}</h4> <form id="ibo-{{group.key}}" class="form-horizontal" autocomplete="off" style="overflow: initial"> <fieldset *ngFor="let field of group.value | keys"> <ng-container *ngComponentOutlet="fieldSets[field.value.template]; ngModuleFactory: smartadminFormsModule;"></ng-container> </fieldset> </form> </div> </div>
The thing is that the data needed to fill those components I'm getting it from an API call:
this.getFiltersSubscription = this.getFilters().subscribe( (filters) => { this.filters = filters; log.info('API CALL. getting filters'); // Sending data to fieldform components this.iboService.updateIBOsRankList(filters['iboRank'].data); this.iboService.updateIBOsNewsletterOptions(filters['iboNewsletter'].data); this.iboService.updateIBOsTotalOrders(filters['iboTotalOrders'].data); } );
So, once I have my data, I'm triggering a service Observable which my components are subscribed to, and they will then process the gathered data.
PROBLEM
If the API call is made before all components load, I'll be triggering these service methods passing data but nobody will be subscribed to those Observables.
An approach would be to:
Load data first, and only when I have the data loaded, I'll render the template and, therefore, render all these components dynamically and only then I'll be triggering these service methods (Observables).
I don't want to make an API call for each component, because it can be like 60 components, I'll rather loose abstraction fo code but I prefer to do something like this:
// Listens to field's init and creates the fieldset triggering a service call that will be listened by the field component this.iboService.initIBOsFilters$.subscribe( (fieldName) => { if (fieldName === 'IBOsRankSelectorFieldComponent') { log.data('inside initIBOsFilters$ subscription, calling updateIBOsFilters()', fieldName); this.iboService.updateIBOsRankList(this.filters['iboRank'].data); // HERE I'M PASSING DATA TO THE COMPONENT RENDERED DYNAMICALY. BUT IF this.filters IS UNDEFINED, IT BREAKS } } );
In order to do this, I need to ensure that this.filters
is defined and thus, I come to conclusion:
How can I wait until API call ends and this.filters
is defined before rendering my template html?
Sorry if my question is a bit long, if you need any more details just let me know.
Thanks!
After studying the different approaches that people gave me, I found the solution on the async
pipe. But, it took me a while to understand how to implement it.
Solution:
// Declaring the Promise, yes! Promise! filtersLoaded: Promise<boolean>; // Later in the Component, where I gather the data, I set the resolve() of the Promise this.getFiltersSubscription = this.getFilters().subscribe( (filters) => { this.filters = filters; log.info('API CALL. getting filters'); this.filtersLoaded = Promise.resolve(true); // Setting the Promise as resolved after I have the needed data } ); // In this listener triggered by the dynamic components when instanced, // I pass the data, knowing that is defined because of the template change // Listens to field's init and creates the fieldset triggering a service call // that will be listened by the field component this.iboService.initIBOsFilters$.subscribe( (fieldName) => { if (fieldName === 'IBOsRankSelectorFieldComponent') { log.data('inside initIBOsFilters$ subscription, calling updateIBOsFilters()', fieldName); this.iboService.updateIBOsRankList(this.filters['iboRank'].data); } } );
In the template, I use the async
pipe that needs an Observable
or a Promise
<div *ngIf="filtersLoaded | async"> <div [saJquiAccordion]="{active: group.value['collapsed']}" *ngFor="let group of filterGroupsTemplate | keysCheckDisplay;"> <div> <h4>{{group.key | i18n}}</h4> <form id="ibo-{{group.key}}" class="form-horizontal" autocomplete="off" style="overflow: initial"> <fieldset *ngFor="let field of group.value | keys"> <ng-container *ngComponentOutlet="fieldSets[field.value.template]; ngModuleFactory: smartadminFormsModule;"></ng-container> </fieldset> </form> </div> </div> </div>
NOTE:
async
pipe need an Observable
or a Promise
from what I understood, that's why the only way to make it work was by creating a Promise
resolver
approach because it's used when you arrive to the component through Angular's routing. This component is part of a larger component and it's not instanced through routing like any other normal component. (Tried that approach though, worked a bit with it, didn't do the job) If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With