![](/img/trans.png)
[英]How to test a ViewChild/ContentChild property in a angular component?
[英]How can I prevent an Angular 8+ component from executing until an Input property is set and a ViewChild is ready
我编写了一个 Angular 组件,用于使用美人鱼 npm package渲染美人鱼图。
它按原样工作(见下文),但我想找到一种使用它的方法,而无需父组件包含*ngIf
(以防止它在没有数据时尝试渲染)。 我希望组件本身能够决定是否调用mermaid.render()
本身。 如果不使用*ngIf
,则当graphDefinition
为null
时,组件的ngAfterContentInit()
可能会运行。 这会导致mermaid
库出错。
我可以使用ngOnChanges()
或属性设置器来检测何时设置了值,但由于组件依赖于ViewChild
,我认为ngOnChanges()
或属性设置器可能会在ViewChild
准备好之前执行。
问题:您能否建议一种可靠地改进此组件的方法,以便不需要*ngIf
?
图定义(字符串)作为Observable<string>
从父组件中的 HTTP 调用提供的示例用法:
父组件.html
<mermaid-viewer
*ngIf="graphDefinition$ | async as graphDefinition"
[graphDefinition]="graphDefinition"
></mermaid-viewer>
mermaid-viewer.component.html
<div #mermaid></div>
mermaid-viewer.component.ts
import {
AfterContentInit,
Component,
Input,
ViewChild
} from "@angular/core";
import * as mermaid from "mermaid";
@Component({
selector: "mermaid-viewer",
templateUrl: "./mermaid-viewer.component.html"
})
export class MermaidViewerComponent implements AfterContentInit {
@ViewChild("mermaid", { static: true })
public mermaidDiv;
@Input()
public graphDefinition: string;
public ngAfterContentInit(): void {
mermaid.initialize({
theme: "default"
});
const element: any = this.mermaidDiv.nativeElement;
mermaid.render(
"graphDiv",
this.graphDefinition,
(svgCode, bindFunctions) => {
element.innerHTML = svgCode;
}
);
}
}
您的问题的完整答案过于复杂,它将涉及BehaviourSubject
和combineLatest
等运算符。 但我试着给你一个提示。
您可以将Input
与 setter 和 getter 结合使用,以便在输入更改时收到通知:
private _graphDefinition: string;
@Input()
public set graphDefinition(value) {
this._graphDefinition = value;
// .next() on BehaviourSubject
}
public get graphDefinition() {
return this._graphDefinition;
}
你可以在 ViewChild 上做同样的事情,在这种情况下,当元素视图被初始化时,你只会发出一次:
private _mermaidDiv;
@ViewChild("mermaid", { static: true })
public set mermaidDiv(value) {
this._mermaidDiv = value;
// .next() on another BehaviourSubject
}
public get mermaidDiv() {
return this._mermaidDiv;
}
然后你可以在combineLatest
上使用ngAfterContentInit
(我猜ngAfterViewInit
也会很好):
ngAfterContentInit() {
// ...
combineLatest(
bs1,
bs2
).subscribe(result => {
// do something with definition + view
// result[0] => is your graph definition
// result[1] => is your queried view
});
// ...
}
现在你可以在你的父组件中多次更改你的graphDefinition
,你的配置会自动更新
我想出了这个,灵感来自Cristian Traìna的答案,但没有使用BehaviourSubject
或combineLatest
。
render()
function 将被调用两次。 第一次只有graphDefinition
或mermaidDiv
中的一个可用,它什么也不做。 第二次两个值都可用,然后调用mermaid.render
。 我认为它更简单一些,尽管可能没有那么优雅——如果需要多个属性,也无法很好地扩展。
import {
Component,
Input,
ViewChild
} from "@angular/core";
import * as mermaid from "mermaid";
@Component({
selector: "mermaid-viewer",
templateUrl: "./mermaid-viewer.component.html"
})
export class MermaidViewerComponent {
private hasRendered = false;
private _mermaidDiv;
@ViewChild("mermaid", { static: true })
public set mermaidDiv(value) {
this._mermaidDiv = value;
this.render();
}
public get mermaidDiv() {
return this._mermaidDiv;
}
private _graphDefinition: string;
@Input()
public set graphDefinition(value) {
this._graphDefinition = value;
this.render();
}
public get graphDefinition() {
return this._graphDefinition;
}
private render() {
if (this.graphDefinition && this.mermaidDiv) {
this.hasRendered = true;
mermaid.initialize({
theme: "default"
});
const element = this.mermaidDiv.nativeElement as HTMLDivElement;
mermaid.render(
"graphDiv",
this.graphDefinition,
(svgCode, bindFunctions) => {
element.innerHTML = svgCode;
}
);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.