javascriptangularsyntaxhighlighter

How to use SyntaxHighlighter on dynamically loaded code blocks with Angular 6?


I am trying to add SyntaxHighlighter to my angular blog project. My articles have <pre> formatted code snippets and is loaded from database. This article HTML is then displayed in ArticleComponent template. This ArticleComponent itself is loaded when a particular route is triggered. But when I browse to this route and an article is loaded, the <pre> formatted codes are not highlighted at all. However, if I refresh the page, highlighting works well. Not sure where I am going wrong.

index.html

<head>
    <meta charset="utf-8">
    <title>My Blog</title>
    <base href="/">

    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">

    <link rel="stylesheet" href="./assets/css/foundation.css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css" integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
    <link href="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" rel="stylesheet" type="text/css" />
    <link href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <app-root></app-root>

    <script type="text/javascript" src="./assets/js/jquery.js"></script>
    <script type="text/javascript" src="./assets/js/what-input.js"></script>
    <script type="text/javascript" src="./assets/js/foundation.js"></script>
    <script>
        $(document).foundation();
    </script>
    <script src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js" type="text/javascript"></script>
    <script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushBash.js" type="text/javascript" ></script>
    <script type="text/javascript">
        SyntaxHighlighter.defaults['toolbar'] = false;
        SyntaxHighlighter.all()
   </script>
</body>

article.component.html

<div class="cell">
    <span [innerHTML]="article"></span>
</div>

article.component.ts

export class ArticleComponent implements OnInit {

    public article = '';
    public title = '';

    constructor(private _route: ActivatedRoute, private _articleService: ArticleService) {
    }

    ngOnInit() {
        const articleId: string = this._route.snapshot.paramMap.get('id');

        const allArticles = this._articleService.getArticles();
        const selectedArticleMetaData = allArticles.find(x => x.id === parseInt(articleId, 10));
        this.title = selectedArticleMetaData.title;

        this._articleService
            .getArticleHtml(selectedArticleMetaData.id)
            .subscribe((article: string) => {
                this.article = article;
            });
    }

}

I checked the HTML DOM elements and I am able to verify that the article that is loaded without refresh has all its <pre> blocks as raw code. But when SyntaxHighlighter is actually working in case of refresh, these <pre> tags are all converted to div elements with proper style classes.

Looks like when when articles are loaded through binding, SyntaxHighlighter is unable to process the <pre> tags. Any idea how to fix this ? Also any idea why it works when I refresh this page ? In either case, the article is loaded through binding.


Solution

  • Solved it. Answering it here as it might be helpful to some one else.

    SyntaxHighlighter.all() method registers a Highlight method to windows.load event. Hence this method is only triggered on page load and never later. This solves the mystery of why highlighting worked during page refresh and not when angular routing is triggered.

    The fix is to trigger a window.load event after article is loaded in the DOM. There are better ways to do this, but here is the most straight forward way to do this. Update article.component.ts to include the code to trigger window.load event.

    this._articleService
        .getArticleHtml(selectedArticleMetaData.id)
        .subscribe((article: string) => {
            this.article = article;
            this.loading = false;
    
            // trigger window.load event after 500 ms of binding the article property
            setTimeout(() => {
                window.dispatchEvent(new Event('load'));
            }, 500);
        });