0

I need advice on how to make observable variable currentShop dependent on variable shopList.

AppComponent reads shopList through shopProvider. It works without a problem, but I need to modify the code to set currentShop from loaded shops.

AppComponent:

export class AppComponent {

    currentShop;
    shopList;

    constructor(router: Router,shopProvider:ShopProvider){
        this.shopList = shopProvider.shops;
        this.currentShop = shopProvider.currentShop;

        shopProvider.loadShops();
        shopProvider.setCurrentShop(-1);

        router.navigate(['/System']);
    }
}

ShopProvider:

export class ShopProvider{

    private _http:Http;

    shops:Observable<Array<Shop>>;

    currentShop:Observable<Shop>;

    private obServer:any;

    private _shopDataStore : {
        shops: Array<Shop>
    }

    constructor(http:Http){
        this._http = http;
        this.shops = new Observable(observer => this.obServer = observer).share();
        //this.currentShop = ??
        this._shopDataStore = {shops: []};
    }

    setCurrentShop(shopId:number){
       //Code ??
    }

    loadShops(){
        this._http
            .get('/services/shop/list')
            .map(res => res.json())
            .subscribe(data => {
                this._shopDataStore.shops = data;
                this.obServer.next(this._shopDataStore.shops);
            })
    }
}

HTML:

<div>{{currentShop.name | async}}</div>

<ul>
    <li *ngFor="#shop of shopList | async">
        <a [routerLink]="['/Shop', {shopId: shop.id}]">
            <span>{{shop.name}}</span>
        </a>
    </li>
</ul>
JaSHin
  • 211
  • 2
  • 16
  • 43

2 Answers2

3

I would go for an architecture more similar to:

ShopService

class Shop {
  constructor(public shopId: Number, public shopName: string) {}
}

@Injectable()
class ShopService {
  shops: Observable<Array<Shop>> = new Observable<Array<Shop>>();
  shopId: Subject<Number> = new Subject<Number>();
  currentShop: Observable<Shop> = new Observable<Shop>();

  public static getShopById(shops: Array<Shop>, shopId: Number): Shop {
    return shops.filter(shop => shop.shopId === shopId)[0];
  }
  public static compareShops(oldShop: Shop, newShop: Shop): Boolean {
    return oldShop.shopId === newShop.shopId;
  }

  constructor(@Inject(Http) private http: Http) {
    this.shops = this.http
      .get('**url here**')
      .map(res => res.json().shops);

    this.currentShop = this.shops
      .combineLatest(this.shopId, ShopService.getShopById)
      .distinctUntilChanged(ShopService.compareShops);
  }

  setCurrentShop(shopId: Number): void {
    this.shopId.next(shopId);
  }
}

Usage in your Component:

  • save ShopService.currentShop to a variable in the Component (let's say this.currentShop
  • use {{ currentShop | async }} or .subscribe() to the ShopService.currentShop observable to be given the shop when it changes

See a working example on CodePen. I like this approach more because I'm not saving any state, which seems more in line with reactive programming.

Ionut Costica
  • 1,382
  • 1
  • 9
  • 19
  • Thank you for the very detailed instructions! But isn't work for me. This application does not write errors. shopList still works well, but CurrentShop does not appear :(. If in component will use js .subscribe(res => {console.log(res)}). Displays {id: 1, name: 'Test'}. What could be the problem that it working in a component and in the template not? Here si full code: http://pastebin.com/TZ0cwFJF – JaSHin Jan 19 '16 at 18:14
  • I would also add that I have to put some logs into code and displays only "setting". – JaSHin Jan 19 '16 at 18:17
  • 1
    I encountered this problem, too. If you see in my example, I pass `currentShop | async` to a separate component. `currentShop.name | async` tries to treat `name` as an observable... Not sure how to make something like `(currentShop | async).name`. You can subscribe to the `currentShop` observable in your component and keep the `Shop` instance in another variable and use that... – Ionut Costica Jan 19 '16 at 18:27
  • I found another one problem I do not understand. After each change routeParametr I try to set a value currentShop below shopId route parameter. http://pastebin.com/WBqdwjxP – JaSHin Jan 19 '16 at 19:13
  • try setting it in `ngAfterViewInit`. It seems to work on my example (but then again, in my example setting it in `ngOnInit` didn't produce errors, even though it didn't actually show the result in the page - the `async` pipe totally missed the change) – Ionut Costica Jan 20 '16 at 06:59
  • ngAfterViewInit doing the same. I found that somebody has a similar problem: http://stackoverflow.com/questions/34364880/expression-has-changed-after-it-was-checked I try setTimeout(function(this._shopProvider.setCurrentShop(...))) without second parameter and it helped my. But it's a strange solution. – JaSHin Jan 20 '16 at 08:00
1

I suggest the following:

ShopProvider

private shopsObserver:Observer<Shop[]>;
private currentShopObserver: Observer<Shop>;
private _shopDataStore : {
    shops: [],
    currentShop: undefined
}

constructor(http:Http){
    this._http = http;
    this.shops = new Observable(observer => this.obServer = observer).share();
    this.currentShop = new Observable(observer => this.shopObserver = observer).share();

}

setCurrentShop(shopId:number){
   var item = this._shopDataStore.shops.find(item=>item.shopId == shopId);
   this._shopDataStore.currentShop = item;
   this.currentShopObserver.next(this._shopDataStore.currentShop);
}

loadShops(){
    this._http
        .get('/services/shop/list')
        .map(res => res.json())
        .subscribe(data => {
            this._shopDataStore.shops = data;
            this.shopsObserver.next(this._shopDataStore.shops);                
        })
}

AppComponent

export class AppComponent {

    currentShop;
    shopList;

    constructor(router: Router,shopProvider:ShopProvider){
        shopProvider.shops.subscribe(shops=>this.shopList = shops);
        shopProvider.currentShop.subscribe(currentShop => this.currentShop = currentShop);

        shopProvider.loadShops();
        shopProvider.setCurrentShop(-1);

        router.navigate(['/System']);
    }
}

HTML

<div>{{currentShop?.name}}</div>

<ul>
    <li *ngFor="#shop of shopList">
        <a [routerLink]="['/Shop', {shopId: shop.id}]">
            <span>{{shop.name}}</span>
        </a>
    </li>
</ul>
Michael Kang
  • 52,003
  • 16
  • 103
  • 135