0
votes

My services

search (packageName: string, refresh = false): Observable<PackageInfo>
   {
    const options = createHttpOptions(packageName, refresh);
    this.searchResults = this.http.get(searchUrl + packageName, options) as Observable<PackageInfo>;
    return this.searchResults
  }

I can do

this.searchResults.subscribe(repoUrl => console.log(repoUrl.repos_url))

This will display the url that is in my Observable. I need to save the repos_url, so I can make a second http.get call. I am not sure how to do this. My thoughts where to save the subscribe, but that returns undefined in the console.log.Which means nothing is returning from what I read it is because value only exist as long as the Observable is active. I am stuck right now. I got my Profile search component working and my Repo list component, but I need the Profile component to pass the repo url to the Repo List component.

profile-search.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { HttpErrorHandler, HandleError } from '../error-handler.service';

export interface PackageInfo {
  login: string
  name: string
  avatar_url: string
  repos_url: string

}

export const searchUrl = 'https://api.github.com/users/';

const httpOptions = {
  headers: new HttpHeaders({
    'x-refresh':  'true'
  })
};

function createHttpOptions(packageName: string, refresh = false) {
    const headerMap = refresh ? {'x-refresh': 'true'} : {};
    const headers = new HttpHeaders(headerMap) ;
    return { headers };
}

@Injectable({
  providedIn: 'root'
})

export class PackageSearchService {
  searchResults: Observable<PackageInfo>;
  private handleError: HandleError;

  constructor(
    private http: HttpClient,
    httpErrorHandler: HttpErrorHandler
   ) {
    this.handleError = httpErrorHandler.createHandleError('Service');
  }

  search (packageName: string, refresh = false): Observable<PackageInfo>
   {
    const options = createHttpOptions(packageName, refresh);
    this.searchResults = this.http.get(searchUrl + packageName, options) as Observable<PackageInfo>;
    return this.searchResults
  }

}

My first thought was to create a blank variable with string type.

export class PackageSearchService {
      searchResults: Observable<PackageInfo>;
      private handleError: HandleError;
      repoUrl: string;  <== new

      constructor(
        private http: HttpClient,
        httpErrorHandler: HttpErrorHandler
       ) {
        this.handleError = httpErrorHandler.createHandleError('Service');
      }

      search (packageName: string, refresh = false): Observable<PackageInfo>
       {
        const options = createHttpOptions(packageName, refresh);
        this.searchResults = this.http.get(searchUrl + packageName, options) as Observable<PackageInfo>;
        this.repoUrl = this.searchResults.subscribe(userRepo => userRepo.repo_url)
        return this.searchResults
      }

repo-list.component.ts

import { Component, OnInit } from '@angular/core';
import { RepoListComponent } from './repo-list.services';
import { PackageSearchService } from '../profile-search/profile-search.service';
import { RepoList } from '../repoList';
import { Observable, Subscription } from 'rxjs';



@Component({
  selector: 'repo-list',
  templateUrl: './repo-list.html',
  styleUrls: ['../profile-search/profile-search.component.css']
})

export class UsersRepoComponent implements OnInit {
  repo: RepoList[] = [];


  constructor( private repoService : RepoListComponent,
    private searchService: PackageSearchService){}



  ngOnInit(){
    this.getRepoList(); 
}


  getRepoList(): void {
    this.repoService.getRepoReturn()
    .subscribe(repo => this.repo = repo);
  }

  getRepoUrl(){
    this.searchService.repoUrl;
  }

}
5
Post what you tried, and we'll explain why it doesn't work, and how to make it work. It's most probably related to asynchrony, and probably has nothing to do with "because value only exist as long as the Observable is active".JB Nizet
ok so I first tried to set the scriber to a blank stringDustin
repoUrl: string this.repoUrl = this.searchResults.subscribe(repoUrl => console.log(repoUrl.repos_url))Dustin
Edit your question, and post all the relevant code in the question. And tell precisely what you want to achieve.JB Nizet

5 Answers

0
votes

You can define a constructor in you services class having url which can be used further. Below is the snippet

repoUrl:string

constructor(private httpClient:HttpClient) {
  this.repoUrl ="http://localhost:3990/"
}

getProjects(packageName:string):Observable<Project[]>{
   return this.httpClient.get(this.repoUrl+packageName)
}
0
votes

Your code doesn't make much sense:

  1. the arguments you pass to createHttpOptions() are not used anywhere
  2. You assign the result of .subscribe(...) to a variable of type string, but subscribe() returns a Subscription, not a string. Observables are used because http calls are asynchronous you can't expect the result to be available immediately after you've called httpClient.get(). It will only be availeble later, when the response is available, and the observable will then notify its subscribers with the result.
  3. You try to use the searchService.repoUrl field without ever calling the method that initializes it.

To chain asynchronous calls returning Observables, the easiest way is to use the switchMap() operator:

getRepoUrl().pipe(
  switchMap(url => getSomethingUsingTheUrl(url))
).subscribe(something => doWhateverYouWant(something));

The disadvantage of this is that everythime you want to load something, you will first make a request to get the repo url, then a second request to get something.

You can only get the URL once and cache it using a single observable using the shareReplay operator:

class UrlService {
  url$: Observable<string>;

  constructor(httpClient: HttpClient) {
    this.url$ = httpClient.get(...).pipe(shareReplay());
  }
}

and in your component:

this.urlService.url$.pipe(
  switchMap(url => getSomethingUsingTheUrl(url))
).subscribe(something => doWhateverYouWant(something));

Observables are hard to grasp, but you definitely need to take time to learn them if you want to use Angular correctly and efficiently. The first step is to understand asynchrony, which is the main readon why Observables exist in the first place.

0
votes

So JB, I am trying to follow your example close. When I did

url$: Observable;

I got an error, that this.url$ is object and not assignable to string, so I did

url$: Observable<object>;

I also had to combine my url and search term into one string.

export class PackageSearchService {
  searchResults: Observable<PackageInfo>;
  private handleError: HandleError;

  url$: Observable<Object>;
  fulluserUrl: string;

  constructor(
    private http: HttpClient,
    httpErrorHandler: HttpErrorHandler
   ) {
    this.handleError = httpErrorHandler.createHandleError('Service');
    this.url$ = http.get(this.fulluserUrl).pipe(shareReplay());
  }

  search (packageName: string, refresh = false): Observable<PackageInfo>
   {
    const options = createHttpOptions(packageName, refresh);
    this.searchResults = this.http.get(searchUrl + packageName, options) as Observable<PackageInfo>;
    return this.searchResults
  }

  userRepo (packageName: string)
  {
    this.fulluserUrl = searchUrl + packageName;
    return this.fulluserUrl;
  }

}

As for component example

this.urlService.url$.pipe(
  switchMap(url => getSomethingUsingTheUrl(url))
).subscribe(something => doWhateverYouWant(something));

I have a similar switchMap in my profile-search.component

switchMap(packageName =>
        this.searchService.search(packageName, this.withRefresh)
      )
      ).subscribe(
        user => this.user = user
      );

just not sure what getSomethingUsingTheUrl or doWhateverYouWant should be or how I should edit

0
votes

If you are trying to chain 2 Observables calls one after the other, using the result of the first observable in the second observable call, FlatMap is what you seek: Chaining RxJS Observables from http data in Angular2 with TypeScript

If you are trying to save the url string for other non-immediate calls, a ReplaySubject inside a service is what I've used in the past.

Quick Note: you can return this.http.get(...) directly, no need to parameterize as 'searchResults'.

0
votes

Ok, thanks to Keenan and JB. I was able to resolve this by using tap and using BehaviorSubject.

MyService

export class ProfileSearchService {
  profile: BehaviorSubject<ProfileInfo> = new BehaviorSubject({ login: '', name: '', avatar_url: '', repos_url: '' });
  repos: BehaviorSubject<any[]> = new BehaviorSubject([]);

  constructor(private http: HttpClient) {
    this.profile.subscribe(({ repos_url }) => {
      if (repos_url) {
        // http request, set repoFetch to return value
        this.http.get(repos_url).pipe(
          tap(repos => this.repos.next(repos as any[]))
        ).subscribe();;
      }
    });
  }

  search (packageName: string) {
    this.http.get(searchUrl + packageName).pipe(
      tap(user => {
        this.profile.next(user as ProfileInfo)
      })
    ).subscribe();
  }
}

MyComponent

export class UsersRepoComponent implements OnInit {
  repo$: Observable<any>;

  constructor(private searchService: ProfileSearchService){}

  ngOnInit() {
    this.repo$ = this.searchService.repos;
  }

}

Also, thanks to Corey Pyle for holding my hand and walking me through this.