2

我编写了一个 Angular 组件,用于使用mermaid npm 包渲染美人鱼图。

它按原样工作(见下文),但我想找到一种使用它的方法,而不需要父组件包含一个*ngIf(以防止它在没有数据时尝试渲染)。我希望组件本身能够决定是否调用mermaid.render()自己。如果*ngIf未使用,则组件ngAfterContentInit()可能会在graphDefinitionis时运行null。这会导致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;
            }
        );
    }
}
4

2 回答 2

2

您的问题的完整答案过于复杂,并且会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;
}

然后你可以使用combineLatestngAfterContentInitngAfterViewInit猜也很好):

ngAfterContentInit() {
  // ...
  combineLatest(
     bs1,
     bs2
  ).subscribe(result => {
     // do something with definition + view
     // result[0] => is your graph definition
     // result[1] => is your queried view
  });
  // ...
}

graphDefinition现在您可以在父组件中多次更改您的配置,您的配置将自动更新

于 2019-11-21T09:27:23.480 回答
2

我想出了这个,灵感来自Cristian Traìna的答案,但没有使用BehaviourSubjector combineLatest

render()函数将被调用两次。第一次只有一个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;
                }
            );
        }
    }
}
于 2019-11-21T10:25:20.513 回答