如何正确处理依赖于ViewChild的组件和Angular 2中可观察数据的变量检测?

时间:2016-05-05 02:32:56

标签: javascript angular

目前,我有一个适合更大仪表板的组件,用于呈现节点的直接父子关系图。每当外部更改node_id输入时,该组件都应刷新其图形。

我已经包含了我的代码的简化版本。

@Component({
  selector: 'relations',
  template: `
      <div [class]="'panel panel-' + (_loading ? 'default' : 'primary')">
          <div class="panel-heading">Child relations</div>
          <div class="panel-body">
              <div class="loading" *ngIf="_loading" style="text-align: center">
                  <img src="./loading.gif" height="100px" width="100px" />
              </div>
              <div class="graph_container" [style.display]="_loading ? 'none': 'block'" #my_graph></div>
          </div>
      </div>
    `
})
export class GraphComponent implements OnChanges {

    @Input('node_id') node_id;
    @ViewChild('my_graph') graphDiv;

    private _loading: boolean = true;
    private _current_node: Node;
    private _parent: Node;
    private _children: Node[];

    constructor(
        private _nodeService: NodeService
    ) {}

    ngOnChanges(changes){
        this.getRelations();
    }

    getRelations() {
        this._loading = true;
        Observable.combineLatest(
            this._nodeService.getEvent(this.node_id),
            this._nodeService.getChildren(this.node_id),
            this._nodeService.getParent(this.node_id)
        ).subscribe(v => {
            this._current_node = v[0];
            this._children = v[1];
            this._parent = v[2];
            this._loading = false
            this.renderGraph();
        });
    }

    renderGraph() {
        ...
    }
}

现在我遇到的问题是竞争条件; renderGraph()方法依赖于@ViewChild('my_graph') graphDiv变量来知道应该删除canvas元素以呈现图形的位置。因此,当observable解析时,它可能会尝试在renderGraph()组件初始化之前调用@ViewChild

我尝试通过以下方式玩生命周期钩子:

ngAfterViewInit(){
    if (!this._loading){
        this.renderGraph();
    }
}

只有在加载视图之前observable完成时才有用,并且如果视图首先完成渲染,则不会渲染任何图形。

所以我的问题是,我怎样才能正确实现我的目标?也就是说,在可观察的解析之后重新渲染图形以响应对node_id的更改。

我在Angular 2(以及一般的前端)非常新,而且我的直觉告诉我,我没有以预期的方式使用observable,但是我很难找到任何例子类似于我想要的。

非常感谢任何帮助/指导/建议。

谢谢!

1 个答案:

答案 0 :(得分:2)

我会使用BehaviorSubject这只是一种特殊类型的Observable。来自文档的片段:

  

它存储发布给消费者的最新值,每当新的Observer订阅时,它将立即收到&#34;当前值&#34;来自BehaviorSubject。

首选BehaviorSubject的原因是因为无论订阅何时实际发生,它总是会发出最后node_id值。如果它是在viewInit之前设置的。此外,由于它始终具有最新价值,因此我们不需要在node_id上拥有GraphComponent属性。我们只需要一个setter,它将向订阅者发送传递的值并自动将其保存在主题上,因此每个新订阅者都将获得当前值。

import {BehaviorSubject} from 'rxjs/BehaviorSubject';
...
export class GraphComponent implements OnChanges {
    @ViewChild('my_graph') graphDiv;
    ...
    private _nodeIdSubject = new BehaviorSubject(-1);

    constructor(...) {}

    @Input('node_id') 
    set node_id(id){  // this is the same as ngOnChanges but will only be triggered if node_id changed
        this._nodeIdSubject.next(id);
    }
    ngAfterViewInit(){  // subscribe to node_id changes after view init
        this._nodeIdSubject.filter(n=> n > -1).subscribe(nodeId=> this.getRelations(nodeId));
    }

    getRelations(nodeId) {
        ...
    }

    renderGraph() {
        ...
    }
}

这可能不是最好的方法,但我喜欢它,因为现在你有一个可以自由操作的node_id流。