18

So let's say you have an interface that has a toolbar, sidebar, and a grid. The toolbar has a drop-down that when a user changes, the content in sidebar and grid changes. Back in Angular 1, I would use a Service to have all of my dynamic data. When something changes in the service, all components that use that service will update as well.

Well in Angular 2, it looks like people are using different methods. I wanted to get your input on which is the preferred way.

  • Static Service
  • OnChanges
  • Inputs and Outputs

The remaining question that I have is if it's best practice to create a new service for each data item that is shared between components or can we just have one service that has an object that stores all shared data.

Original Plunker - Each change will have its own service

app.component.ts

import {Component} from 'angular2/core';
import {NavService} from '../services/NavService';

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number;
  subscription: any;
  constructor(private _navService:NavService) {}
  ngOnInit() {
    this.item = this._navService.navItem();
    this.subscription = this._navService.navChange$.subscribe(
      item => this.selectedNavItem(item));
  }
  selectedNavItem(item: number) {
    this.item = item;
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
    <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>
  `,
})
export class Navigation {
  item = 1;
  constructor(private _navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this._navService.changeNav(item);
  }
}

@Component({
  selector: 'my-app',
  template: `{{title}}
  <p>
  <my-nav></my-nav>
  <button (click)="showObsComp = !showObsComp">toggle ObservingComponent</button>
  <div *ngIf='showObsComp'>
    <obs-comp></obs-comp>
  </div>
  `,
  directives: [Navigation, ObservingComponent]
})
export class AppComponent {
  title = "Angular 2 - event delegation";
  showObsComp = true;
  constructor() { console.clear(); }
}

NavService.ts:

import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/share';

export class NavService {
  private _navItem = 0;
  navChange$: Observable<number>;
  private _observer: Observer;
  constructor() {
    this.navChange$ = new Observable(observer =>
      this._observer = observer).share();
    // share() allows multiple subscribers
  }
  changeNav(number) {
    this._navItem = number;
    this._observer.next(number);
  }
  navItem() {
    return this._navItem;
  }
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>User Input</title>
    <link rel="stylesheet" href="styles.css">
    <script src="https://code.angularjs.org/2.0.0-beta.11/angular2-polyfills.js"></script>
    <script src="https://code.angularjs.org/tools/system.js"></script>
    <script src="https://code.angularjs.org/tools/typescript.js"></script>
    <script src="https://code.angularjs.org/2.0.0-beta.11/Rx.js"></script>
    <script src="https://code.angularjs.org/2.0.0-beta.11/angular2.dev.js"></script>
    <script>
      System.config({
        transpiler: 'typescript', 
        typescriptOptions: { emitDecoratorMetadata: true }, 
        packages: {
          app:      {defaultExtension: 'ts'},
          services: {defaultExtension: 'ts'},
        } 
      });
      System.import('app/boot')
            .then(null, console.error.bind(console));
    </script>
  </head>

  <body>
    <my-app>Loading...</my-app>
  </body>

</html>

app/boot.ts

import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
import {NavService} from '../services/NavService';

bootstrap(AppComponent, [NavService]);

Revised Plunker for example - Only one service which stores all data in object. A type will be passed to each listener to check if it needs to do anything based on that type.

George Stocker
  • 55,025
  • 29
  • 167
  • 231
Marin Petkov
  • 2,038
  • 3
  • 13
  • 21
  • 1
    Others have provided good answers but wanted to note that there is a section of the docs on [Component Interaction](https://angular.io/docs/ts/latest/cookbook/component-communication.html) that may be worth a read as well. – jandersen Mar 08 '16 at 22:21

2 Answers2

23

You could leverage shared service for this. It could contain both data and observables to subscribe on to be notified when data are updated.

  • Service

    export class ListService {
      list1Event: EventEmitter<any> = new EventEmitter();
    
      getLists() {
        return this.http.get(url).map(res => res.json())
          .subscribe(
            (data) => {
              this.list1Event.emit(data.list1);
            }
          );
      }
    }
    
  • Component

    @Component({
      selector: 'my-component1',
      template: `
        <ul>
         <li *ngFor="#item of list">{{item.name}}</li>
        </ul>
      `
    })
    export class MyComponent1 {
      constructor(private service:ListService) {
        this.service.list1Event.subscribe(data => {
          this.list = data;
        });
      }
    }
    
  • bootstrap

    bootstrap(AppComponent, [ ListService ]);
    

See this question for more details:

Liam
  • 22,818
  • 25
  • 93
  • 157
Thierry Templier
  • 182,931
  • 35
  • 372
  • 339
  • Excellent answer, however as noted in the eventemitter-or-observable forum you have linked to, it looks like observable is the better way. I will try it out right now and see how it works for me. – Marin Petkov Mar 08 '16 at 22:04
2

In your use case I would use services. Services are used to communicate its data to other components. One component could update this data to the service, and another component could read from it. Or both components could just read from it, and the server itself gets it's data from the 'outside world'.

You use input to pass along data from the parent to the child and you use output to output events from the child to the parent.

You use ngOnChanges to do something when something changes in the component itself, but I prefer to use get() and set() functions for that.

At least, that is my take on it :)

Poul Kruijt
  • 58,329
  • 11
  • 115
  • 120
  • Thank you for the excellent description on when to use each one. That will be really handy for me in the future. I am going to go with the Observable service approach mentioned in the EventEmitter vs Observable thread that @Thiery mentioned. – Marin Petkov Mar 08 '16 at 22:05
  • So I have a follow up question on your description of when to use events vs ngOnChanges. So let's say I have a parent component that uses a shared childComponent grid. By shared, I mean that multiple other components use this grid to display their own data. So if I want the parent component to call a function inside this child (grid), but only in this instance not all of the other ones, would ngOnChanges be the best use for this case? – Marin Petkov Mar 14 '16 at 15:10