Angular - 等到我在加载模板之前收到数据

时间:2017-07-04 08:26:25

标签: angular typescript

所以,我有一个使用此模板动态渲染多个组件的组件

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

问题是填充这些组件所需的数据我从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);
            }
        );

所以,一旦我获得了数据,我就会触发我的组件订阅的服务Observable ,然后他们将处理收集的数据。

问题:

如果在所有组件加载之前进行API调用,我将触发这些服务方法传递数据,但没有人会订阅这些Observable。

方法是:

首先加载数据,并且只有在我加载数据时,我才会渲染模板,因此,动态渲染所有这些组件,然后我才会触发这些服务方法(Observables)。

我不想为每个组件进行API调用,因为它可能像60个组件,我宁愿松散抽象代码,但我还是喜欢这样做:

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

为了做到这一点,我需要确保定义this.filters,因此,我得出结论:

如何才能等到API调用结束并在呈现模板html之前定义this.filters

对不起,如果我的问题有点长,如果您需要更多详细信息,请告诉我。

谢谢!

5 个答案:

答案 0 :(得分:28)

在研究了人们给我的不同方法后,我在async管道上找到了解决方案。但是,我花了一些时间来了解如何实现它。

<强>解决方案:

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

在模板中,我使用需要asyncObservable

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>

注意:

    根据我的理解,
  • async管道需要ObservablePromise,这就是为什么使其成功的唯一方法是创建Promise
  • 我没有使用resolver方法,因为当你通过Angular的路由到达组件时会使用它。此组件是较大组件的一部分,并且不像任何其他正常组件那样通过路由实例化。 (尽管尝试过这种方法,但有点工作,却没有做到这一点)

答案 1 :(得分:8)

您可以使用resolver确保在激活路线之前加载这些数据(或者您的过滤器已初始化)。

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

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

答案 2 :(得分:2)

<p class="p-large">{{homeData?.meta[0].site_desc}}</p>

只需使用“?”在已从服务器加载数据的变量之后。

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

答案 3 :(得分:0)

最简单的解决方案是将其包装在 ngIf 中,我认为这不是一个好习惯,但它解决了问题。

在这种情况下错误是:未定义的标题

<div *ngIf="form">
  {{form.Title}}
</div>

答案 4 :(得分:0)

我知道这已经很晚了,但对我有用的是在我的减速器中为我的状态添加一个加载布尔值。

reducer.ts:

export interface State {
 things: Stuff;
 loaded: boolean;
}
export const reducers: ActionReducerMap<State> = {
    things: reducer,
    loaded: loadedReducer,
};

然后确保导出加载的函数,以便在状态恢复时切换为真

export function loadedReducer(state: boolean = false, action: ThingActions): boolean {
    switch (action.type) {
        case ThingActionTypes.GetThings:
            return true;
    }
    return state;
}

然后在你的 ts 订阅加载。

parent.component.ts:

this.loaded$ = this.store.pipe(select(fromReducer.loaded));

并将其用作模板中的异步管道。

parent.component.html:

        <ng-container *ngIf="(loaded$ | async); else loading">
            <child-component></child-component>
        </ng-container>
        <ng-template #loading></ng-template>