skip to Main Content

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>

2

Answers


  1. 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) ;
        }
    
    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search