Accessing async variable of service

蹲街弑〆低调 提交于 2019-12-22 09:18:03

问题


This is part of my first Angular 4 project. I am currently able to call the searchCall function just fine from a search bar, but the data being stored in tweetsData doesn't seem to be in scope with my *ngFor call in app.component.html, as well as being an asynchronous backend call to the twitter api. I get this error: TypeError: Cannot read property 'subscribe' of undefined, so I must not have the observable setup correctly. Any help is greatly appreciated.

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>

回答1:


First problem, like pointed out in comments, having your providers array at component level will mean that you have separate instances of services, so it's not shared at all. So remove those!

Also you have race conditions, like also mentioned in comments.

I understand that you want to have subscribers listen to when tweetsData has values. What you need to do, is provide these subscribers with observables What you are doing now:

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

returns an array (assumingly), not an observable of an array. You cannot subscribe to a "regular" array.

So what I would do, is to declare an Observable in the service:

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

then when you get your data, call next():

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

Then you have your subscribers listen to this observable, like:

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

That should do it. Further reading from the docs: https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service




回答2:


what is the type of tweetsData in twitter.service.ts

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

Is this function returning a observable ? If this is not an observable , you can return Observable.of(this.tweetsData)

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


来源:https://stackoverflow.com/questions/47065112/accessing-async-variable-of-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!