26

I am trying to use AngularJS two-way binding text which includes Latex style equations. I would like to call MathJax to format the equations, but I'm not sure of the best way to ensure that MathJax is called after AngularJS finishes changing the model. I think I need a callback. Here is my JavaScript:

var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
   $scope.Update = function() {
       $scope.Expression = 'Evaluate: \\( \\frac{9}{4} \\div \\frac{1}{6} \\)';
       MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
   }
   $scope.Expression = 'Evaluate: \\( \\frac{5}{4} \\div \\frac{1}{6} \\)';

}

And here is my HTML:

<div ng-controller="MyCtrl">
    <button ng-click="Update()">Update</button>
  {{Expression}}
</div>

Fiddle is here: http://jsfiddle.net/LukasHalim/UVjTD/1/. You'll notice that on the fiddle the original expression isn't removed even after you click the update button twice - seems like a bug or conflict.

Lukas Halim
  • 535
  • 1
  • 7
  • 12
  • Thank you for all of the help so far! Ideally, I'd like to be able to have this work without having to enclose each mathematical expression in its own span... I'd like to be able to render something like `Evaluate: \\( \\frac{5}{4} \\div \\frac{1}{6} \\)` without having to change it to `Evaluate: \\frac{5}{4} \\div \\frac{1}{6}.` – Lukas Halim May 20 '13 at 18:26
  • Note from the future: cdn.mathjax.org is nearing its end-of-life, check https://www.mathjax.org/cdn-shutting-down/ for migration tips. – Peter Krautzberger Apr 12 '17 at 08:58

10 Answers10

36

Having wasted many days (and maybe weeks) fighting MathJax, I'm all too familiar with its various quirks with updating math expressions on the fly. I'm brand new to Angular but this gave me a good chance to dive in and I ended up with a solution which solves my problems -- hopefully it'll solve yours as well.

Live demo: jsfiddle


Instead of using the plain interpolation that Angular provides, I created a new directive based on ng-bind called mathjax-bind.

If expression is a variable containing math code, then instead of \( {{expression}} \) you can write:

<span mathjax-bind="expression"></span>

and everything will be typeset and updated at the appropriate times.

The supporting code for the directive follows:

myApp.directive("mathjaxBind", function() {
    return {
        restrict: "A",
        controller: ["$scope", "$element", "$attrs",
                function($scope, $element, $attrs) {
            $scope.$watch($attrs.mathjaxBind, function(texExpression) {
                var texScript = angular.element("<script type='math/tex'>")
                    .html(texExpression ? texExpression :  "");
                $element.html("");
                $element.append(texScript);
                MathJax.Hub.Queue(["Reprocess", MathJax.Hub, $element[0]]);
            });
        }]
    };
});
Edgar
  • 6,022
  • 8
  • 33
  • 66
Sophie Alpert
  • 139,698
  • 36
  • 220
  • 238
  • 3
    If you prefer to use the `\( ... \)` delimiters instead of specifically inserting the math with a separate tag, don't despair! If you change the watch callback to use `$element.text(value == undefined ? "" : value);` and run `Typeset` instead of `Reprocess` in the `$watch` handler, then you'll be to have it typeset entire runs of text containing any number of math expressions. – Sophie Alpert May 20 '13 at 09:47
  • Wow! I'm trying to get this to work with \( ... \) but my fiddle must not be right - http://jsfiddle.net/LukasHalim/feXSh/ – Lukas Halim May 20 '13 at 15:08
  • 1
    Here you go: http://jsfiddle.net/spicyj/9UXFE/. (You forgot to make the first of my two suggested changes.) – Sophie Alpert May 20 '13 at 18:40
  • Thank you Ben! I didn't forget - I'm just too much of a n00b to figure out what exactly to change :-) – Lukas Halim May 21 '13 at 14:25
  • To prevent accidental name collision, Angular prefixes names of objects which could potentially collide with $. Please do not use the $ prefix in your code as it may accidentally collide with Angular code. http://docs.angularjs.org/guide/concepts#angular_namespace – heralight Jun 07 '13 at 09:49
  • This is great! I had some trouble because I was trying to make this work with MathML. To do so, change ` – sjagr Oct 02 '14 at 17:08
  • 3
    How can i do it if my "expression" has some html tags? How can i use ng-bind-html and mathjax bind? – maxtorzito Oct 27 '14 at 20:12
  • It works well. Thank you. I keep getting an error in console though: ReferenceError: MathJax is not defined. – Anshuman Biswas May 16 '15 at 20:20
  • It has some problem with \quad ? On using $({ \alpha }_{ copper }\quad >\quad { \alpha }_{ steel })$ \quad is not getting converted into space, and instead shows as 'quad' string – Saurabh Verma Jun 09 '15 at 05:27
  • This seems to stop working in AngularJs 1.4.x, does anyone have a suggestion how to get it working again? – lanoxx Jan 28 '16 at 13:16
13

Simplest, fastest and most stable solution:

$rootScope.$watch(function(){
  MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
  return true;
});

Advantages:

  • Easy to setup, just copy this code.
  • Everything on your page is typeset.
  • It renders much faster than the other solutions. This is because it can render the page in one go. Other answers here wait for one item to finish, until they typeset the next one. That makes rendering veeeery slow if there are for example multiple mathjax-bind directives (as another answer suggests). This point is the reason I was looking for a different answer.
  • You can still easily exclude elements using the option “ignoreClass” in your mathjax settings.

Benchmarking: 100 mathjax-bind directives took 63 seconds, while with this method it took 1.5 second to render the page. I know that this function will be executed a lot since it's called on every digest cycle, however, it doesn't noticeably slow down the page.

7

I created a simple fiddle expanding on Ben Alpert's answer. Here's the fiddle and plunk.

Specifically If a text has only a part of it to be converted by Mathjax, you can use this. For inline mathjax you must surround the text by $, and for block display you must surround the block by $$. (You can use any format you like if you create the corresponding regex)

app.js

MathJax.Hub.Config({
    skipStartupTypeset: true,
    messageStyle: "none",
    "HTML-CSS": {
        showMathMenu: false
    }
});
MathJax.Hub.Configured();
var myApp = angular.module("myApp", []);
myApp.directive("mathjaxBind", function() {
    return {
        restrict: "A",
        scope:{
            text: "@mathjaxBind"
        },
        controller: ["$scope", "$element", "$attrs", function($scope, $element, $attrs) {
            $scope.$watch('text', function(value) {
                var $script = angular.element("<script type='math/tex'>")
                    .html(value == undefined ? "" : value);
                $element.html("");
                $element.append($script);
                MathJax.Hub.Queue(["Reprocess", MathJax.Hub, $element[0]]);
            });
        }]
    };
});
myApp.directive('dynamic', function ($compile) {
  return {
    restrict: 'A',
    replace: true,
    link: function (scope, ele, attrs) {
      scope.$watch(attrs.dynamic, function(html) {
          html = html.replace(/\$\$([^$]+)\$\$/g, "<span class=\"blue\" mathjax-bind=\"$1\"></span>");
          html = html.replace(/\$([^$]+)\$/g, "<span class=\"red\" mathjax-bind=\"$1\"></span>");
        ele.html(html);
        $compile(ele.contents())(scope);
      });
    }
  };
});
function MyCtrl($scope, $element) {    
    $scope.html = "A coin of is $ \\frac{5}{4} $ thrown $$\\frac{1}{6}$$ dfv";
}

index.html

<!DOCTYPE html>
<html ng-app="myApp">    
  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML&delayStartupUntil=configured&dummy=.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.7/angular.js" data-semver="1.2.7"></script>
    <script src="app.js"></script>
  </head>
  <body>
  <div ng-controller="MyCtrl">
     <input type="text" ng-model="html"/><br/>
     <div dynamic="html"></div>
  </div>
</body>    

style.css

input[type="text"] {
    width: 800px;
}
.red{
    color:red;
    display:inline-block;
}
.blue{
    color:blue;
    display:block;
}
Roney Island
  • 179
  • 2
  • 5
2

Take a look at http://jsfiddle.net/pz5Jc/

In your template:

    {{Label}} <span id="mathElement">{{Expression}}</span>

In your controller:

$scope.Update = function() {
    $scope.Expression = '\\frac{9}{4} \\div \\frac{1}{6}';
    $scope.Label = 'Updated Expression:'
    var math = MathJax.Hub.getAllJax("mathElement")[0];
    math.Text('\\frac{4}{4} \\div \\frac{2}{6}');
}

Couple of points:

I'm not too familiar with mathjax, but:

  • Splitting the label out from the expression allows you to work with the expression directly.
  • You need to manually pick up a DOM element to force a refresh of the expression. This isn't a very 'angular' way to do things unfortunately - but when mathjax parses the expression (and inserts it's own DOM elements), it pushes those elements outside the angular bindings.
  • Fix here is to specifically select the correct mathjax element and call a text change function to update the expression.
Alex Osborn
  • 9,831
  • 3
  • 33
  • 44
2

Here's a directive that lets you use double curly markup inside the expression (and doesn't require setting an expression variable on the scope). It's based on this blog post, except I only support MathJax, and I save the compiled DOM, so that it updates on changes to scope variables.

As Alex Osborn said, it's best to separate non-math from math.

Usage:

<p>This is inline math: <latex>x^{ {{power}} }</latex>, 
and this is display math: <div latex> y^{ {{power}} } .</div></p>

In a snippet:

angular.module('app', [])
  .controller('ctrl', function($scope) {
    $scope.power = "\\sin(x^2)";
  })
  .directive('latex', function() {
    return {
      restrict: 'AE',
      link: function(scope, element) {
        var newDom = element.clone();
        element.replaceWith(newDom);
        var pre = "\\(",
          post = "\\)";
        if (element[0].tagName === 'DIV') {
          pre = "\\[";
          post = "\\]";
        }
        scope.$watch(function() {
          return element.html();
        }, function() {
          console.log(element);
          newDom.html(pre + element.html() + post);
          MathJax.Hub.Typeset(newDom[0]);
        });
      }
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>

<div ng-app="app" ng-controller="ctrl">
  <p>Power:
    <input ng-model="power" />
  </p>
  <p>This is the inline latex,
    <latex>x^{ {{power}} }</latex>, followed by some display mode latex
    <div latex>y^{ {{power}} } = {{power}}.</div>And that's it!
  </p>
</div>
jladan
  • 807
  • 7
  • 6
2

A simple solution is to use $timeout to put MathJax.Hub.Queue(["Typeset", MathJax.Hub]) in the browser event queue (see Run a directive after the DOM has finished rendering).

Something like this:

            var app = angular.module('myApp', []);
            app.controller('myController', function ($scope, $timeout) {
                controller = this;

                $scope.Update = function () {
                    $scope.value = " \\( \\frac{5}{4} \\div \\frac{1}{6} \\)";
                    $timeout(controller.updateMathJax, 0);
                }

                this.updateMathJax = function () {
                    MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
                }
            });
1

You can try with my modifications http://jsfiddle.net/bmma8/4/ modify input or click on button will update your expression.

js:

MathJax.Hub.Config({
    extensions: ["tex2jax.js"],
    jax: ["input/TeX","output/HTML-CSS"],
    tex2jax: {inlineMath: [["$","$"],["\\(","\\)"]]}
});

var myApp = angular.module('myApp',[]);


function MyCtrl($scope, $log) {

    var QUEUE = MathJax.Hub.queue;  // shorthand for the queue

    $scope.Update = function() {
        QUEUE.Push(["Text",MathJax.Hub.getAllJax("MathOutput")[0],"\\displaystyle{"+ $scope.Expression+"}"]);
        //$scope.Expression = 'Updated Expression: \\( \\frac{9}{4} \\div \\frac{1}{6} \\)';
        //MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
    }
    $scope.Expression = 'Original Expression: \\( \\frac{5}{4} \\div \\fra

and html:

 <div ng-controller="MyCtrl">
         <button ng-click="Update()">Update</button>

         <input ng-model="Expression" ng-change="Update()">
         <div id="MathOutput">
         You typed: ${}$
         </div>
 </div>

Alexandre

heralight
  • 920
  • 1
  • 7
  • 15
0

I actually thought of another solution. When you render some angular and math you do this:

ANGULAR CONTROLLER

$scope x = 5;

HTML

<h3> {{ '$ Multiplication = '+ x + ' * 2 =' + (x*2) + '$'}} </h3>

Formated Math Jax result

Multiplication = 5 * 2 = 10

The key is to include the dollar signs inside the brackets as text. When Angular renders them, the dollar signs will appear as plain text, but when the Math Jax format comes into action it will recognize the dollar signs and do the magic.

Abe
  • 757
  • 6
  • 15
0

I Build a directive for this....

FIDDLE: http://jsfiddle.net/8YkUS/1/

HTML

p data-math-exp data-value="math">

JAVASCRIPT

 appFlipped.directive("mathExp", function () {
    return {
        scope: {
            value: "="
        },
        link: function (scope, el) {

            var domEl = el[0];
            scope.$watch("value", function (newValue) {

                //nothing to do here
                if (newValue == null || window.MathJax == null)return;

                //update the dom with the new value and pass the hub for styling the equation
                domEl.innerHTML = '`' + newValue + '`';
                MathJax.Hub.Queue(["Typeset", MathJax.Hub, domEl]);
            });
        }
    }
});
Jose Ch.
  • 3,856
  • 1
  • 20
  • 34
0

I fiddled a bit more on Roney's solution. The display math should be displayed in display mode; with

<script type="math/tex; mode=display">

I added an attribute to the generated span to indicate that.

Fiddle is here http://jsfiddle.net/repa/aheujhfq/8/

user3021380
  • 146
  • 3