Angular2 / Typescript 知道何时获取了 http 或更新了一个可观察对象

Angular2 / Typescript knowing when http has been fetched or an observable has updated

我有一个 angular2 和 typescript 应用程序,我在其中使用 angular2 的 http 方法从服务中的数据库加载数据。我在其 onInit() 期间触发组件内的服务。这工作正常,我能够加载数据。问题是我还想使用从 onInit() 函数内的服务加载的数据。当我尝试执行此操作时,出现类似于以下错误的错误:

Error: Uncaught (in promise): TypeError: Cannot read property 'user_id' of undefined
TypeError: Cannot read property 'user_id' of undefined

这里是调用服务的组件的缩减代码

export class ProfileComponent implements OnInit {

public profile: StaffProfile[];

constructor(private userService: UserService) {}

ngOnInit() {
    this.userService.fetchProfile();
    this.profile = this.userService.getProfile();
//I just want to be able to do anything once the data is loaded
    console.log(this.profile[0].user_id);
}
}

这里是服务的缩减代码

@Injectable()
export class WorkforceUserService implements OnInit {

private Profile: Profile[];

constructor(private http: Http) {
    this.Profile = [];
}

public getProfile(){
    return this.Profile;
}

public fetchStaffProfile(){
return this.http.get('http://localhost:3000/api/staff/1')
  .map((response: Response) => response.json())
  .subscribe(
    (data) => {
      var user_id = data.user_id || null;

      var loadedProfile = new Profile(user_id);

      this.Profile.push(loadedProfile);
    }
  );
}
}

我想要的只是能够在来自服务器的数据到达或刚刚更新时在我的组件中触发一个函数。请让我知道您对如何实现这一目标的想法。

提前致谢。

因为你调用fetchStaffProfile是异步进程,所以直接调用getProfile,值return为空,改成:fetch will return observable/promise,然后调用的时候, 你订阅了。

@Injectable()
export class WorkforceUserService {


constructor(private http: Http) {
}

public fetchStaffProfile(){
return this.http.get('http://localhost:3000/api/staff/1')
  .map((response: Response) => response.json());
}
}

例如在组件中

export class ProfileComponent implements OnInit {

public profile: StaffProfile;

constructor(private userService: UserService) {}

ngOnInit() {
    this.userService.fetchStaffProfile()
      .subsribe(res => { 
        // do some transform data
       this.profile = res; 
       console.log(this.profile);
     }
  }
}

订阅结果如下:

ngOnInit() {
    this.userService.fetchProfile().subscribe(() => {
      this.profile = this.userService.getProfile();
      console.log(this.profile[0].user_id);
   });
}

涉及同步和异步世界的经典场景。 (TL;DR - 我建议的解决方案如下

所以,这是您在 ngOnInit() 运行时期望的流程:

1. (Component) Ask the service to fetch the profile  
2. (Service) Fetch the profile  
3. (Service) Extract the user_id from the profile received and create new profile  
4. (Service) Push the profile into this.Profile
5. (Component) Set this.profile as service's Profile
6. (Component) Print profile's first entry that was fetched and configured in the service.

您实际得到的流量是:

1 => 2 => 5 => 6 (fails, but hypothetically) => 4 => 5.

在同步世界中:

  • Fetch 方法运行并且 return 发送 Subscription 到 http 调用,此时,fetch 方法完成 。紧接着,ngOnInit 继续 this.profile = this.userService.getProfile();

同时,在异步世界中:

  • http 请求已执行,将来某个时候 将填充 this.Profile

但是,在此之前,ngOnInit 会尝试访问未定义的第一项元素的 属性 user_id

所以,在这种情况下你需要的是留在异步世界中,在这个领域 rxjs 提供了非常酷的 well documented 工具集来处理这种情况。


我的建议是:

朴素的解决方案 - 而不是return订阅,获取方法将return一个Promise,它将在ngOnInit中解决。

// WorkforceUserService

public fetchStaffProfile() {
    return this.http.get('http://localhost:3000/api/staff/1')
        .map((response: Response) => response.json())
        .toPromise()
        .then((data) => {
            var user_id = data.user_id || null;
            var loadedProfile = new Profile(user_id);
            this.Profile.push(loadedProfile);
        });
       // trying to explain my point, don't forget to catch promise errors
}

// ProfileComponent

ngOnInit() {
    this.userService.fetchProfile().then(() => { 
        // this lines are called when http call was done, as the promise was resolved
        this.profile = this.userService.getProfile();
        console.log(this.profile[0].user_id);
    });
            
}

Rxjs 样式解决方案 - 保存一个 Subject 类型的配置文件数组,组件将订阅该数组:

// WorkforceUserService

this.Profile = new Subject<Profile[]>(); // the subject, keep it private and do not subscribe directly
this.Profile$ = this.Profile.asObservable(); // expose an observable in order to enable subscribers.

public fetchStaffProfile(){
    return this.http.get('http://localhost:3000/api/staff/1')
        .map((response: Response) => response.json())
        .subscribe(
            (data) => {
                var user_id = data.user_id || null;
                var loadedProfile = new Profile(user_id);
                this.Profile.next([loadedProfile]);
    });
}

// ProfileComponent

export class ProfileComponent implements OnInit {

    public profile: StaffProfile[];

    constructor(private userService: UserService) {
        // here you can subscribe to the Profile subject, and on each call to 'next' method on the subject, the provided code will be triggered
        this.profile = this.userService.getProfile();
        console.log(this.profile[0].user_id);  
    }

    ngOnInit() {
        // here, we'll ask the service to start process of fetching the data.
        this.userService.fetchProfile();
    }
}

不管您的问题如何,以下几点可能对您有所帮助:

  1. this.Promise => this.promise 在 js 命名约定方面更好。
  2. var 是老派。使用 letconst 代替。 see angular styleguide

This article 可能会通过详细的示例和解释说明如何使用可观察对象。