目前,我有一个适合更大仪表板的组件,用于呈现节点的直接父子关系图。每当外部更改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,但是我很难找到任何例子类似于我想要的。
非常感谢任何帮助/指导/建议。
谢谢!
答案 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
流。