0

I am using a Heroku based twitter API and getting data into my web app using Angular Mat table and other associated components.

I want to be able to have the user change the string query and update the results based on input field (with deBounce applied).

Example of API: https://am-twitter-scrape.herokuapp.com/hashtags/Python?pages_limit=3&wait=0

I want the hashtags value to be updated by what user enters into input field and then return updated results. I'm following a tutorial by https://www.freakyjolly.com/angular-7-6-add-debounce-time-using-rxjs-6-x-x-to-optimize-search-input-for-api-results-from-server/ but I am struggling to purpose it to my needs.

My code is not throwing errors, nor is it working now. Can I ask for any help please?

Data service (twitterdata.service.ts)

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Users } from '../models/users.model';

@Injectable()
export class TwitterdataService {

    // setup custom placeholder vars that will be binded to search input field
    // They will modify the declared JSON APIs below
    private myCustomHashtag:string = 'Python';
    private myCustomUser:string = 'Twitter';

    // Load JSON APIs via HttpClient and set them up with obervables (models)
    private hashtagsUrl:string = `https://am-twitter-scrape.herokuapp.com/hashtags/${this.myCustomHashtag}?pages_limit=3&wait=0`;
    private usersUrl:string = `http://am-twitter-scrape.herokuapp.com/users/${this.myCustomUser}?pages_limit=3&wait=0`;

    constructor( private http: HttpClient ) { }

    // Retrieve JSON API (hashtags), using template model
    getTweetsByHashtag(): Observable<Users[]> {
        return this.http.get<Users[]>(this.hashtagsUrl);
    }

    // Retrieve JSON API (Users), using template model
    getTweetsByUsers(): Observable<Users[]> {
        return this.http.get<Users[]>(this.usersUrl);
    }
}

Layout of twitter table data in HTML template (hashtag-tweets-component.html)

<mat-card>

  <!--Search input field to filter table data-->
  <div class="search-container" style="direction: rtl;">
    <mat-form-field>
      <mat-icon matPrefix aria-hidden="false" aria-label="Search">search</mat-icon>
      <input matInput #hashtagsSearchInput placeholder="Search by hashtag" [(ngModel)]="myCustomHashtag">
    </mat-form-field>
  </div>

  <!--display loading spinner whilst data loads-->
  <div class="spinner-container" *ngIf="dataSource.loading$ | async">
    <mat-spinner></mat-spinner>
  </div>

  <!--Table populated by twitter hashtags API feed-->
  <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
    <ng-container matColumnDef="text">
      <th mat-header-cell *matHeaderCellDef> Tweet </th>
      <td mat-cell *matCellDef="let hashtags"> {{hashtags.text | ellipsis: 50}} </td>
    </ng-container>
    <ng-container matColumnDef="likes">
      <th mat-header-cell *matHeaderCellDef> Likes </th>
      <ng-container *matCellDef="let hashtags">
        <td mat-cell *ngIf="(hashtags.likes>0); else noLikes"> {{hashtags.likes}} </td>
      </ng-container>
      <ng-template #noLikes>-</ng-template>
    </ng-container>
    <ng-container matColumnDef="replies">
      <th mat-header-cell *matHeaderCellDef> Replies </th>
      <ng-container *matCellDef="let hashtags">
        <td mat-cell *ngIf="(hashtags.replies>0); else noReplies"> {{hashtags.replies}} </td>
      </ng-container>
      <ng-template #noReplies>-</ng-template>
    </ng-container>
    <ng-container matColumnDef="retweets">
      <th mat-header-cell *matHeaderCellDef> Retweets </th>
      <ng-container *matCellDef="let hashtags">
        <td mat-cell *ngIf="(hashtags.retweets>0); else noRetweets"> {{hashtags.retweets}} </td>
      </ng-container>
      <ng-template #noRetweets>-</ng-template>
    </ng-container>
    <ng-container matColumnDef="hashtags">
      <th mat-header-cell *matHeaderCellDef> Hashtags </th>
      <td mat-cell *matCellDef="let hashtags"> {{hashtags.hashtags | slice:0:2}} </td>
    </ng-container>
    <ng-container matColumnDef="date">
      <th mat-header-cell *matHeaderCellDef> Date </th>
      <td mat-cell *matCellDef="let hashtags"> {{convertDate(hashtags.date)}} </td>
    </ng-container>
    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>
</mat-card>

<!--Table pagination-->
<mat-paginator
  [length]="length"
  [pageSize]="pageSize"
  [pageSizeOptions]="pageSizeOptions"
  [showFirstLastButtons]="yes">
</mat-paginator>

Twitter table data Typescript (hashtag-tweets-component.ts)

import { Component, ViewChild, ElementRef, OnInit } from '@angular/core';
import { TwitterdataService } from '../services/twitterdata.service';
import { Users } from '../models/users.model';
import { Observable, of, fromEvent } from 'rxjs';
import { debounceTime, map, distinctUntilChanged, filter } from 'rxjs/internal/operators';
import { MatTableDataSource, MatPaginator } from '@angular/material';

@Component({
  selector: 'app-hashtag-tweets',
  templateUrl: './hashtag-tweets.component.html',
  styleUrls: ['./hashtag-tweets.component.sass']
})
export class HashtagTweetsComponent implements OnInit {

    dataSource = new MatTableDataSource<Users>();
    displayedColumns = ['text', 'likes', 'replies', 'retweets', 'hashtags', 'date'];

    // Setup pagination attr
    length = 50;
    pageSize = 10;
    pageSizeOptions = [5, 10, 20];
    @ViewChild( MatPaginator ) paginator: MatPaginator;

    // Setup element reference for the search field
    @ViewChild('hashtagsSearchInput') hashtagsSearchInput: ElementRef;
    apiResponse:any;
    isSearching:boolean;

    // Trim and reformat date string (unfortunately not already a date object to start with)
    convertDate(rawDate: string): string {
        const dateOnly = rawDate.split('-')[1].trim();
        const [day, month, year] = dateOnly.split(' ');
        return `${month} ${day}, ${year}`;
    }

    constructor( private twitterdataService: TwitterdataService ) {
    }

    ngOnInit() {
        // Query the api using the data service and pull it into dataSource for Mat table
        this.twitterdataService.getTweetsByHashtag().subscribe(
            data => this.dataSource.data = data
        );

        // Listen to the user input on search field and update results
        fromEvent(this.hashtagsSearchInput.nativeElement, 'keyup').pipe(
            // get value
            map((event: any) => {
                return event.target.value;
            })
            // if character length greater then 2
            ,filter(res => res.length > 2)
            // Time in milliseconds between key events (wait until run search)
            ,debounceTime(1000)
            // If previous query is diffent from current
            ,distinctUntilChanged()
            // subscription for response
        ).subscribe((text: string) => {
            this.isSearching = true;
            this.twitterdataService.getTweetsByHashtag().subscribe(
                data => this.dataSource.data = data
            );
                this.isSearching = false;
                // this.apiResponse = res;
            },(err)=>{
                this.isSearching = false;
                console.log('error',err);
            });
        }

        ngAfterViewInit(): void {
            // Add the MatTable paginator after view init
            this.dataSource.paginator = this.paginator;
        }
    }
3
  • can you please share the User model once? So that I can try in mine? Commented Jul 29, 2019 at 5:42
  • Does fromEvent emits any values? Can you put console.log inside the operators and check? Commented Jul 29, 2019 at 5:46
  • could you please tell did my answer helped? Commented Jul 29, 2019 at 12:40

1 Answer 1

1

The following code worked for me. (In network tab i can see response but in console i am not because i am getting Cross-Origin Request Blocked). I think in your code myCustomHashTag is not updated from ts file means you are not passing arguements to service class from ts class.

component.html

<div class="row">
  <div class="col-12 text-center">
    <h1>Angular 7 Search using Debounce in RXJS 6.X.X</h1>
    <input type="text" #movieSearchInput class="form-control"
      placeholder="Type any movie name" />

  </div>
</div>

component.ts

import { Component, ViewChild, ElementRef, OnInit } from "@angular/core";
import { of } from "rxjs";
 import {
  debounceTime,
  map,
  distinctUntilChanged,
  filter
} from "rxjs/operators";
import { fromEvent } from 'rxjs';
import { HttpClient, HttpParams } from "@angular/common/http";

import { TwitterTestService } from '../Service/twitter-test.service'

@Component({
  selector: 'app-twitter-test',
  templateUrl: './twitter-test.component.html',
  styleUrls: ['./twitter-test.component.css']
})


export class TwitterTestComponent implements OnInit {

  @ViewChild('movieSearchInput') movieSearchInput: ElementRef;
  apiResponse:any;
  isSearching:boolean;

  constructor(
    private httpClient: HttpClient,
    private twitterTestService:TwitterTestService
  ) {
    this.isSearching = false;
    this.apiResponse = [];
  }

   ngOnInit() {
  fromEvent(this.movieSearchInput.nativeElement, 'keyup').pipe(
    // get value
    map((event: any) => {
      return event.target.value;
    })
    // if character length greater then 2
    ,filter(res => res.length > 2)
    // Time in milliseconds between key events
    ,debounceTime(1000)        
    // If previous query is diffent from current   
    ,distinctUntilChanged()
    // subscription for response
    ).subscribe((text: string) => {
      this.isSearching = true;
      this.twitterTestService.getData(text).subscribe((res)=>{
        console.log("in ts file data is ");
        console.log(res);
      },err=>{
        console.log("error is coming ");
        console.log(err);
      })

    });

service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class TwitterTestService {

  private hasTag:string;
  private baseUrl = 'https://am-twitter-scrape.herokuapp.com/hashtags/';
  constructor(private http: HttpClient) { }


  getData(data) {
     //this.hasTag = data;
     console.log("inside service data url is  ");  
     console.log(this.hasTag);
     return this.http.get(this.baseUrl+data+'?pages_limit=3&wait=0');

 }
}

And one more if you want to use templateStringLiterals then we need to write that in one function. Because templateStringLiterals doesn't update values if we don't write inside functions. If you want to make api using templateStringLiterals then we need to write another function which return url like below.

service.ts

private hasTag:string='';
geturl() {
  return `https://am-twitter-scrape.herokuapp.com/hashtags/${this.hasTag}?pages_limit=3&wait=0`;
}

constructor(private http: HttpClient) { }

getData(data) {
  this.hasTag = data;
  console.log("inside service data url is  ");  
  console.log(this.hasTag);
  console.log("fun is  ");
  let temp = this.geturl();
  console.log("temp is ");
  console.log(temp);
  return this.http.get(temp);

 }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.