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?
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.
Add @types/mathjax
dependency to your package.json
file.
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.
I had some issues with Mathml so i have created wrapper for math which looks like this :
export interface MathContent {
latex?: string;
mathml?: string;
}
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]);
}
}
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);
}
}
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}]
};
}
}
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
Here is working stackblitz example https://stackblitz.com/edit/mathjax-example so you can check your progress against implementation
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With