Angular - Esperar a recibir datos antes de cargar plantilla

Entonces, tengo un componente que renderiza varios componentes dinámicamente, con esta plantilla:

<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>

El caso es que los datos necesarios para rellenar esos componentes los estoy obteniendo de una llamada a la API:

      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);
            }
        );

Así que, una vez tengo mis datos, disparo un servicio Observable al que están suscritos mis componentes, que procesarán los datos recogidos.

PROBLEMA:

**Si la llamada a la API se realiza antes de que se carguen todos los componentes, estaré disparando estos métodos de servicio pasando datos pero nadie estará suscrito a esos Observables.

Una aproximación sería:

Cargar los datos primero, y sólo cuando tenga los datos cargados, renderizar la plantilla y, por tanto, renderizar todos estos componentes dinámicamente y sólo entonces disparar estos métodos de servicio (Observables).

No quiero hacer una llamada al API por cada componente, porque pueden ser como 60 componentes, prefiero perder abstracción del código pero prefiero hacer algo así:

// 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
                }
            }
        );

Para hacer esto, necesito asegurarme de que this.filters está definido y así, llego a la conclusión:

**¿Cómo puedo esperar a que termine la llamada a la API y se defina this.filters antes de renderizar el html de mi plantilla?

Lo siento si mi pregunta es un poco larga, si necesita más detalles sólo hágamelo saber.

Gracias.

Solución

Después de estudiar los diferentes enfoques que la gente me dio, encontré la solución en la tubería async. Pero, me tomó un tiempo entender cómo implementarlo.

Solución:

// Declaring the Promise, yes! Promise!
filtersLoaded: Promise;

// 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);
        }
    }
);

En la plantilla, utilizo la tubería async que necesita un Observable o un Promise.

<div *ngIf="filtersLoaded | async">
    <div [saJquiAccordion]="{active: group.value['collapsed']}" *ngFor="let group of filterGroupsTemplate | keysCheckDisplay;">
        <div>
            <h4>{{group.key | i18n}}</h4>





        </div>
    </div>
</div>

NOTA:

  • La tubería async necesita un Observable o una Promise por lo que he entendido, por eso la única forma de hacerla funcionar era creando una `Promise
  • No utilicé el enfoque resolver porque se utiliza cuando se llega al componente a través del enrutamiento de Angular. Este componente es parte de un componente mayor y no se instancian a través de enrutamiento como cualquier otro componente normal. (Aunque he probado ese enfoque, he trabajado un poco con él, pero no ha funcionado)
Comentarios (3)

Podrías utilizar un resolver para asegurarte de que esos datos están cargados (o tus filtros han sido inicializados) antes de que se active la ruta.

https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html

https://angular.io/api/router/Resolve

Comentarios (7)