Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Syntax highlighting in Angular 2

I'm creating simple web app using angular 2. I have two components there. First is basically table with some data rows. When click on row is performed, data corresponding to row are displayed in the second component. Data are XML, loaded to code element. It looks something like

@Component
export class TableComponent {
    items: Data[];
    selectedItemsXml: string;
    // ...other stuff

    //when row is clicked
    toggleSelectedRow(rowIndex: number) {
        // ...other stuff related to change row's background color
        this.selectedItemsXml = this.items[i].xml;
    }
    // ...other stuff again
}

//TableComponent's template
<div>
    <table>
        ...
        ...*ngFor="let item of items; let i = index;"...
        <tr (click)="toggleSelectedRow(i)">
            <td>{{item.id}}</td>
            <td>{{item.time}}</td>
        </tr>
        ...
    </table>
</div>
<xml-detail [xml]="selectedItemsXml"></xml-detail>

@Component
export class XmlDetailComponent {
    @Input() xml: string;
}

//XmlDetailComponent's template
<div>
    <pre><code>{{xml}}</code></pre>
</div>

Everything worked fine until I wanted to add syntax highlighting for xml. First I wanted to use plugin ng2-prism, but I had problems to make it work correctly. After I saw issues in its git repo, I threw it away. What I tried next was to create directive using highlight.js based on this post: highlight.js does not work with Angular 2. Xml is highlighted using this method, but only the first time row is clicked. When another row is clicked, new xml is not even displayed. I also tried to use prism.js but I'm getting the same behavior. Once some xml string is first time binded, displayed in code element and highlighted using either highlight.js or prism.js, it remains.

My guess is it is related with how DOM and data binding works in angular 2, because without using syntax highlighting, I'm binding and passing string to code element every time row is selected. Using highlighting causes to bind string, pass it to code element and then pretiffy it. That means there is no simple string inside code element, but a lot of styled span elements, what causes problems to load new xml string when new row is selected. I also tried to bind "pre-prettified" string using Prism.highlight(text_to_prettify), but using this method causes to display xml with all of the span elements added by Prism.

Right now, I'm scratching my head thinking about how to solve this problem. Please give me some push how could I make it work.

like image 556
Another Noob Avatar asked Dec 24 '22 19:12

Another Noob


2 Answers

I would create a component that leverages prismjs.

Include the prismjs scripts including any from the components folder that you need.

Index.html

<link href="node_modules/prismjs/themes/prism.css" rel="stylesheet" />
<script src="node_modules/prismjs/prism.js"></script>
<script src="node_modules/prismjs/components/prism-core.js"></script>
<script src="node_modules/prismjs/components/prism-clike.js"></script>
<script src="node_modules/prismjs/components/prism-csharp.js"></script>

Install the latest type definition files for Prism (this will give you type-safety):

npm install @ryancavanaugh/prismjs

Note: don't install @types/prismjs - its either out-of-date or not authored correctly. The author of prismjs recommends @ryancvanaugh/prismjs for the type definitions.

Add the folder to your typeRoots list property in tsconfig.json so that the typescript compiler can find it:

tsconfig.json (under compilerOptions)

"typeRoots": [
  "node_modules/@types",
  "node_modules/@ryancavanaugh"
]

Create a custom prism component that calls Prism.highlight:

prism.component.ts

/// <reference path="../node_modules/@ryancavanaugh/prismjs/prism.d.ts" />
import { 
    Component, 
    AfterViewInit, 
    Input, 
    ElementRef, 
    ViewChild 
} from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'prism',
    template: `
        <div hidden="true" #rawContent>
            <ng-content></ng-content>
        </div>
        <pre>
            <code [innerHTML]="content">
            </code>
        </pre>

    `
})
export class PrismComponent implements AfterViewInit {
    @Input() language: string;
    @ViewChild("rawContent") rawContent: ElementRef;
    content: string;

    constructor(private elementRef:ElementRef) {
     }

    ngAfterViewInit() {
        this.content = Prism.highlight(this.rawContent.nativeElement.innerHTML, Prism.languages[this.language]);
     }
}

Declare the Prism component in app.module:

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule  } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { PrismComponent } from './prism.component';

@NgModule({
    imports: [
        BrowserModule,
        HttpModule        
        ],
    declarations: [AppComponent, PrismComponent],
    providers: [/* TODO: Providers go here */],
    bootstrap: [AppComponent],
})
export class AppModule { }

Use it like this:

<prism language="csharp">
    var x = 3;
    class T
    {{ '{' }}
    {{ '}' }}
</prism>
like image 143
pixelbits Avatar answered Dec 28 '22 11:12

pixelbits


You can try this simple angular-prism component I wrote for Angular 2/4 since ng2-prism is no longer maintained and does not work for latest angular releases.

like image 31
Vaibhav Bansal Avatar answered Dec 28 '22 10:12

Vaibhav Bansal