typescript - Angular - Warten, bis ich Daten erhalte, bevor die Vorlage geladen wird
Ich habe eine Komponente, die mehrere Komponenten dynamisch rendert, mit dieser Vorlage:
<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>
Die Sache ist die, dass die Daten, die benötigt werden, um diese Komponenten zu füllen, bekomme ich von einem API-Aufruf:
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);
}
);
Also, sobald ich meine Daten habe, löse ich ein Service-Observable aus, auf das meine Komponenten abonniert sind, und sie werden dann die gesammelten Daten verarbeiten.
PROBLEM
Wenn der API-Aufruf gemacht wird, bevor alle Komponenten geladen sind, werde ich diese Servicemethoden auslösen und Daten übergeben, aber niemand wird auf diese Observables abonniert sein.
Ein Ansatz wäre:
Lade zuerst die Daten, und erst wenn ich die Daten geladen habe, werde ich die Vorlage rendern und daher all diese Komponenten dynamisch rendern und erst dann werde ich diese Servicemethoden (Observables) auslösen.
Ich möchte für jede Komponente keinen API-Aufruf machen, weil es wie 60 Komponenten sein können. Ich verliere lieber Abstraktion im Code, aber ich ziehe es vor, so etwas zu machen:
// 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
}
}
);
Um dies zu tun, muss ich sicherstellen, dass this.filters definiert ist, und komme daher zu folgendem Schluss:
Wie kann ich warten, bis der API-Aufruf beendet ist und this.filters definiert ist, bevor ich meine Template-HTML rendere?
Entschuldigung, wenn meine Frage etwas lang ist, falls du weitere Details benötigst, lass es mich einfach wissen.
Danke!
Antwort #1
Nachdem ich die verschiedenen Ansätze untersucht hatte, die mir die Leute gegeben haben, fand ich die Lösung in der async-Pipe. Aber es hat eine Weile gedauert, bis ich verstanden habe, wie man sie implementiert.
Lösung:
// 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 der Vorlage verwende ich die async Pipe, die ein Observable oder ein Promise benötigt
<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>
HINWEIS:
async-Pipe benötigt einObservableoder einPromise, soweit ich verstanden habe, deshalb war der einzige Weg, es zum Laufen zu bringen, einPromisezu erstellen- Ich habe den
resolver-Ansatz nicht verwendet, weil er verwendet wird, wenn man über Angulars Routing zu der Komponente gelangt. Diese Komponente ist Teil einer größeren Komponente und wird nicht wie jede andere normale Komponente über Routing instanziiert. (Habe diesen Ansatz jedoch ausprobiert, hat ein wenig damit funktioniert, hat die Aufgabe aber nicht erfüllt)
Antwort #2
Sie könnten einen Resolver verwenden, um sicherzustellen, dass diese Daten geladen werden (oder Ihre Filter initialisiert wurden), bevor die Route aktiviert wird.
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html
https://angular.io/api/router/Resolve
Antwort #3
<p class="p-large">{{homeData?.meta[0].site_desc}}</p>
Habe gerade ein „?“ nach der Variablen verwendet, die mit Daten vom Server geladen wird.
home.component.ts
import { Component, OnInit } from '@angular/core';
import { HomeService } from '../services/home.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
public homeData: any;
constructor(private homeService: HomeService) {}
ngOnInit(): void {
this.homeService.getHomeData().subscribe( data => {
this.homeData = data[0];
}, error => {
console.log(error);
});
}
}
