0

I am trying to implement a tree view component. This component uses itself recursively. I have gotten it to work properly and display all directories and subdirectories.

The problem i am facing is when dealing with events (item selected). All tree view components subscribe to the shared service event, as does the base component.

When i debug the code, i can see that at any given time, the service only has 1 listener to the event, which is wrong. This causes the base component to not receive any callbacks, as well as some other problems.

When clicking on a directory, i can see inside the shared service that the number of listeners is incorrectly set to 1.

Edit: I can see that the TreeViewService's constructor is called 4 times (instead of 1).

using AngularJS 2.0.0-rc.2

Here is the code.


TreeViewComponent

export class TreeViewComponent {
  @Input('items') items: TreeViewItem[] = [];
  @Input('item') item: TreeViewItem = new TreeViewItem();

  constructor(private treeViewService: TreeViewService) {
    let me = this;
    this.treeViewService.on('itemSelected', function(item) {
      me.item = item;
    });
  }

  treeViewItemSelected(item: TreeViewItem) {
    this.treeViewService.broadcast('itemSelected', item);
  }

treeview.component.html

<ul class="treeview">
  <li *ngFor="let selItem of items">
    <a [class.file]="selItem.children.length == 0" [class.folder]="selItem.children.length > 0" [class.selected]="selItem.id == item.id" (click)="treeViewItemSelected(selItem)">{{selItem.title}}</a>
    <my-treeview [items]="selItem.children" *ngIf="selItem.children.length > 0"></my-treeview>
  </li>
</ul>

TreeViewService

import {Injectable} from '@angular/core';
import * as Rx from 'rxjs/Rx';

@Injectable()
export class TreeViewService {

  private listeners:any;
  private eventSubject: any;
  private events: any;

  constructor(){
    this.listeners = {};
    this.eventSubject = new Rx.Subject();
    this.events = Rx.Observable.from(this.eventSubject);

    this.events.subscribe(
      ({name, args}) => {
        if(this.listeners[name]) {
          for(let listener of this.listeners[name]) {
            listener(...args);
          }
        }
      }
    );
  }

  on(name, listener) {
    if(!this.listeners[name]) {
      this.listeners[name] = [];
    }

    this.listeners[name].push(listener);
  }

  broadcast(name, ...args) {
    this.eventSubject.next({
      name,
      args
    });
  }
}

BaseComponent

@Component({
  selector: 'my-article-form',
  template: require('./articleform.component.html'),
  styles: [require('./articleform.component.scss')],
  providers: [FileUploadService, TreeViewService],
  directives: [FileManagerComponent, TreeViewComponent]
})
.
.
.

  constructor(private fileUploadService:FileUploadService, private apiService:ApiService, private treeViewService:TreeViewService) {
    let me = this;
    this.treeViewService.on('itemSelected', function(item) {
      me.treeViewItem = item;
    });
  }

BaseComponent html

Selected: {{treeViewItem.title}}<br />
<my-treeview [items]="treeViewItems"></my-treeview>
sm0ke21
  • 441
  • 5
  • 20
  • Where do you provide the `TreeViewService`. If you provide it at the `TreeViewComponent` every component will get its own instance. You need to provide it at a parent. – Günter Zöchbauer Jul 20 '16 at 10:02
  • The TreeViewService is provided inside the base component's providers array. This is the root component. It is also provided inside the TreeViewComponent component. – sm0ke21 Jul 20 '16 at 10:26
  • I guess you need to use `share` or `publish` https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/publish.md for multiple subscribers. I don't use rxjs myself and don't know details. – Günter Zöchbauer Jul 20 '16 at 10:47
  • I think it has something to do with the TreeViewService getting instantiated once for every component. I thought services were singletons but after some searching i can see that Services are singletons for each component. – sm0ke21 Jul 20 '16 at 10:52
  • Services are singletons for each provider. If you provide it at the component and have several instances of that component, then there is a single instance per component. If you provide it once on a common parent component, then there is this single component for all descendants. This is why I asked above where you provide the service. – Günter Zöchbauer Jul 20 '16 at 10:55
  • You are correct Günter. I followed your answer here: https://stackoverflow.com/questions/34929665/angularjs-2-multiple-instance-of-service-created and got it working. – sm0ke21 Jul 20 '16 at 10:55

1 Answers1

2

The issue is that Services are instantiated once for every component. This works in a hierarchical manner bottom-up.

See answer by Günter Zöchbauer here: AngularJs 2 - multiple instance of service created

Service needs to be defined when bootstrapping the application and the only provided in the root component of all the other child components that need to share this same service instance.

sm0ke21
  • 441
  • 5
  • 20