Angular 6 中的 MathJax?

MathJax in Angular 6?

不幸的是,关于这个库的信息很少。安装后我不完全清楚我需要将什么导入 app.module.ts 以及那里是否有要导入的东西?我在index.html中规定了以下代码:

<script type="text/x-mathjax-config">
MathJax.Hub.Config({
  tex2jax: {
    inlineMath: [['$','$'], ['\(','\)']]
  }
});
</script>
<script type="text/javascript" async
 src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js? 
 config=TeX-MML-AM_CHTML'>
</script>

如果我没有简单的文本,而是 table 在某些列中出现带有公式的文本,我该如何应用 MathJax?也许你可以以某种方式将整个 table 转移到 MathJax.Hub.Queue?

两周前我也在看同样的问题,今天我终于设法解决了。我不是 angular 专家,所以它可能需要一些优化,但核心功能正在运行。

第一步

@types/mathjax 依赖项添加到您的 package.json 文件。

第二步

像这样将新添加的类型添加到 tsconfig.app.json

{
  "compilerOptions": {
    "types": ["mathjax"]
  }
}

这将允许您使用 MathJax.Callback.Queue 等结构,并且您的 IDE 不会抱怨未知类型等。

第三步为您的数学内容创建包装器对象(可选)

我在使用 Mathml 时遇到了一些问题,所以我创建了数学包装器,如下所示:

export interface MathContent {
  latex?: string;
  mathml?: string;
}

第四步

现在我们需要定义模块,它将配置注入 MathJax 脚本标签。因为它将动态加载 async 我们需要确保在 MathJax 完全加载之前不会开始排版。最简单的方法是将 Observer<any> 存储到 window 对象中。 Observer 和渲染功能可以包装到服务中。

// see 
declare global {
  interface Window {
    hubReady: Observer<boolean>;
  }
}

@Injectable()
export class MathServiceImpl {
  private readonly notifier: ReplaySubject<boolean>;

  constructor() {
    this.notifier = new ReplaySubject<boolean>();
    window.hubReady = this.notifier; // as said, bind to window object
  }

  ready(): Observable<boolean> {
    return this.notifier;
  }

  render(element: HTMLElement, math?: MathContent): void {
    if (math) {
      if (math.latex) {
        element.innerText = math.latex;
      } else {
        element.innerHTML = math.mathml;
      }
    }

    MathJax.Hub.Queue(['Typeset', MathJax.Hub, element]);
  }
}

第五步

现在我们将创建指令,一旦加载 MathJax 就会触发渲染。该指令可能如下所示:

@Directive({
  selector: '[appMath]'
})
export class MathDirective implements OnInit, OnChanges, OnDestroy {
  private alive$ = new Subject<boolean>();

  @Input()
  private appMath: MathContent;
  private readonly _el: HTMLElement;

  constructor(private service: MathServiceImpl,
              private el: ElementRef) {
    this._el = el.nativeElement as HTMLElement;
  }

  ngOnInit(): void {
    this.service
      .ready()
      .pipe(
        take(1),
        takeUntil(this.alive$)
      ).subscribe(res => {
        this.service.render(this._el, this.appMath);
    });
  }

  ngOnChanges(changes: SimpleChanges): void {
    console.log(changes);
  }

  ngOnDestroy(): void {
    this.alive$.next(false);
  }
}

第六步(差不多了)

在第四步中我提到了 async 加载。根据 MathJax 上的文档,这是使用 document.createElement 完成的。 Angular 模块是这个逻辑的完美场所。要在我们的 MathService 上触发 .ready() 方法,我们将使用 MathJax.Hub.Register.StartupHook 并从 MathService 传递 observable 所以我们的模块将如下所示:


@NgModule({
  declarations: [MathDirective],
  exports: [MathDirective]
})
export class MathModule {
  constructor(mathService: MathServiceImpl) {
    // see https://docs.mathjax.org/en/latest/advanced/dynamic.html
    const script = document.createElement('script') as HTMLScriptElement;
    script.type = 'text/javascript';
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML';
    script.async = true;

    document.getElementsByTagName('head')[0].appendChild(script);

    const config = document.createElement('script') as HTMLScriptElement;
    config.type = 'text/x-mathjax-config';
    // register notifier to StartupHook and trigger .next() for all subscribers
    config.text = `
    MathJax.Hub.Config({
        skipStartupTypeset: true,
        tex2jax: { inlineMath: [["$", "$"]],displayMath:[["$$", "$$"]] }
      });
      MathJax.Hub.Register.StartupHook('End', () => {
        window.hubReady.next();
        window.hubReady.complete();
      });
    `;

    document.getElementsByTagName('head')[0].appendChild(config);
  }

  // this is needed so service constructor which will bind
  // notifier to window object before module constructor is called
  public static forRoot(): ModuleWithProviders {
    return {
      ngModule: MathModule,
      providers: [{provide: MathServiceImpl, useClass: MathServiceImpl}]
    };
  }
}

第七步:渲染数学

现在一切就绪,只需在要渲染数学的模块中导入 MathModule.forRoot() 即可。该组件将如下所示:

export class AppComponent {
  mathLatex: MathContent = {
    latex: 'When $a \ne 0$, there are two solutions to $\frac{5}{9}$'
  };

  mathMl: MathContent = {
    mathml: `<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mrow>
    <mover>
      <munder>
        <mo>∫</mo>
        <mn>0</mn>
      </munder>
      <mi>∞</mi>
    </mover>
    <mtext> versus </mtext>
    <munderover>
      <mo>∫</mo>
      <mn>0</mn>
      <mi>∞</mi>
    </munderover>
  </mrow>
</math>`
  };
}

和模板

<div [appMath]="mathLatex"></div>

<div [appMath]="mathMl"></div>

<!-- will render inline element math -->
<div [appMath]>
  $E = mc^2$
</div>

哪个应该呈现给这个

第八步(奖金)

这是工作中的 stackblitz 示例 https://stackblitz.com/edit/mathjax-example 因此您可以检查您的实施进度