3

I am writing an Angular 4 app. This contains 2 lists. If I click an element in the first list the result should be a subset of my second list (via foreign keys) in a new view. I do the filtering by id (foreign key) with a function in my service. In my component I receive just 'undefined'. I think, the reason is that I use an Observable in my service and the data is not ready, when the new view for the subset list is shown. How can I do this in another way to reach my goal?

calling the method in my service via click event in a mat-table-cell:

method in my service.ts

getItemsByID(id: number): Observable<Item[]> {
    return this.http.get('/api/Item').map((response) => {
      console.log(id); //shows correct item id
      console.log(this.items.filter(iteration => item.item_id === id)); // shows correct array of subset list
      return this.items.filter(item=> item.item_id === id);
    });
  }

method in my component.ts

getItem(): void { 
    const id = +this.route.snapshot.paramMap.get('id');
    this.itemService.getItemsByID(id)
        .subscribe(response => this.items = response);    
    console.log(this.items); // shows 'undefined'
}

What else do I need to show you from my code?

Complete log:

undefined

4

Array [ {…}, {…}, {…}, {…} ]

Thanks a lot!

Community
  • 1
  • 1
dafna
  • 893
  • 2
  • 10
  • 21
  • Put the console.log() **inside** the callback passed to subscribe(). http is asynchronous. That's why it returns an Observable. It if blocked until the response is available, it wouldn't bother you with observables. It would return the response directly. You can't just eat a toast immediately after you've put it in the toaster. eat it when the toaster notifies you that the toast is ready. – JB Nizet Nov 27 '17 at 21:01
  • Please add details that help troubleshooting the problem. There are two Http services provided by Angular. Which module are you using? – André Werlang Nov 28 '17 at 01:39

3 Answers3

2

I infer you're using the HttpClient as http, given response as returned a list and .json() is not a function, as you wrote.

So, it's better if you remove the this.items property on your service. Perhaps you think that this property exist as this.items in both service and component? this points to separate objects. And would be best if service didn't had to maintain a state like this one, but only methods.

getItemsByID(id: number): Observable<Item[]> {
  return this.http.get<Item[]>('/api/Item').map((items) => {
    console.log(id); //shows correct item id
    const filtered = items.filter(item => item.item_id === id);
    console.log(filtered); // shows correct array of subset list
    return filtered;
  });
}

Then consume the service:

getItem(): void {
  const id = +this.route.snapshot.paramMap.get('id');
  this.itemService.getItemsByID(id)
    .subscribe(items => {
        this.items = items;
        console.log(items);
      );
    });
}
André Werlang
  • 5,839
  • 1
  • 35
  • 49
  • Great! This was helping me! Thanks a lot! Now I try to understand and I think I have much to learn. Thanks! – dafna Nov 28 '17 at 07:42
1

I don't know why you're saving this response inside a service array variable, but you only need to return the mapped data from the service.

getItemsByID(id: number): Observable<Item[]> {
    return this.http.get('/api/Item')
      .map((response: Response) => {
      // return json object from http response
      let mapped = response.json();
      // given that mapped is an array of itens
      return mapped.filter(item => item.item_id === id);
    });
}

Also, your console is outside the subscribe method. It's async, you have to wait until the response ends.

getItem(): void { 
    const id = +this.route.snapshot.paramMap.get('id');
    this.itemService.getItemsByID(id)
        .subscribe((response) => { 
           this.items = response;
           console.log(this.items);
        });
}
Fals
  • 6,813
  • 4
  • 23
  • 43
  • thanks for your answer. just first information to the first method: the property items does not exist for response. – dafna Nov 27 '17 at 21:06
  • @dafna cool! if you fallow the same path you can achieve your desired result. It was an exemple where the response from the server has It's itens inside the response. – Fals Nov 27 '17 at 21:08
  • but I get this error. if I change in method 1, line 4 'response' to 'this', I also will get the udefined problem. do you know why? – dafna Nov 27 '17 at 21:11
  • @dafna because this is scoped to your service, if this method resides inside one. You dont need this inside the function scope, just call the variable. – Fals Nov 27 '17 at 21:13
  • okay, I understand. But still visual studio shows an error in your code by underlining items in the service method 'return response.items.filter(item => item.item_id === id);', error: Property 'items' does not exist on type 'Object':' – dafna Nov 27 '17 at 21:16
  • sorry, but a new error appears: 'response.json() is not a function'. do I have to import anything? – dafna Nov 27 '17 at 21:22
  • I have seen you updated again. But in that case the error remains AND Visual Studio underlines 'mapped' - same error type. Any further idea? I thought that would be no big thing :-( – dafna Nov 27 '17 at 21:41
0

Observables, unless they have an initial value (BehaviorSubject, .startsWith()) or are synchronous, won't be ready just after a call to subscribe. The Http service is asynchronous, so you'll need to adapt your consumer. In time, I find it better to structure as if all observables are async.

Given your getItem() is void, one option is moving processing to the subscriber:

getItem(): void {
  const id = +this.route.snapshot.paramMap.get('id');
  this.itemService.getItemsByID(id)
    .subscribe(response => {
      this.items = response;
      // process it here, for instance calling ChangeDetectorRef#markForChanges() 
      // to make Angular update the component view
      // see https://stackoverflow.com/a/47463896/592792
    });
}

Another, pretty cool feature of Angular is using the async pipe:

getItem(): void {
  const id = +this.route.snapshot.paramMap.get('id');
  return this.itemService.getItemsByID(id);
}
Access properties:
{{ (getItem() | async).someProperty }} 

Pass to inner components: 
<cmp [item]="item | async"></cmp>

In this case we pass an Observable to view. async unwraps and passes values to wherever we need them. It's also possible to store the observable to a component property so it will be created and subscribed to just once.

André Werlang
  • 5,839
  • 1
  • 35
  • 49
  • I tried the first way, bit it's still undefined. The second way I don't understand. Sorry. But thanks for your answer! – dafna Nov 27 '17 at 21:40
  • @dafna `The second way I don't understand.` That's a very important concept. Don't need to use it now, but I suggest you do get an understanding of this (async pipe, OnPush change detection, and observables composition) – André Werlang Nov 28 '17 at 01:55