4

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.

Yashwardhan Pauranik
  • 5,370
  • 5
  • 42
  • 65
Another Noob
  • 69
  • 1
  • 6

3 Answers3

5

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>
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Michael Kang
  • 52,003
  • 16
  • 103
  • 135
  • I was able to get things working with a combination of this answer and the repo provided by @Vaibhav below. Thanks to all! – Jaime Still Jul 06 '17 at 15:37
  • 1
    I'm getting an error: `ERROR ReferenceError: Prism is not defined` while trying to call `this.content = Prism.highlight(this.rawContent.nativeElement.innerHTML, Prism.languages[this.language]);` on my prism component :( – Gerardo Tarragona Jun 20 '18 at 23:02
5

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.

Vaibhav Bansal
  • 346
  • 1
  • 8
  • angular-prism worked well for me. I spent ages making my own and this is much better. – rjdkolb Jul 29 '17 at 14:42
  • Thank you for this! Using what you made and the original prism source, I wrote a version that works natively in typescript / Angular 2/4 without having to import any separate, vanilla javascript libraries. Its just a service, a component, and whatever CSS for the syntax theme, packaged into a NgModule. I've never contributed anything to Github before so I'm pretty clueless on how this works. Should I create an entirely new repo and upload it as a separate thing? Or should I contribute it to yours and see if you can incorporate it? – diopside Aug 23 '17 at 02:40
  • @diopside You can use one of the Yeoman generators available for creating Angular library packages. Once it works, push the code to a git repo and you can also publish your module on npm. You will easily find guides on the internet for all these steps – Vaibhav Bansal Nov 18 '17 at 07:19
0

I use PixelBits solution, with few changes, first make sure you don't try the prism component in the App.component.ts file, you would get a "View changed after it has been checked" error and ngAfterViewInit() would not be called if you reload the page. So make a basic routing, and a page and trys your prism component inside that new page. Next if you want to use line-number plugin, Prism.highlight won't work. You need to use Prism.highlightElement. Also with PixelBits solution you get empty space before and after your code. Don't forget to change reference path url so it match your project.

ts:

export class PrismComponent implements OnInit,AfterViewInit {
    @Input() language: string;
    @ViewChild("rawContent") rawContent: ElementRef;
    @ViewChild("codeRef") codeRef: ElementRef;
    content: string;
    myClass:string;

    constructor(
        private elementRef:ElementRef,
    ) {
    }

    ngOnInit(){
        this.myClass = "language-" + this.language;
    }

    ngAfterViewInit() {
        // trim because ng-content add space
        this.content = this.rawContent.nativeElement.innerHTML.trim();
        // make line number plugin work.
        Prism.highlightElement(this.codeRef.nativeElement,true);
    }
}

and html:

<pre class="line-numbers"><code #codeRef [innerHTML]="content" [class]="myClass"></code></pre>
<div style="display: none;" hidden="true" #rawContent>
  <ng-content></ng-content>
</div>

Also i don't use npm to get prism.js, but i took it from the official prism website (plugin for line-number is integrated inside prism.js).

Ambroise Rabier
  • 3,636
  • 3
  • 27
  • 38