How to render MathML in browser using Angular and MathJax 3 along with lazy loading?

Madhusuthanan B
Kongsberg Digital
Published in
7 min readSep 25, 2020
Photo by ThisisEngineering RAEng on Unsplash

If you have some MathML expressions that you are trying to render beautifully in browser using angular, then you are at the right place!

In this article, we will see how we can render mathml expressions in a browser using Angular 9 and MathJax 3.

Note: I will be using images to explain the concepts step by step. You can always refer to the complete code from my git repository here

Code Time!

1. Define the module structure

Let’s start by creating a module in our angular application, which will contain a directive and service. I will call this a MathModule. The folder structure of our module will look like this

Math Module Structure

The initial content of module, service and directive will look like below

Initial module structure
Initial directive structure
Initial service structure

2. Setting up a strategy to load MathJax script.

We will consider the below steps/ options given below:

  • We will load the script from a CDN asynchronously.
  • Sub Resource Integrity consideration for security and fallback strategy.
  • We will notify our directive once the script has finished loading.

Loading the script from CDN

Let’s copy the details of mml-chtml.js from this CDN including SRI (Sub Resoure Integrity)

Add the below interface for Mathjax config in math.service itself.

MathJaxConfig interface

Let’s update our math.service in order to support loading the script from CDN asynchronously and to send a signal after loading. After performing the above-mentioned steps, it will look like the following code section:

Whats going on here?

  1. registerMathjaxAsync() → This method registers the mathjax script based on provided config.
  2. It returns a promise based on the result.
  3. We have setup a signal in this service. Once the script registration is successful, we will emit the signal by invoking next().
  4. ready() → This method simply exposes our signal.
  5. We will add a fallback strategy when the script registration fails. We will come back and implement this later.

3. Handling global MathJax in typescript

Let’s add the below interface in our math service. Since MathJax will be present in global window object, this is to setup a little intellisense around what we are interested in.

4. Method to update DOM

Let’s add a render() method which will take care of updating the DOM and typesetting (A way of converting string containing mathematics into another form).

render method for DOM update
  • In this method we take initial typesetting which MathJax performs into account by using MathJax.startup.promise
  • MathJax.typesetPromise() → This method is used to typeset dynamic pages asynchronously.
  • You can find very detailed information about typesetting concepts here.

5. Directive implementation

Now, we need to perform the following steps:

  • We need to connect our directive and service in such a way that it listens to the signal from math service and invokes render() method.
  • We will take the MathMl expression as a string input via directive.
  • Also we will include ngOnChanges() lifecycle hook to handle change detection.

Considering all these, Let’s update our directive as shown in the image below:

Directive update

That’s all the changes we need w.r.t our math module. Now, it’s time to hook up our directive with a component and see how it works.

6. Using math module

Import math module in app module

Import math module in app module

We will use the AppComponent for demonstrating our math module. Let’s update our app component shown here:

App Component HTML
App Component

Please consider the following points after performing the above-mentioned steps:

  • We are using our appMath directive in the html
  • We have initialised mathml variable to empty string. And we are purposefully using ngIf along with appMath directive in this demo, to show that mathjax script is not loaded from cdn when the page loads.
  • It gets loaded lazily when the directive is rendered. That is, when you enter a mathjax expression in text area.
  • Please use the network tab in your developer tool to check this lazy loading feature.

7. Running the application

On running the application and putting a mathml expression in text area will result into a beautiful rendering of math.

For example, we can put the below mathml code in text area to see the output.

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
<msup>
<mrow data-mjx-texclass="INNER">
<mo data-mjx-texclass="OPEN">(</mo>
<munderover>
<mo data-mjx-texclass="OP">&#x2211;</mo>
<mrow>
<mi>k</mi>
<mo>=</mo>
<mn>1</mn>
</mrow>
<mi>n</mi>
</munderover>
<msub>
<mi>a</mi>
<mi>k</mi>
</msub>
<msub>
<mi>b</mi>
<mi>k</mi>
</msub>
<mo data-mjx-texclass="CLOSE">)</mo>
</mrow>
<mrow>
<mstyle scriptlevel="0">
<mspace width="negativethinmathspace"></mspace>
</mstyle>
<mstyle scriptlevel="0">
<mspace width="negativethinmathspace"></mspace>
</mstyle>
<mn>2</mn>
</mrow>
</msup>
<mo>&#x2264;</mo>
<mrow data-mjx-texclass="INNER">
<mo data-mjx-texclass="OPEN">(</mo>
<munderover>
<mo data-mjx-texclass="OP">&#x2211;</mo>
<mrow>
<mi>k</mi>
<mo>=</mo>
<mn>1</mn>
</mrow>
<mi>n</mi>
</munderover>
<msubsup>
<mi>a</mi>
<mi>k</mi>
<mn>2</mn>
</msubsup>
<mo data-mjx-texclass="CLOSE">)</mo>
</mrow>
<mrow data-mjx-texclass="INNER">
<mo data-mjx-texclass="OPEN">(</mo>
<munderover>
<mo data-mjx-texclass="OP">&#x2211;</mo>
<mrow>
<mi>k</mi>
<mo>=</mo>
<mn>1</mn>
</mrow>
<mi>n</mi>
</munderover>
<msubsup>
<mi>b</mi>
<mi>k</mi>
<mn>2</mn>
</msubsup>
<mo data-mjx-texclass="CLOSE">)</mo>
</mrow>
</math>

The output of parsing this expression will look something like this:

Math ml rendered in browser
MathML output

This should be good enough for us to render MathML in browser!

8. Sub Resource Integrity and Implementing a fallback strategy

Imagine a scenario where the CDN from which you are loading the script has been compromised and the attacker has managed to update the script with some malicious code. How can we prevent loading an altered script? This is exactly where SRI (Sub Resource Integrity) comes into the picture.

What is Sub Resource Integrity?

It is a security feature that allows our browser to find out if the file being loaded from an external source has been compromised. This is why the integrity hash is of utmost importance while loading scripts from any CDN.

Note: The integrity key is generated by hashing the content of a file. So if this file is altered by an attacker, the hash will not match and the browser will prevent the file from loading.

How to implement a fallback strategy for our math module?

  • We will maintain a local copy of the same file (Under assets)
  • When an error occurs while loading script from CDN, the we will load the script from our assets.

Let’s copy paste the script file from CDN into a local file under assets/mathjax.

Note: Do not update the content of the copied file

After this, let’s create a fallback config for Mathjax in math.service. In this configuration, we will point to the javascript file in our local. However; the integrity key will be the same.

Since we have a fallback configuration now, let’s update the code within the constructor to implement fallback mechanism. As you can see in the below snippet, when an error occurs while registering the mathjax script from CDN, we will try to load the fallback script from our server.

In this way, we can prevent loading the script from CDN when there is a SRI failure and continue using the script from our server.

You can verify this scenario by changing the integrity key of the CDN mathjax configuration. If we change the key, then the integrity check will fail and we can see the below error.

SRI error

However; you can still see that the application will render math properly due to our fallback strategy implementation.

With this, we are able to successfully render MathMl in browser beautifully with the help of MathJax!

Summary

Now, you will see that the module we have built has the following features:

  • MathMl rendering capability
  • Lazy loadable
  • Sub Resource Integrity Consideration for Security
  • Fallback Strategy when there is an issue in loading script from CDN

I prefer to put out blogs on use cases which I find absolutely interesting. And this was one such use case that I found very interesting to work with. I have tried my best to demonstrate things in-depth here. Please feel free to provide your feedback and suggestions.

You can see the demo of this module here.

--

--

Madhusuthanan B
Kongsberg Digital

Front end heavy full-stack developer. Proficient in Angular, TypeScript, JavaScript, D3.js, .Net Core, C#. Data visualization and BDD enthusiast!