Angular2 / Typescript知道何时获取了http或者已经更新了observable

时间:2017-02-06 00:20:04

标签: javascript angular typescript angular2-observables

我有一个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);
    }
  );
}
}

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

提前谢谢。

3 个答案:

答案 0 :(得分:1)

因为你调用fetchStaffProfile是异步进程,然后你立即调用getProfile,返回值为空,只需更改为:fetch将返回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);
     }
  }
}

答案 1 :(得分:1)

只需订阅这样的结果:

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

答案 2 :(得分:1)

涉及同步和传播的经典场景异步世界。 ( 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方法运行并返回Subscription到http调用,此时获取方法已完成。之后,ngOnInit继续this.profile = this.userService.getProfile();

同时,在异步世界中:

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

但是,在它发生之前,ngOnInit尝试访问未定义的第一个项元素的属性user_id

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

我的建议是:

天真的解决方案 - fetch方法将返回一个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 =&gt;就{js命名约定而言this.promise更好。
  2. var是oldschool。请改用 const see angular styleguide
  3. This article可能会对使用observables有一些启示,详细的例子和说明。