4

Basically what i try to do is to hit my API once and save the result inside global variable in my Service, and then share and modify this value in my parent and child component with two helpers functions.

repairs.service.ts

public myItems:any[];

   public GetRepairs = ():Observable<any> => {

     this.headers = new Headers();
     this.headers.set('Authorization', 'Bearer' + ' ' + JSON.parse(window.localStorage.getItem('token')));

      return this._http.get(this.actionUrl +'repairs'{headers:this.headers})
              .map((res) => {return res.json();
                }).map((item) => {
                    let result:Array<any> = [];
                    if (item.items) {
                        item.items.forEach((item) => {
                            result.push(item);
                        });
                    }

                    this.myItems = result;
                    return this.myItems;
             });
    };

    public GetItems() {
       return this.myItems;
    };

    public UpdateItems(data:any[]) {
       this.myItems = data;
    };

And then in my main component i do

repairs.component.ts

export class RepairsComponent implements OnInit {
    public myItems:any[];

    constructor(private _userService:UserService,
                private _RepairsService:RepairsService,
                public _GlobalService:GlobalService) {
    }

    ngOnInit() {
        this._userService.userAuthenticate();
        this.getAllItems();
    }

    private getAllItems():void {
        this._RepairsService
            .GetRepairs()
            .subscribe((data) => {
                    this._RepairsService.UpdateItems(data);
                },
                error => console.log(error),
                () => {
                    this.myItems = this._RepairsService.GetItems();
                });
          }
   }

This work just fine but when i try to invoke GetItems() in child component i get undefinded. I try to do it inside constructor and ngOnInit with the same result.

child.component.ts

export class ChildComponent {
    private items:any[] = [];


    constructor(private _RepairsService:RepairsService, 
                private _Configuration:Configuration) {
        this.items = this._RepairsService.GetItems();
        // undefinded
    }


    ngOnInit() {
        this.items = this._RepairsService.GetItems();
        // undefinded
    }
}
MichalObi
  • 133
  • 1
  • 1
  • 11
  • When do you load the child component? Are you sure it's only after the getAllItems call in the repairs component has completed successfully? – MSwehli Apr 11 '16 at 17:43
  • I load child component as usual inside directives statement for my repairs.component. Is it even possible to load child component AFTER getAllItems has completed successfully? – MichalObi Apr 11 '16 at 18:54

3 Answers3

2

From what i can see in the limited amount of code you shared, it would seem you are trying to get the items before the http get call finishes and saves the data. I think a better design pattern would be to make the GetItems() function also an observable or promise, and check if the data is there, if not call the http get call, and once that completes send the data back to the different components that need it.

MSwehli
  • 483
  • 4
  • 12
  • Can you provide some code sample to show me, how to make Observable inside GetItems() function ? – MichalObi Apr 11 '16 at 18:42
  • Will edit the answer above in a bit to show an example hopefully, in the mean time however i'd highly recommend this video (which is recommended within the angular2 docs themselves) https://www.youtube.com/watch?v=UHI0AzD_WfY – MSwehli Apr 11 '16 at 18:55
2

As @MSwehli mentioned with async code execution you can't rely on the order of code lines. In this code:

ngOnInit() {
    this.items = this._RepairsService.GetItems();
    // undefinded
}

the async code in GetItems(); is scheduled for later execution into the event queue and then continued with the sync code. The scheduled code will be executed eventually but it's not determined when. It depends on the response of the server in this example.

If you return a Promise you can use .then(...) the chain the execution so that your code is only executed when the async execution is completed.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • What is better option in my situation Promise, or as @MSwehli mension an Observable ? Can you show me how excacly i shoud use this kind of pattern inside my return statement ? – MichalObi Apr 11 '16 at 18:46
  • `Observable` is usually for a series of events and `Promise` for a single event. There is a strong movement to use `Observables` everywhere because they are cancelable and to have the same API everywhere. I guess the best way is to return an `Observable` instead of a `Subscription`. This requires the caller to do the subscription. I'll update my answer. – Günter Zöchbauer Apr 11 '16 at 18:49
  • Actually it's a bit more complicated in your case. I'll have another look tomorrow. It's quite late here. Maybe @MSwehli or someone else provides some example earlier. – Günter Zöchbauer Apr 11 '16 at 18:53
  • I guess you should switch to a pattern shown in http://stackoverflow.com/questions/36271899/what-is-the-correct-way-to-share-the-result-of-an-angular-2-http-network-call-in and then call `myService.getData().subscribe((data => doSomethign())` whereever you want to access the data. – Günter Zöchbauer Apr 12 '16 at 05:16
0

There are two errors/inconsistencies in your code:

  1. userAuthenticate() call followed with getAllItems() call. These calls are async, user is not yet authenticated by the time getAllItems() is called, getAllItems will fail.

Solution here is to chain calls using rxjs flatMap:

//assuming userAuthenticate returns Observable
userService.userAuthenticate().flatMap(()=>{
    return repairsService.GetRepairs();
}).subscribe(..process repairs..);
  1. getAllItems() is called nearly at the same time as GetItems(). In most cases it fails also, because previous http request is not completed when GetItems() is called.

In my opinion early initialization is not necessary here, use service directly:

//ChildComponent
ngOnInit() {
    this._RepairsService.GetRepairs().subscribe(..do anything with list of repairs i.e. assign to bindable property..);
}

You could add console.log statements in each part of the code to see the order of events in your app.

kemsky
  • 14,727
  • 3
  • 32
  • 51
  • But in this moment i use this.getAllItems() on ngOnInit in repairs.component.ts to iterate through myItems array with NgFor in my repairs.component.html. – MichalObi Apr 11 '16 at 19:42
  • you could declare component public field as `public repairs:Array = [];` and then `.subscribe(repairs=> this.repairs = repairs);` and binding should do the rest. – kemsky Apr 11 '16 at 20:21