【问题标题】:Accessing async variable of service访问服务的异步变量
【发布时间】:2018-04-14 09:01:19
【问题描述】:

这是我的第一个 Angular 4 项目的一部分。我目前可以从搜索栏中很好地调用 searchCall 函数,但是存储在 tweetsData 中的数据似乎不在我在 app.component.html 中调用的 *ngFor 范围内,并且作为异步后端调用 twitter api。我收到此错误:TypeError: Cannot read property 'subscribe' of undefined,所以我不能正确设置 observable。非常感谢任何帮助。

twitter.service.ts

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class TwitterService {
  searchQuery: string = '';
  tweetsData;

  constructor(private http: Http) {
    console.log('TwitterService created');
    this.getBearerToken();
  }

  getBearerToken() {
    // get bearer token from twitter api
    var headers = new Headers();

    headers.append('Content-Type', 'application/x-www-form-urlencoded');

    this.http.post('http://localhost:3000/authorize', {headers: headers}).subscribe((res) => {
      console.log(res);
    });
  }

  getTweetsData():Observable<any> {
    return this.tweetsData;
  }

  searchCall() {
    console.log('searchCall made');
    console.log(this.searchQuery);
    var headers = new Headers();
    var searchTerm = 'query=' + this.searchQuery;

    headers.append('Content-Type', 'application/x-www-form-urlencoded');

    this.http.post('http://localhost:3000/search', searchTerm, {headers: headers}).subscribe((res) => {
      this.tweetsData = res.json().data.statuses;
    });
  }

}

app.component.ts

import { Component, OnInit, TemplateRef } from '@angular/core';
import { Http, Headers } from '@angular/http';

import { TwitterService } from './twitter.service';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ 'custom-styles/app.component.css' ],
  providers: [TwitterService]
})
export class AppComponent implements OnInit {
  searchQuery: string = '';
  tweetsData;
  expandedNewTweetBox: boolean = false;

  public modalRef: BsModalRef;

  constructor(private http: Http, private twitterService: TwitterService, private modalService: BsModalService) {}

  ngOnInit() {
    this.twitterService.getTweetsData().subscribe(
      data => this.tweetsData = data
    )
  }

  public openModal(template: TemplateRef<any>) {
    this.modalRef = this.modalService.show(template);
  }
}

app.component.html

...
<div *ngIf="tweetsData">
  <div *ngFor="let item of tweetsData" class="col-12">
...

navbar.component.html

<div class="input-group">
   <input type="text" class="form-control" placeholder="Search Chiller" aria-label="Search Chiller" [(ngModel)]="twitterService.searchQuery" [ngModelOptions]="{standalone: true}">
   <span class="input-group-btn">
      <button class="btn btn-secondary" type="button" (click)="twitterService.searchCall()">Go!</button>
   </span>
</div>

【问题讨论】:

  • 你永远不会打电话给searchCall
  • @Aluan Haddad 我愿意。该调用位于 app.component.html 的另一部分,我省略了该部分,因为我测试了对搜索函数的调用并且它按预期工作。
  • 你不应该假设这样的缓存。你可能有比赛条件或其他什么。
  • 抱歉,对 searchCall 的调用是在另一个组件“navbar.component”中进行的。我已经从该文件中添加了相关代码。希望这会有所帮助。
  • 啊,我明白了问题,您在组件级别有providers: [TwitterService],这意味着组件获得了一个具有自己状态的单独服务实例。这通常没问题,除了您的服务是有状态的并且期望是单例的。

标签: angular observable angular2-services


【解决方案1】:

twitter.service.ts 中的 tweetsData 是什么类型

   getTweetsData():Observable<any> 
    {
    return this.tweetsData;
    } 

这个函数是否返回一个 observable ? 如果这不是 observable ,则可以返回 Observable.of(this.tweetsData)

 getTweetsData():Observable<any> {
    return Observable.of(this.tweetsData) ;
    }

【讨论】:

  • 遗憾的是,Observable 的属性 of 不存在。我对getTweetsData 的意图是观察变量tweetsData 并在对该变量进行更改时通知订阅的组件。我可能是错的,我应该知道这一点,但我相信 tweetsData 将在调用 searchCall 后不久保存一组 JSON 对象(不是立即,因此是异步的)。
  • 在最新版本的 RxJS 中,我们可以这样导入它: import 'rxjs/add/observable/of'; ,并使用 Observable.of({})
  • tweetsData 将保存一个 JSON 对象数组,但它不是 Observable。要观察/观察 tweetsData 的变化,您必须从函数 getTweetsData() 返回一个 Oservable。您可以使用 Observable .of 或创建 Behavior Subject 并使用该对象创建一个 observable,如 AJT_82 的评论中所述。
【解决方案2】:

第一个问题,就像在 cmets 中指出的那样,将您的 providers 数组放在组件级别意味着您有单独的服务实例,因此它根本不共享。所以删除那些!

你也有竞争条件,就像 cmets 中提到的那样。

我了解您希望订阅者在tweetsData 具有值时收听。您需要做的是为这些订阅者提供 observables 您现在正在做什么:

getTweetsData():Observable<any> {
  return this.tweetsData;
}

返回一个数组(假设),而不是一个数组的可观察对象。您不能订阅“常规”数组。

所以我要做的就是在服务中声明一个 Observable:

import { BehaviorSubject } from 'rxjs/BehaviorSubject';

// don't use 'any', type your data instead
// you can also use a 'Subject' if the subscribers are always listening
private tweetsData = new BehaviorSubject<any>(null);
public tweetsData$ = this.tweetsData.asObservable();

然后,当您获取数据时,请致电next()

searchCall() {
  // ....
  this.http.post(...)
    .subscribe((res) => {
      this.tweetsData.next(res.json().data.statuses)
    });
}

然后你让你的订阅者收听这个 observable,比如:

constructor(private twitterService: TwitterService) {
  twitterService.tweetsData$
    .subscribe(data => {
       this.tweetsData = data;
    });
}

应该这样做。进一步阅读文档:https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service

【讨论】:

  • 解决了问题!你是最棒的,非常感谢。我必须更改的一件事是 tweetsData$ 必须是公开的,以便我可以在 app.component.ts 中引用它
  • 哦,对不起,你完全正确,它必须是public!很高兴听到该解决方案有效,周末愉快,编码愉快! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多