John Arrowwood
John Arrowwood

Reputation: 2470

Can an object be notified when a component object changes in Angular 2/4?

In Angular, a component can receive as @Input() a variable or object. Whenever the variable changes, Angular notifies the component. Very cool...

I'm trying to build an app that shows a ng2-tree view on the left, and an edit-view on the right. When the user clicks on a node in the tree, the details of that item is displayed on the right, and the user can change properties of that item. What I need is for those changes to propagate back to the tree.

The individual nodes in the tree are an ItemTreeNode:

export class ItemTreeNode implements TreeModel {
  value: string;
  id: string;
  children: Array<ItemTreeNode>;
  icon: string;
  settings: TreeModelSettings;
  loadChildren;

  constructor(
    private itemService: ItemService,
    private parent_id: string,
    public item: Item
  ){ ... }
}

The tree renders the node using .value for the name. This is set during the constructor by calling this.item.nodeName(). Once set, it doesn't change. The problem is, it needs to change whenever the item is changed.

What I'm looking for is a way for this object to be notified if the item changes, so that the value property can be updated.

Is there a way for the ItemTreeNode to "watch" the item and receive a callback if it ever changes, as part of Angular's change detection cycle?

Or is there a better way of implementing the desired behavior?

Upvotes: 0

Views: 1314

Answers (1)

dhilt
dhilt

Reputation: 20814

This could be done with the help of RxJs. Let's interrupt Item's name setting via setter and trigger an Observable .next().

import {BehaviorSubject} from 'rxjs/BehaviorSubject'

class Item {
  private _name: string;
  private nameSource = new BehaviorSubject<string>('');
  name$ = this.nameSource.asObservable();

  set name(name: string) {
    this._name = name;
    this.nameSource.next(name);
  } 

  get name() { return this._name; } 

  getNodeName() { return this._name + ' !!!'; }

  constructor(name: string){
    this._name = name;
  }
}

You see, I push the original Item's name on this.nameSource.next(name). In my example I assume that Item's name and the Node's name are defferent. Observable $name is public, it is for external subscriptions. Then I need to subscribe on Item's name change, and the best place for this is the ItemTreeNode constructor which gets an Item instance as a parameter:

class Node {
  value: string;

  constructor(item: Item) {
    item.name$.subscribe(result => this.value = item.getNodeName());
  }
}

Here the result is the Item's new name, but since it's not the same as Item's Node name, I do an extra call .getNodeName(). So the only thing you need to do is to pass Item's instance to Node's constructor, and then RxJs does the binding magic for you.

this.item = new Item('My Name');
const node = new Node(this.item);

That's all, I creatred a Plunker to demonstrate this approach.

Upvotes: 0

Related Questions