Angular 2中与ng-bind-html、$sce.trustAsHTML()和$compile等价的方法是什么?

20
在Angular 1.x中,我们可以使用HTML标签ng-bind-html和JavaScript调用$sce.trustAsHTML()实时插入HTML。这让我们完成了80%的工作,但当使用Angular标签(如插入使用ng-repeat或自定义指令的HTML)时,它将无法工作。
为了使其工作,我们可以使用自定义指令,调用$compile
在Angular 2中,所有这些的等效方式是什么?我们可以使用[inner-html]进行绑定,但这仅适用于非常简单的HTML标记,例如<b>。它不能将自定义的Angular 2指令转换为功能性的HTML元素。(就像没有$compile步骤的Angular 1.x一样。)在Angular 2中,$compile的等效方式是什么?
6个回答

13
在Angular2中,您应该使用DynamicComponentLoader将一些“已编译内容”插入到页面上。例如,如果要编译下一个HTML,则可以这样做:
<div>
    <p>Common HTML tag</p>
    <angular2-component>Some angular2 component</angular2-component>
</div>

然后你需要使用这个HTML作为模板来创建一个组件(我们称之为CompiledComponent),并使用 DynamicComponentLoader 将此组件插入到页面中。

@Component({
  selector: 'compiled-component'
})
@View({
  directives: [Angular2Component],
  template: `
    <div>
      <p>Common HTML tag</p>
      <angular2-component>Angular 2 component</angular2-component>
    </div>
  `
})
class CompiledComponent {
}

@Component({
  selector: 'app'
})
@View({
  template: `
    <h2>Before container</h2>
    <div #container></div>
    <h2>After conainer</h2>
  `
})
class App {
  constructor(loader: DynamicComponentLoader, elementRef: ElementRef) {
    loader.loadIntoLocation(CompiledComponent, elementRef, 'container');
  }
}

请查看这个plunker

更新loader.loadIntoLocation()调用之前可以动态创建组件:

// ... annotations
class App {
  constructor(loader: DynamicComponentLoader, elementRef: ElementRef) {
    // template generation
    const generatedTemplate = `<b>${Math.random()}</b>`;

    @Component({ selector: 'compiled-component' })
    @View({ template: generatedTemplate })
    class CompiledComponent {};

    loader.loadIntoLocation(CompiledComponent, elementRef, 'container');
  }
}

就我个人而言,我不喜欢它,它看起来像是一个肮脏的黑客。但这里有plunker

PS请注意,目前angular2正在积极开发中。因此情况随时可能会发生变化。


在运行时使用JavaScript更改模板的HTML是否可能?我无法预先知道HTML以便将其输入到模板中...它是动态计算的。 - Vern Jensen
据我所知,您无法更改组件装饰器的属性。而且这不是一个好主意。 - alexpods
对于我的用例,我只需将样式标签添加到返回的HTML中即可。虽然不太美观,但现在可以使用了。 - JimTheDev
@JimTheDev 我更新了示例,添加了动态创建组件的功能(对我来说是个小技巧)。至于您关于差异的问题,在这种情况下我没有看到什么特殊之处。 您只需要创建一个名为 JsonDiff 的组件 (<json-diff json1="firstJson" json2="secondJson"/>),该组件必须负责呈现两个json之间的差异。 也许您需要创建一些辅助组件用于递归处理,但不是真正特别的东西。 - alexpods
6
浪费了2个小时,解决方案非常简单,请查看https://dev59.com/RlwZ5IYBdhLWcg3wWvRY?rq=1 - sathishkumar
显示剩余7条评论

5

DynamicComponentLoader 已经被弃用,您可以使用 ComponentResolver 替代。

如果需要进行额外的数据处理,您可以添加管道(pipe)来使用此指令。此指令还允许进行懒加载(lazy loading),虽然在您的情况下可能用不到,但值得一提。

指令(Directive)(我找到了这段代码并做了一些更改,您也可以根据自己的喜好进行修改或直接使用):

import { Component, Directive, ComponentFactory, ComponentMetadata, ComponentResolver, Input, ReflectiveInjector, ViewContainerRef } from '@angular/core';
declare var $:any;

export function createComponentFactory(resolver: ComponentResolver, metadata: ComponentMetadata): Promise<ComponentFactory<any>> {
    const cmpClass = class DynamicComponent {};
    const decoratedCmp = Component(metadata)(cmpClass);
    return resolver.resolveComponent(decoratedCmp);
}

@Directive({
    selector: 'dynamic-html-outlet',
})
export class DynamicHTMLOutlet {
  @Input() htmlPath: string;
  @Input() cssPath: string;

  constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver) {
  }

  ngOnChanges() {
    if (!this.htmlPath) return;
    $('dynamic-html') && $('dynamic-html').remove();
    const metadata = new ComponentMetadata({
        selector: 'dynamic-html',
        templateUrl: this.htmlPath +'.html',
        styleUrls:  [this.cssPath]
    });
    createComponentFactory(this.resolver, metadata)
      .then(factory => {
        const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
        this.vcRef.createComponent(factory, 0, injector, []);
      });
  }
}

使用示例:

import { Component, OnInit } from '@angular/core';
import { DynamicHTMLOutlet } from './../../directives/dynamic-html-outlet/dynamicHtmlOutlet.directive';

@Component({
  selector: 'lib-home',
  templateUrl: './app/content/home/home.component.html',
  directives: [DynamicHTMLOutlet]
})
export class HomeComponent implements OnInit{
    html: string;
    css: string;

    constructor() {}

    ngOnInit(){
    this.html = './app/content/home/home.someTemplate.html';
    this.css = './app/content/home/home.component.css';
    }

  }

home.component.html:

<dynamic-html-outlet [htmlPath]="html" [cssPath]="css"></dynamic-html-outlet>

嗨... 上述解决方案出现以下错误,Typescript 错误 模块“D:/app/node_modules/@angular/core/core”没有导出成员“ComponentResolver”。Typescript 错误 模块“D:/app/node_modules/@angular/core/core”没有导出成员“ComponentMetadata”。 - Parag Ghadge

4

在阅读了很多资料之后,我决定在这里回答问题,试着帮助其他人。据我所见,Angular 2的最新版本(目前是Beta9)有几个变化。

为了避免我曾经遇到的困惑,我会尝试分享我的代码...

首先,在我们的index.html中

像往常一样,我们应该有以下内容:

<html>
 ****
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

AppComponent (使用innerHTML属性)

通过这个属性,您可以渲染基本的HTML,但是您将无法像Angular 1.x中使用$scope进行$compile编译的类似操作:

import {Component} from 'angular2/core';

@Component({
    selector: 'my-app',
    template: `
                <h1>Hello my Interpolated: {{title}}!</h1>
                <h1 [textContent]="'Hello my Property bound: '+title+'!'"></h1>
                <div [innerHTML]="htmlExample"></div>
             `,
})

export class AppComponent {
    public title = 'Angular 2 app';
    public htmlExample = '  <div>' +
                                '<span [textContent]="\'Hello my Property bound: \'+title"></span>' +
                                '<span>Hello my Interpolated: {{title}}</span>' +
                            '</div>'
}

这将呈现以下内容:

你好我的插值:Angular 2应用程序!

你好我的属性绑定:Angular 2应用程序!

你好我的插值:{{title}}

AppComponent使用DynamicComponentLoader 文档中有一个小错误,记录在这里。所以如果我们知道了这个问题,我的代码现在应该是这样的:
import {DynamicComponentLoader, Injector, Component, ElementRef, OnInit} from "angular2/core";

@Component({
    selector: 'child-component',
    template: `
        <div>
            <h2 [textContent]="'Hello my Property bound: '+title"></h2>
            <h2>Hello my Interpolated: {{title}}</h2>
        </div>
    `
})
class ChildComponent {
     title = 'ChildComponent title';
}

@Component({
    selector: 'my-app',
    template: `
                <h1>Hello my Interpolated: {{title}}!</h1>
                <h1 [textContent]="'Hello my Property bound: '+title+'!'"></h1>
                <div #child></div>
                <h1>End of parent: {{endTitle}}</h1>
             `,
})

export class AppComponent implements OnInit{
    public title = 'Angular 2 app';
    public endTitle= 'Bye bye!';

    constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {
//        dynamicComponentLoader.loadIntoLocation(ChildComponent, elementRef, 'child');
    }

    ngOnInit():any {
        this.dynamicComponentLoader.loadIntoLocation(ChildComponent, this.elementRef, 'child');
    }
}

这将呈现以下内容:

你好,我的插值:Angular 2 应用程序!

你好,我的属性绑定:Angular 2 应用程序!

你好,我的属性绑定:ChildComponent 标题

你好,我的插值:ChildComponent 标题

父组件结尾:再见!


4

我认为你只需要使用[innerHTML]="你的组件作用域变量"来设置你想要编译html的元素。


1
你能详细说明一下这个解决方案吗? - sg7
this.yourcomponentscopevar = '<b>粗体文本</b>'; - mruanova
你对这个问题有什么建议吗?- https://stackoverflow.com/questions/68637985/issue-with-ng-html-compile-displaying-ui-grid-twice-while-rendering-saved-html-f - RedBottleSanitizer

0

Angular提供了DynamicComponentLoader类来动态加载HTML。 DynamicComponentLoader有用于插入组件的方法。其中之一是loadIntoLocation,用于插入组件。

paper.component.ts

import {Component,DynamicComponentLoader,ElementRef,Inject,OnInit} from 'angular2/core';
import { BulletinComponent } from './bulletin.component';

@Component({
    selector: 'paper',
    templateUrl: 'app/views/paper.html'

    }
})
export class PaperComponent {
    constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {

    }

    ngOnInit(){
        this.dynamicComponentLoader.loadIntoLocation(BulletinComponent, this.elementRef,'child');

    }
}

bulletin.component.ts

import {Component} from 'angular2/core';

@Component({
    selector: 'bulletin',
    templateUrl: 'app/views/bulletin.html'
    }
})
export class BulletinComponent {}

paper.html

<div>
    <div #child></div>
</div>

需要注意的几件事:

  • 不要在类的构造函数中调用loadIntoLocation。当组件构造函数被调用时,组件视图尚未创建。否则会出现以下错误 -

AppComponent实例化期间出错!元素[object Object]上没有组件指令

  • 在HTML中放置锚点名称#child,否则会出现以下错误。

找不到变量child


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接