web-dev-qa-db-fra.com

Angular - Attendez de recevoir des données avant de charger le modèle

Donc, j'ai un composant qui rend plusieurs composants dynamiquement, avec ce 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>

Le problème est que les données nécessaires pour remplir ces composants, je les obtiens d'un appel d'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);
            }
        );

Donc, ne fois que j'ai mes données, je déclenche un service Observable auquel mes composants sont abonnés, et ils traiteront ensuite les données collectées.

PROBLEME:

Si l'appel API est effectué avant le chargement de tous les composants, je déclencherai le transfert de données par ces méthodes de service, mais personne ne sera abonné à ces observables.

Une approche serait de:

Commencez par charger les données, et ce n’est que lorsque les données seront chargées que je rendrai le modèle et, par conséquent, tous les composants de manière dynamique, puis je déclencherai ces méthodes de service (Observables).

Je ne veux pas faire d'appel API pour chaque composant, car il peut s'agir de 60 composants, je vais perdre l'abstraction du code, mais je préférerais faire quelque chose comme ça:

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

Pour ce faire, je dois m'assurer que this.filters est défini et j'arrive donc à la conclusion:

Comment puis-je attendre la fin de l'appel de l'API et this.filters est défini avant le rendu de mon modèle html?

Désolé si ma question est un peu longue, si vous avez besoin de plus de détails, faites le moi savoir.

Merci!

22
SrAxi

Après avoir étudié les différentes approches que les gens m'ont proposées, j'ai trouvé la solution sur le tuyau async. Mais, il m'a fallu un certain temps pour comprendre comment le mettre en œuvre.

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

Dans le modèle, j'utilise le tuyau async qui nécessite un Observable ou un 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 nécessite un Observable ou un Promise d'après ce que j'ai compris, c'est pourquoi la seule façon de le faire fonctionner était de créer un Promise
  • Je n'ai pas utilisé l'approche resolver car elle est utilisée lorsque vous arrivez au composant via le routage d'Angular. Ce composant fait partie d'un composant plus grand et n'est pas instancié via le routage comme tout autre composant normal. (J'ai essayé cette approche, j'ai travaillé un peu avec elle, je n'ai pas fait le travail)
31
SrAxi

Vous pouvez utiliser un resolver pour vous assurer que ces données sont chargées (ou que vos filtres ont été initialisés) avant l'activation de la route.

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

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

8
Matt