简体   繁体   中英

Angular 5 Observable, return when complete

Help with Angular 5 Observables.

I want to hide the complexity from the controller; that is to not make it have to subscribe to the observable. Have all the complexity in the service and have it simply return the results/payload to the component. But obviously I'm getting stuck in a timing issue. I feel as if this should be a popular thing/topic, but haven't found the answer. So perhaps I'm going about things wrong?

//in the component.ts
const allColors = this.auth.getColors(); //service call
console.log(allColors); // returns undefined

//in the service.ts
getColors() {
  var myColors = "";

  var colors = this.http.get<any>('http://localhost/Account/GetColors', httpOptions)
    .pipe(catchError(this.handleError));

  //
  colors.subscribe(
    res => {
      myColors = res.body;
    },
    error => {}
  );

  return myColors;
}

I think you can't do what you wanna do, because until the subscribe in your service doesn't get resolved it won't send anything to the component, in the execution of the component it's undefined cuz the response haven't come yet.

That's why callback functions are invented. The correct way to do it is having the subscribe in your component cuz you want to wait for the response of the service.

Sorry but I cannot use comments yet so I had to make this reply. Hope it helps

So perhaps I'm going about things wrong?

Indeed. Embrace asynchronous programming with Observables and Promises. I believe that is the only reason you are asking this question, you do not fully understand how they work so you want to abstract it away which is something you can't do. See also How do I return the response from an asynchronous call?


That said you could use async/await which can give the appearance of synchronous code.

component.ts

async ngOnInit() {
  const allColors = await this.auth.getColorsAsync(); //service call
  console.log(allColors); // returns color array
}

service.ts

getColorsAsync(): Promise<color[]> {
  return this.http.get<color[]>('http://localhost/Account/GetColors', httpOptions)
    .pipe(catchError(this.handleError))
    .toPromise();
}

I guessed as to the actual type. Ideally you should use a strong type over any when possible

When will your method return? That's the question you need to answer, and to spare you the suspense it will return prior to myColors being filled with anything, and that's the nature of asynchronous programming.

You need to return the observable and let it notify you when the results are available. This complexity cannot be hidden in the way you are trying, the closest you can get is to pass a callback function into your service method that will then be called with the results when they are available, something like this:

getColors(callback) {
    //...

    colors.subscribe(
       res => {
           callback(res.body);
       }
       //...
    );
}

But then you'll miss out on a lot of cool functionality, such as being able to have multiple subscribers to the same observable.

You want to use a Resolver and routing to pre-load the data before the service is used.

A resolver is an asynchronized action that is finished before the rest of the routes are activated. So you would define a top-level route that doesn't do anything, but contains the resolver to fetch the color data. I say top-level because I assume this is global application data. If this data is specific to a feature then place the resolver in the correct routing path.

const routes: Routes = [
    { 
       path: '',
       resolve: {
           Colors: ColorsResolver
       },
       children: [
             // your application routes here
       ]
    }
] 

The class ColorsResolver would fetch the color data from the server, and then assign it to the service.

@Injectable()
export class ColorsResolver implements Resolve<any> {
     public constructor(private colorService: ColorService) {}

     public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
          return this.http.get<any>('http://localhost/Account/GetColors', httpOptions)
                  .do((resp)=> this.colorService.setColor(resp));;
     }
}

In your service you just assign the value to a property

 @Injectable()
 public ColorService {
     public setColor(resp: any) { this._color = resp; }
     public getColor():any { return this._color; }
 }

You can now call getColor() many times after the resolver has finished without it blocking.

Going off of what Osakr said. You will have to subscribe in your component.

In your service.ts

getColors(): Observable<any> {

    let urlString = 'http://localhost/Account/GetColors'

    return this.http.get(urlString);
  }

In your component:

getColors() {

    this.service.getColors()
      .subscribe((colors) => {

      this.myColors = colors;

  })
}

I think you can do it in a different way

//in the component.ts
this.service.getColors(); //service call
console.log(this.service.allColors); // print the service property.  which will resolve after the oberservable resolve

//in the service.ts
@injectable()
export class Service {
  public allColors: any; //Public property to print.
  public getColors() {
    var myColors = "";

    var colors = this.http.get<any>('http://localhost/Account/GetColors', httpOptions)
    .pipe(catchError(this.handleError));

    colors.subscribe(
      res => {
         this.allColors= res.body;
      },
      error => {}
     );

    return;
  }
}

Here's how I would do it:

this.http.get<any>('http://localhost/Account/GetColors', httpOptions)
  .pipe(catchError(this.handleError))
  .subscribe(res => {
    console.log(res.body);
  });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM