Is there a way to get the HTML template for an Angular* component?

|▌冷眼眸甩不掉的悲伤 提交于 2019-11-27 18:32:11

问题


I'm trying to create a library of shared Angular components to use across a number of different web projects. Along with the shared component library, I'm trying to create a web project that contains and displays all of those components and code examples as to how to use them.

As seen from other queries related to a similar question, in order to display HTML code in a webpage in in <pre> or <code> tags, certain aspects of those HTML snippets need to have HTML encoded characters(i.e. > needs to be &gt;, < needs to be &lt;). The need to make these changes by hand from the source code can be quite onerous and tedious.

I would like to find a way to be able to read the HTML template for a given component, make the few text replacements needed and then set that value to a property to be displayed in the view. I've seen other similar SO questions that have a different and insufficient answer such as this.

If I have foo.component.ts as follows:

import { Component } from '@angular/core';

@Component({
    selector: 'foo',
    template: `
        <div class="foo">
            <div class="content">
                <ng-content select="[top-content]"></ng-content>
            </div>
            <div class="extra content">
                <ng-content select="[bottom-content]"></ng-content>
            </div>
        </div>
    `
})
export class FooComponent {}

I want to create a display-foo.component.ts with something as follows:

import { Component } from '@angular/core';
import { FooComponent } from './foo.component';

@Component({
    selector: 'display-foo',
    template: `
        <pre class="example-code">
          <code>
            {{encodedExampleHtml}}
          </code>
        </pre>
        <foo>
          <div top-content>TOP</div>
          <div bottom-content>BOTTOM</div>
        </foo>
    `
})
export class DisplayFooComponent {
  encodedExampleHtml: string;

  constructor() {
    this.encodedExampleHtml = new FooComponent().template.replace('<', '&lt;').replace('>', '&gt;');
  }
}

In essence this would put the template in the UI for the next developer to see and understand how to utilize as well as an actual rendered element of that type of component as well displaying what the component would look like when used in an actual application.

The part of the DisplayFooComponent that I can't figure out how to work is this line this.encodedExampleHtml = new FooComponent().template.replace('<', '&lt;').replace('>', '&gt;'); Take that as pseudocode for what I would like to do because an Angular component object has no template property and as far as I can see no property that exposes the template for that component.

Is there a way to utilize the Angular framework to get the HTML template for a component? Again I don't want the interpolated content with expressions replaced, but the actual raw HTML template that is associated with an element either by the decorator's template property or its templateUrl property?


回答1:


You can try using special function to get annotations:

annotation.ts

declare let Reflect: any;
export function getAnnotation(typeOrFunc: Type<any>): any[]|null {
  // Prefer the direct API.
  if ((<any>typeOrFunc).annotations) {
    let annotations = (<any>typeOrFunc).annotations;
    if (typeof annotations === 'function' && annotations.annotations) {
      annotations = annotations.annotations;
    }
    return annotations[0];
  }

  // API of tsickle for lowering decorators to properties on the class.
  if ((<any>typeOrFunc).decorators) {
    return convertTsickleDecoratorIntoMetadata((<any>typeOrFunc).decorators)[0];
  }

  // API for metadata created by invoking the decorators.
  if (Reflect && Reflect.getOwnMetadata) {
    return Reflect.getOwnMetadata('annotations', typeOrFunc)[0];
  }
  return null;
}

function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[] {
  if (!decoratorInvocations) {
    return [];
  }
  return decoratorInvocations.map(decoratorInvocation => {
    const decoratorType = decoratorInvocation.type;
    const annotationCls = decoratorType.annotationCls;
    const annotationArgs = decoratorInvocation.args ? decoratorInvocation.args : [];
    return new annotationCls(...annotationArgs);
  });
}

and then

this.encodedExampleHtml = getAnnotation(FooComponent).template;

Plunker Example

See also

  • When and how s decorator applied to the decorated classes from the @angular packages
  • Read and List all exports of an angular module

  • Example of how angular parses template




回答2:


yurzui's answer works fine for Angular 4.4.2 but not for 5.0.2. The annotation mechanism changed: a property instead of Reflect.defineMetadata() is used to store metadata. Since it's not documented, one can only find it in the code:

const /** @type {?} */ TypeDecorator = (function TypeDecorator(cls) {
    const /** @type {?} */ annotations = Reflect.getOwnMetadata('annotations', cls) || [];
    annotations.push(annotationInstance);
    Reflect.defineMetadata('annotations', annotations, cls);
    return cls;
});

in Angular 4.4.2 (@angular/core/@angular/core.js, l.356) to

const ANNOTATIONS = '__annotations__';
const /** @type {?} */ TypeDecorator = /** @type {?} */ (function TypeDecorator(cls) {
    // Use of Object.defineProperty is important since it creates non-enumerable property which
    // prevents the property is copied during subclassing.
    const /** @type {?} */ annotations = cls.hasOwnProperty(ANNOTATIONS) ?
        (/** @type {?} */ (cls))[ANNOTATIONS] :
        Object.defineProperty(cls, ANNOTATIONS, { value: [] })[ANNOTATIONS];
    annotations.push(annotationInstance);
    return cls;
});

in Angular 5.0.2 (@angular/core/esm2015/core.js, l.96).

Updated code for accessing Metadata, e.g. the template:

import 'reflect-metadata';

export function getAnnotation(typeOrFunc: Type<any>): any[]|null {
  // Prefer the direct API.
  if ((<any>typeOrFunc)['__annotations__']) {
      return (<any>typeOrFunc)['__annotations__'][0];
  }

  // API of tsickle for lowering decorators to properties on the class.
  if ((<any>typeOrFunc).decorators) {
    return convertTsickleDecoratorIntoMetadata((<any>typeOrFunc).decorators)[0];
  }

  // API for metadata created by invoking the decorators.
  if (Reflect && Reflect.getOwnMetadata) {
    return Reflect.getOwnMetadata('annotations', typeOrFunc)[0];
  }
  return null;
}

function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[] {
  if (!decoratorInvocations) {
    return [];
  }
  return decoratorInvocations.map(decoratorInvocation => {
    const decoratorType = decoratorInvocation.type;
    const annotationCls = decoratorType.annotationCls;
    const annotationArgs = decoratorInvocation.args ? decoratorInvocation.args : [];
    return new annotationCls(...annotationArgs);
  });
}


来源:https://stackoverflow.com/questions/45497561/is-there-a-way-to-get-the-html-template-for-an-angular-component

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!