0

Referring to Global Events in Angular 2 I am trying to implement a cart service that emits an event when a new item is added to the cart. I subscribe to this service event in another component called navbar (which is not a child of cart) where I show my number of cart items etc.

My cart service:

export class CartService {
    public itemAdded$: EventEmitter<any>;
    ...

    constructor(private http: Http){

        this.itemAdded$ = new EventEmitter();
        this.cart=[];
    }

    addToCart(id: any): any {

        this.itemAdded$.emit(id);

        return this.http.get(  this.myUrl + 'add' + '/' + id + '/', { withCredentials:true})
        .toPromise()
        .then(response => response.json())

    }
  ...
  ...
} 

My navbar component:

@Component({
directives:[ROUTER_DIRECTIVES, Cart],
providers:[CartService],

})
export class Navbar implements OnInit{

    ...
    totalCost: any;
    cartItemCount : any;
    addedItem: any;

    constructor(private cartService: CartService){

        this.cartService.itemAdded$.subscribe( (id: any) => {
            alert(id);
            this.onItemAdded(id);
            this.fetchCartElements();
        });
    }

     private onItemAdded(item: any): void {
        // do something with added item
        this.addedItem = item;
    }
...
...
}

However, whenever I add an item, nothing happens in the navbar i.e. alert(id) or onItemAdded() are not called automatically so that cartItemCount etc can get automatically updated.

What is going wrong?

Community
  • 1
  • 1
Thinker
  • 5,326
  • 13
  • 61
  • 137
  • 2
    Where did you provided this service? – Vlado Tesanovic Nov 08 '16 at 08:34
  • I did not get your question correctly, but in my navbar component I have added it in [providers]. I updated my question with that. Cart service otherwise has nothing to do with navbar directly. – Thinker Nov 08 '16 at 08:42

3 Answers3

1

You should use EventEmitter only for @Output()'s!

For your scenario perfectly fits an Observable ..

Create an Subject and call next(), it's similar to emit().

import { Subject } from 'rxjs';

// ..

   public itemAdded$ = new Subject<any>();

// ..

   this.itemAdded$.next(id);

The subscribing part should be fine.

slaesh
  • 16,659
  • 6
  • 50
  • 52
  • import { Subject } from 'rxjs'; gives me error saying 404 not found. However I have import { Observable } from 'rxjs/Observable'; which works fine.. – Thinker Nov 08 '16 at 09:05
  • then try this: `import { Subject } from 'rxjs/Subject';` – slaesh Nov 08 '16 at 09:06
1

I think your error here is a conception error.

As @mxii said, your case perfectly fits Observable behaviour, you shouldn't use promise and an event emitter (which is called before your item is actually added on server side).

Solution with Promise:

In case you want to fix your problem with promise, I think the solution is to trigger the event once you got your answer only:

    return this.http.get(  this.myUrl + 'add' + '/' + id + '/', { withCredentials:true})
    .toPromise()
    .then(response => {
                       this.cart.push(response.json());
                       this.itemAdded$.emit(id);
                       });

And then provide it in your top-level module (usually AppModule) to ensure you get only one instance accross your aplication.

Solution with Observable:

addToCart(id: any): Observable<any> {//Don't you have an item interface to type the object you get?
        return this.http.get(  this.myUrl + 'add' + '/' + id + '/', { withCredentials:true})
        .map(res => res.json());
    }

And then on your NavBar:

@Component({
directives:[ROUTER_DIRECTIVES, Cart]
//Note that I removed CartService, as I said you HAVE to provide it on the AppModule, else you'll have different instances accross your application and it seems like it's not what you want.
})
export class Navbar implements OnInit{

    ...
    totalCost: any;
    cartItemCount : any;
    addedItem: any;

    constructor(private cartService: CartService){
    }

     private onItemAdded(item: any): void {
        // do something with added item
        this.addedItem = item;
    }

    public addItemToCart(id:number):void{ 
        //I guess you're adding item from a button click, the button should call this method, passing the id of the item as parameter.
        this.cartService.addToCart(id).subscribe(this.onItemAdded);
    }
...
...
}
Community
  • 1
  • 1
Supamiu
  • 8,501
  • 7
  • 42
  • 76
  • In my case I do not have addItemToCart(id:number) methos in my navbar component, I call it from another component. In navbar component, I simple fetch cart elements, and can remove them. Adding to cart happens in my different product components. – Thinker Nov 08 '16 at 09:23
0

Services are singleton per provider. You are injecting different instances of the CartService to navbar and whomever is calling addToCart function.

I expect Navbar is displayed in every page of your application, so here is how to make CartService globally singleton.

@NgModule({
  declarations: [
  ],
  imports: [
  ],
  providers: [CartService], //<--- added here
  bootstrap: [AppComponent]
})
export class AppModule { }

Remove any other providers: [CartService] from modules and components (if you are not indenting to create another instance)

Sefa
  • 8,865
  • 10
  • 51
  • 82
  • And referring to above two answers, which one is more apt with your solution? event emiiter or with Subject and next()? – Thinker Nov 08 '16 at 09:03
  • You need to make your service singleton as i shown in both ways. – Sefa Nov 08 '16 at 09:05