1

I need to bind template data variables from a service. Service uses HTTP to retrieve data in JSON format. I get the data right, but because request is so asynchronous return of Service is always undefined.

How can I sell asynchronous data into the template? Without the use of callback?

AppComponent:

    import {Component} from 'angular2/core';
    import {RouteConfig, Router, ROUTER_DIRECTIVES} from 'angular2/router';
    import {SystemComponent} from "./system.component";
    import {MenuProvider} from "./providers/menuprovider";
    import {Menu} from "./entity/menu";
    import {Http, Headers, HTTP_PROVIDERS} from 'angular2/http';

    @Component({
        selector: 'app',
        templateUrl: '/templates/layout',
        directives: [ROUTER_DIRECTIVES]
    })

    @RouteConfig([
        {
            path: '/',
            redirectTo: ['/System'],
            useAsDefault: true
        },
        {
            path: '/-1/...',
            name: 'System',
            component: SystemComponent
        }
    ])



    export class AppComponent {

        menusItems:Array<Menu>;

        constructor(router: Router, menuProvider:MenuProvider){
            this.menusItems = menuProvider.getMenuItems(1);
            router.navigate(['/System']);
        }
    }

MenuProvider:

    import {Menu} from "../entity/menu";
    import {IndexedCollection} from "../collections/IndexedCollection";
    import {Injectable}    from 'angular2/core'
    import {Http, Headers, HTTP_PROVIDERS} from 'angular2/http';

    @Injectable()
    export class MenuProvider{

        _menuItems:Array<Menu>;

        private _http:Http;

        constructor(http:Http){
            this._http = http;
        }

        getMenuItems(shopId:number){
            this.loadMenuForShopId(shopId);
            return this._menuItems;
        }

        private loadMenuForShopId(shopId:number){
            var base = this;
            this._http.get('/services/menu/list/' + shopId)
                .subscribe(
                  function(res){
                      res = res.json();
                      for(index in res['menuItems']){
                          var menu = res['menuItems'][index];
                          base._menuItems.push(new Menu(menu['url'], menu['name']));
                      }
                  }
                );
        }
    }

MenuEntity:

     export class Menu{

        private _url;

        private _name;

        constructor(url:string,name:string){
            this._url = url;
            this._name = name;
        }

        get url() {
            return this._url;
        }

        get name() {
            return this._name;
        }
    }

Template:

        <ul>
            <li  *ngFor="#menu of menusItems">
                <a>{{menu.getName()}}</a>
            </li>
        </ul>
JaSHin
  • 211
  • 2
  • 16
  • 43
  • You can return the observable and subscribe to it in the Controller. The observable will be delivered immediatly and you can choose what to do with the info once it gets back in the controller. – Langley Jan 16 '16 at 19:26

2 Answers2

2

Take a look at Observable; it's your new best friend (;

You can make "cold" observable (just describe what it should do):

getMenuItems(shopId:number){
  return this._http
    .get('/services/menu/list/' + shopId)
    .map(res => res.json())
    .map(res => {
      let menu = res.menuItems;
      return new Menu(menu.url, menu.name)
    })
}

... and let angular subscribe to it using AsyncPipe:

<ul>
  <li  *ngFor="#menu of menusItems |async">
    <a>{{menu.getName()}}</a>
  </li>
</ul>

AsyncPipe works on Observables, Promises and Events...

transform(obj: Observable<any>| Promise<any>| EventEmitter<any>, args?: any[]) : any
Sasxa
  • 40,334
  • 16
  • 88
  • 102
  • .map() is not a function :X – JaSHin Jan 17 '16 at 16:00
  • It's a [different issue](http://stackoverflow.com/a/34581771/1876949). You need to import map operator... – Sasxa Jan 17 '16 at 16:04
  • How Can I use this for 1 item? My simple code: http://pastebin.com/K1rEYeNj Error: EXCEPTION: TypeError: undefined is not an object (evaluating 'l_currentShop0.name') in [{{currentShop.name}} in AppComponent@22:12] – JaSHin Jan 17 '16 at 19:09
  • You can use [Elvis operator](https://angular.io/docs/ts/latest/guide/template-syntax.html#!#expression-operators) in your template: `
    {{currentShop?.name}}
    `
    – Sasxa Jan 18 '16 at 11:38
1

Please don't use a synchronous HTTP request. This can block your browser from running any code until after the request has completed.

The best solution, in my opinion, is to keep the request asynchronous but to show a message to the user, such as "Loading..." while you retrieve the data.

You can use a ngIf directive along with a variable that will become true after the request is completed.

Bryan
  • 747
  • 1
  • 7
  • 19
  • 1
    in addition browser vendors are deprecating synchronous XmlHttpRequest and already placing warnings in dev tools console. A day will come soon when synchronous code approach will break – charlietfl Jan 16 '16 at 18:09