【问题标题】:Wait for Data Before Rendering View in Angular 5在 Angular 5 中渲染视图之前等待数据
【发布时间】:2018-09-05 08:29:24
【问题描述】:

我是 Angular 5 的新手,并且能够使用订阅创建可以访问服务方法的视图,但遇到渲染延迟问题。用户看到第一个“未定义”字符串,0.5 秒后它变为正确的值。

这是组件的 .ts 文件:

 public trips: Trip[];
  public numberOfUsers : Map<string, number> = new Map<string, number>();

  constructor(private tripService: TripService) { }

  ngOnInit() {
   this.getTrips();
  }

  getTrips() {
    this.tripService.getTripForMe().subscribe(
      data => { this.trips = data;},
      err => console.error(err),
      () => this.initNumberOfUsers()
    );
  }

  initNumberOfUsers() {
    for (let i = 0; i < this.trips.length; i++) {
      let count;
      this.tripService.getNumberOfUsers().subscribe(
        data => { count = data},
        err => console.error(err),
        () => this.numberOfUsers.set(this.trips[i].id, count)
      );


      ;
    }
  }

  getNumberOfUsers(data) {
    return this.numberOfUsers.get(data.id);
  }

方法 initNumberOfUsers 在第一次订阅完成后调用,这很好,但 getNumberOfUsers(data) 可能在之前调用并得到“未定义”。

这是模板中的相关部分(“数据”是 ngFor 循环的当前值):

<span [innerText]="getNumberOfUsers(data)"></span>

有没有办法确保两个订阅方法在渲染之前按顺序完成?

更新

我能够创建解析器,并且 concole 显示对象,但是我很难将数据传递给组件。

转接器代码:

import { Injectable } from '@angular/core';
import { Router, Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Trip} from ....
import { TripService } from .....
import { Observable } from 'rxjs/Rx';

@Injectable()
export class TripsResolve implements Resolve<Trip[]> {

   public trips: Trip[];

   constructor(private service: TripService) {}

   resolve(route: ActivatedRouteSnapshot, 
           state: RouterStateSnapshot) {
     this.service.getTrips().subscribe(
        data => { this.trips = data },
        err => console.error(err),
        () => console.log(this.trips)
      );
      return this.trips;
   }
}

从路线

resolve: {
         trips: TripsResolve
      }

来自模块

providers: [
        ...
        TripsResolve,
        ...

组件中数据不通过/不可用:

import { Component, OnInit } from '@angular/core';
import { Trip } from ...
import { Router, ActivatedRoute } from '@angular/router';

@Component({
  selector: ...
  templateUrl: ...
  styleUrls: ...
})
export class MyComponent implements OnInit {

    public trips: Trip[];

    constructor(private route: ActivatedRoute,
                private router: Router) {
    }

    ngOnInit() {
        this.route.data.forEach(data => {
            this.trips = data.trips;
            console.log(this.trips);  //can't see it
       });

    } 
}

知道如何访问/检索组件中的数据吗?

【问题讨论】:

    标签: angular typescript


    【解决方案1】:

    我在通过 API 调用处理数据时遇到了类似的问题

    我尝试了两种解决方案

    一个正在使用 *ngIf

    只有在数据准备好时才会填充 DOM。

    <div *ngIf="isLoaded">Text to show</div>
    

    另一个是使用 resolver,在渲染视图本身之前它会准备数据。但有时,如果数据加载时间过长,解析器会让您的屏幕保持空白。

    @Injectable()
    export class HeroDetailResolve implements Resolve {
        constructor(private heroService: HeroService, private router: Router) { }
    
        resolve(route: ActivatedRouteSnapshot): Promise | boolean {
            let id = +route.params['id'];
            return this.heroService.getHero(id).then(hero => {
                if (hero) {
                    return hero;
                } else { // id not found
                    this.router.navigate(['/dashboard']);
                    return false;
                }
            });
        }
    }
    

    所以你现在有两个选择。

    我建议*ngIf。在加载数据的过程中,显示​​一些加载器 gif 或一些消息,让用户知道后台发生了一些事情。

    【讨论】:

    • isLoaded 到底是什么?
    • 我们将有一个用户定义的标志变量来维护 API 调用的状态
    • 谢谢。我遇到了同样的问题
    • @marknt15 很高兴您的问题已解决
    【解决方案2】:

    查看 Angular 中的解析器。

    这是一个链接: https://angular.io/api/router/Resolve

    很像守卫,它们在组件完全创建之前运行,并防止您在加载时必须在模板中使用 if/else 逻辑来等待。

    如果您不想使用解析器,也可以执行以下操作:

    <span *ngIf="myVariable; else stillLoading" [innerText]="getNumberOfUsers(data)"></span>
    
    <ng-template #stillLoading><p>Loading...</p></ng-template>
    

    myVariable 将是您在渲染 &lt;span&gt; 标记之前要定义的任何内容。我只是不想仔细查看您的地图:)

    在模板中是否使用解析器或 if/else 的选择取决于您希望您的用户体验是什么样的。可能还有其他原因,但首先想到的是那个。

    【讨论】:

    • 通常我同意这个答案,但是由于用例,我会推荐 ngShow 而不是 ngIf。因为 ngIf 不会将标签发布到 DOM,这可能会导致不受欢迎的布局问题。 ngShow 将在 DOM 上为隐藏标签应用并保留预期的“不动产”,但只有在数据准备好后才会填充。也就是说,我通常在很多情况下推荐 ngIf,但听起来这个显示的渲染很重要,所以暂时隐藏 span 可能会更好。
    • 我不得不承认..我没有仔细阅读这个问题...厌倦并使用一些打字稿gimmes解压缩...但是,使用ngIf不会影响如果您使用 ngIf/Else 并根据数据的状态呈现事物,则以负面的方式布局......
    • 当然,这是正确的,但是您可能必须以级联方式管理 DOM 渲染以适应缺失的部分。 ngShow 在这方面可能会更简洁一些,但实际上任何一种方法都可以工作。
    • 我同意 ngShow 在某些情况下更容易处理布局......但处理空值要困难得多。我想是权衡。我认为我个人使用 ngIf 95/100 次......也许我需要重新评估什么时候使用什么。
    • 我同意,ngIf 90% 的时间也是如此。除非布局(有时是维护)和演示是高优先级。无论他走哪条路,本地都应该被初始化。除非它是一个复杂的对象,否则不应在您的模板中考虑 null/undefined。同样在复杂类型的情况下......安全导航操作员FTW! myComplexType?.myPotentiallyNullObject?.myPotentiallyNullObjectsPotentiallyNullProperty。 干杯
    【解决方案3】:

    您可以使用 mergeMap 组合这两个订阅。这样,一旦触发了最后一个 sub 的完成方法,它们就会发出。

    尽管在您的情况下,您可能会发现将 numberOfUsers 简单地初始化为 null 会更容易。然后在您的模板中使用 ng-show 仅在 numberOfUsers 不为空时显示跨度。

    例如:

    <div *ngShow="numberOfUsers">
      <span [innerText]="getNumberOfUsers(data)"></span>
    </div>
    

    干杯

    【讨论】:

    • 我可能是错的,但我认为ngShow 渲染了元素并且只是有效地隐藏了它......仍然抛出空指针......不要认为这会起作用
    • 你是对的,ngShow 仍然将标签应用于 DOM。然而,根据所描述的用例,这将是理想的方法。听起来好像我们在这里没有遇到错误,而只是由于数据尚未准备好而在转入期间得到了不良结果。
    • 说了这么多,无论哪种方式都可以,但是根据尝试完成的任务,我会推荐在这种情况下使用 ngShow。
    • 在我的回答中查看我对您的评论的回复:)
    • 在这两个之间他会有一些选择,希望他能和他们一起玩,找到最有效的。对他来说,另一个选择是将它放在(激活的)路线上,因为您逃避、输入甚至更复杂地使用 NG 钩子,但这两个建议可能是最简单的。
    猜你喜欢
    • 2017-09-01
    • 1970-01-01
    • 2019-12-31
    • 2018-09-07
    • 2016-10-25
    • 2016-04-16
    • 2016-04-30
    • 2019-05-30
    • 2021-04-27
    相关资源
    最近更新 更多