1

I'm quite new to Angular and I struggle with following problem.

I'd like to render the content of my component templates within the DOM. The goal is to produce a styleguide where I want to show code-snippets of the templates. Therefor I need the escaped template-content.

I've tried several things and came up with a possible solution to create a custom renderer which escapes the template output.

I thought it could be as easy as described here: Rendering in Angular But... it wasn't.

I created as described here Stack Overflow - custom-renderer-for-angular2 a custom renderer, which should first of all function like the default DebugDomRenderer. But I do not know how to use this renderer.

I tried things like:

import {Component} from '@angular/core';
import {EscapeRootRenderer} from "../../renderer/escapeRootRenderer";
import {DebugDomRootRenderer} from "@angular/core/src/debug/debug_renderer";

@Component({
  selector:'escape',
  template:'<pre><code><ng-content></ng-content></code></pre>',
  providers:[
    {provide: DebugDomRootRenderer, useClass:EscapeRootRenderer}
  ]
})

export class EscapeComponent{

}

This part is not ready yet. My plan is to take the ChildViews and render them escaped...

My EscapeRootRender does exactly the same thing as the DebugDomRootRenderer except that I removed the debug stuff and added some console.logs()

import {Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';

import {AnimationStyles} from "@angular/core/esm/src/animation/animation_styles";
import {AnimationKeyframe} from "@angular/core/esm/src/animation/animation_keyframe";


@Injectable()
export class EscapeRootRenderer implements RootRenderer {

  constructor(private _delegate:RootRenderer) {
    console.log('EscapeRootRenderer constructed')
    console.log(_delegate)
  }


  renderComponent(componentProto: RenderComponentType): EscapeRenderer {
    console.log('EscapeRootRenderer - renderComponent');
    return new EscapeRenderer(this._delegate.renderComponent(componentProto));
  }

}

export class EscapeRenderer implements Renderer{

  constructor(  private _delegate: Renderer){
    console.log('EscapeRenderer constructed');
  }

  animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): any {
    console.log('EscapeRenderer animate');
    return this._delegate.animate(element, startingStyles, keyframes, duration, delay, easing);
  }

  selectRootElement(selector: string): any {
    console.log('EscapeRenderer selectRootElement');
    var nativeEl = this._delegate.selectRootElement(selector);
    return nativeEl;
  }

  createElement(parentElement: any, name: string): any {
    console.log('EscapeRenderer createElement');
    var nativeEl = this._delegate.createElement(parentElement, name);
    return nativeEl;
  }

  createViewRoot(hostElement: any): any {
    console.log('EscapeRenderer createViewRoot');
    return this._delegate.createViewRoot(hostElement); }

  createTemplateAnchor(parentElement: any): any {
    console.log('EscapeRenderer createTemplateAnchor');
    var comment = this._delegate.createTemplateAnchor(parentElement);
    return comment;
  }

  createText(parentElement: any, value: string): any {
    console.log('EscapeRenderer createText');
    var text = this._delegate.createText(parentElement, value);
    return text;
  }

  projectNodes(parentElement: any, nodes: any[]) {
    console.log('EscapeRenderer projectNodes');
    return this._delegate.projectNodes(parentElement, nodes);
  }

  attachViewAfter(node: any, viewRootNodes: any[]) {
    console.log('EscapeRenderer attachViewAfter');
    return this._delegate.attachViewAfter(node, viewRootNodes);
  }

  detachView(viewRootNodes: any[]) {
    console.log('EscapeRenderer detachView');
    return this._delegate.detachView(viewRootNodes);
  }

  destroyView(hostElement: any, viewAllNodes: any[]) {
    console.log('EscapeRenderer destroyView');
    return this._delegate.destroyView(hostElement, viewAllNodes);
  }

  listen(renderElement: any, name: string, callback: Function) {
    console.log('EscapeRenderer listen');
    return this._delegate.listen(renderElement, name, callback);
  }

  listenGlobal(target: string, name: string, callback: Function): Function {
    console.log('EscapeRenderer listenGlobal');
    return this._delegate.listenGlobal(target, name, callback);
  }

  setElementProperty(renderElement: any, propertyName: string, propertyValue: any) {
    console.log('EscapeRenderer setElementProperty');
    return this._delegate.setElementProperty(renderElement, propertyName, propertyValue);
  }

  setElementAttribute(renderElement: any, attributeName: string, attributeValue: string) {
    console.log('EscapeRenderer setElementAttribute');
    return this._delegate.setElementAttribute(renderElement, attributeName, attributeValue);
  }

  /**
   * Used only in debug mode to serialize property changes to comment nodes,
   * such as <template> placeholders.
   */
  setBindingDebugInfo(renderElement: any, propertyName: string, propertyValue: string) {
    console.log('EscapeRenderer setBindingDebugInfo');
    return this._delegate.setBindingDebugInfo(renderElement, propertyName, propertyValue);
  }


  setElementClass(renderElement: any, className: string, isAdd: boolean) {
    console.log('EscapeRenderer setElementClass');
    return this._delegate.setElementClass(renderElement, className, isAdd);
  }

  setElementStyle(renderElement: any, styleName: string, styleValue: string) {
    console.log('EscapeRenderer setElementStyle');
    return this._delegate.setElementStyle(renderElement, styleName, styleValue);
  }

  invokeElementMethod(renderElement: any, methodName: string, args: any[]) {
    console.log('EscapeRenderer invokeElementMethod');
    return this._delegate.invokeElementMethod(renderElement, methodName, args);
  }

  setText(renderNode: any, text: string) {
    console.log('EscapeRenderer setText');
    return this._delegate.setText(renderNode, text); }

}

Now to the question(s):

  1. Is that (customRenderer) a way to handle the problem of escaping template-content, or am I totally wrong?
  2. How can I tell a single component to use a different Renderer?

Thanks for some help,

best Jan

Community
  • 1
  • 1
Jan Fanslau
  • 103
  • 2
  • 8
  • I think a renderer needs to be provided globally not per component (not tried myself yet). – Günter Zöchbauer Sep 16 '16 at 11:33
  • i also tried: providers:[ {provide:DebugDomRootRenderer, useClass:EscapeRootRenderer} ] – Jan Fanslau Sep 16 '16 at 13:33
  • Do you actually want to render the view source of a component or do you want to render HTML that contains Angular2 bindings but prevent Angular2 from processing the bindings? If you use AoT, the components won't have the original view code included. Bindings are then replaced by JS code. – Günter Zöchbauer Sep 16 '16 at 13:37
  • I want to render the escaped Template-File --> `
    `to see the real HTML-Code on the website. The Code-Snippets for developer to copy without opening the Dev-Tools... And I already made it, but in a really simple way, see answer
    – Jan Fanslau Sep 16 '16 at 18:16
  • [Somebody here on stackblitz](https://stackblitz.com/edit/angular-custom-renderer?file=app%2Frenderer.ts) went as far as implementing a complete renderer (traversing nodes, adding classes by commands,...) – Frank N Feb 14 '20 at 16:31

2 Answers2

2

I solved my problem without any Custom-Renderer:

import {
  Component, ViewChild, OnInit, AfterViewInit, OnInit
} from '@angular/core';


@Component({
  selector:'escape',
  template:'<content #kiddo><ng-content></ng-content></content><pre><code #codeContent>{{componentInnerHTML}}</code></pre>'
})

export class EscapeComponent implements OnInit, AfterViewInit{
  @ViewChild('kiddo') viewChild;
  @ViewChild('codeContent') codeContent;
  componentInnerHTML:string;

  ngAfterViewInit(){
    this.componentInnerHTML = this.viewChild.nativeElement.innerHTML;
  }

  ngOnInit(){
    var that = this;
    setTimeout(()=>hljs.highlightBlock(that.codeContent.nativeElement), 1)
  }
}

The Result looks now like this. Developers can now simply copy code directly from the website...

Screenshot output EscapeComponent

But, I'm still interested on how I can use a Custom-Renderer.

Jan Fanslau
  • 103
  • 2
  • 8
0

I still don't fully understand the question but perhaps this does what you want

<pre ngNonBindable>
    template content here
</pre>
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567