1

I have an Angular component called "tree". What I want is to call methods on tree from any other component in my app. There will only be one tree in my entire app at any time. Is it ok to create the component in the place I want to view it and share the componentRef using a service this way:

export class AppComponent implements OnInit {

    @ViewChild("tree") treeComponent: TreeComponent;

    constructor(private treeService: TreeService) { }

    ngOnInit() {
        this.treeService.setTreeReference(this.treeComponent);
    }

}

Then I could call it this way:

export class OtherComponent {

    constructor(private treeService: TreeService) { }

    getActiveNode() {
        return this.treeService.treeReference.getActiveNode();
    }

}

Is this a good practice in Angular2+ or generally in MVC?

Edit1: I forgot to specify that I don't have access to the TreeComponent code as it is imported from a library.

XandruDavid
  • 310
  • 3
  • 13
  • The consumers of this service need to know the active node at all times? – bryan60 Dec 18 '17 at 14:59
  • Instead of storing the tree component itself in the tree service, you should allow the tree service to contain the methods that manipulate the data the tree component needs. Set up an on observable(s) on the tree service, and subscribe to it from the tree component, so that you can properly update the tree component when some piece of data it depends on has changed (in the tree service). – Eric Lease Dec 18 '17 at 15:00

2 Answers2

1

This is not the recommended approach to shared service architecture. The reason is because this creates tight coupling between the components. All consumers need to know the specific implementation details of both the tree service and the tree component, if either needs to change, every consumer will break. The way it should be exposed is a simple service api that exposes a way to set and subscribe to the shared data. This means you only need to maintain your service's API and then the implementation itself can be quite flexible. It also makes your code much easier to read and more obvious what's going on. Someone poking their head in might find it kind of mysterious that that app component is setting a tree reference onIinit and wouldn't understand. This solution also doesn't really scale very well as you have more needs of similar functions

This is how it would be achieved:

@Injectable()
export class TreeService {
  private activeNodeSubject: BehaviorSubject<any> = new BehaviorSubject(null);
  activeNode$ = this.activeNodeSubject.asObservable();
  setActiveNode(node) {
      this.activeNodeSubject.next(node);
  }
}

then in tree component, you call setActiveNode every time it changes, and all consumers subscribe to activeNode$ to always know which node is active.

bryan60
  • 28,215
  • 4
  • 48
  • 65
  • As added to my original question, TreeComponent is imported from a library, can't use the TreeService from inside. Should I do the same thing but inside AppComponent? – XandruDavid Dec 18 '17 at 15:05
  • The use of an external library makes this extra true. I'd actually recommend creating a wrapper component around the tree component to capture this rather than doing it in app component. This insulates your app if you ever need to change the 3d party lib. does treecomponent emit an event when the active node is changed that you can bind to in app component? – bryan60 Dec 18 '17 at 15:06
  • Yes, it does and this looks like a really good and clean strategy, thank you! – XandruDavid Dec 18 '17 at 15:09
1

There multiple ways to achieve what you are looking for:

  • Event driven

You can dispatch events to your "tree" component by using the mediator pattern. You can find multiple libs for that. For instance: EventEmitter2

  • Inject the component has value

You can inject the component itself has value and then use it everywhere on your application. An example of an injection factory.

Best, José

jpsfs
  • 708
  • 2
  • 7
  • 24