MathJax in Angular 6?

2020-05-19 03:37发布

问题:

Unfortunately, very little information on this library. It is not completely clear to me after installation what I need to import into the app.module.ts and whether there is something to import there? I have prescribed the following code in the 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>

And how can I apply the MathJax, if I have not simple text, but a table in which text with formulas appears in some columns? Perhaps you can somehow transfer the entire table to MathJax.Hub.Queue?

回答1:

I was looking at the same problem like two weeks ago and today i finally manage to make it work. I'm no angular expert so it may need some optimization, but core funcionality is working.

Step one

Add @types/mathjax dependency to your package.json file.

Step two

Add newly added type to tsconfig.app.json like this:

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

This will allow you to use constructs such as MathJax.Callback.Queue and your IDE won't complain about unkown type etc.

Step three create wrapper object for your math content (optional)

I had some issues with Mathml so i have created wrapper for math which looks like this :

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

Step four

Now we need to define module which whill inject MathJax script tag with configuration. Because it will be loaded dynamically async we need to make sure typesetting doesn't start before MathJax is fully loaded. Easiest way is to store Observer<any> into window object. Observer and render functionality can be wrapped into service.

// see https://stackoverflow.com/a/12709880/1203690
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]);
  }
}

Step five

Now we will create directive which will trigger rendering once MathJax is loaded. The directive may look like this:

@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);
  }
}

Step six (almost there)

In step four i mentioned async loading. According to docs onMathJax this is done using document.createElement. Angular Module is perfect place for this logic. To trigger .ready() method on our MathService we will use MathJax.Hub.Register.StartupHook and pass observable from MathService So our module will look like this:


@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}]
    };
  }
}

Step seven: render math

Now everything is ready simply import MathModule.forRoot() in module where you want to render math. The component will look like this:

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>`
  };
}

and template

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

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

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

Which should render to this

Step eight (bonus)

Here is working stackblitz example https://stackblitz.com/edit/mathjax-example so you can check your progress against implementation



回答2:

You can also look here for nice implementation of Mathjax with Angular 6:

https://github.com/shubhvjain/angular-mathjax