【问题标题】:Dynamically inject HTML and CSS on request根据请求动态注入 HTML 和 CSS
【发布时间】:2020-06-07 23:44:30
【问题描述】:

所以我一直在寻找如何从服务器加载 CSS 和 HTML。

我想要实现的是请求显示某个模板,该模板将 HTML 和 CSS 发送到网站并将其与一些用户定义的样式(如颜色)一起加载

到目前为止,我能够使用以下方法注入 HTML:

<div [innerHTML]="template | sanitizeHtml"></div>

import { Pipe, PipeTransform, SecurityContext } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Pipe({
    name: 'sanitizeHtml'
})
export class SanitizeHtmlPipe implements PipeTransform {

    constructor(private sanitizer: DomSanitizer) { }
    transform(value: any): any {
        return this.sanitizer.bypassSecurityTrustHtml(value);
    }

}

我从不同的帖子和博客中看到的(谢谢)。

我一直在构建的 HTML 就像一个魅力:

this.template = "<div class='template' style='width: 1080px; height: 1920px; background-color: #212121;'><div class='clr-row' style='padding:45px 0px 10px 25px; position: relative; width: inherit;'><div class='clr-col-5'><div style='width: 230px; height: 60px; background-image: url(*LINK_TO_IMAGE*); background-repeat: no-repeat; float: left;'></div></div></div></div>"

此 HTML 是完整模板的一部分。 所以我想做的是通过使用变量来使用样式。

所以我尝试的是制作一个样式对象:

public style: {};
public template: string;
ngOnInit(){
    this.style = {
        template: {
            "color": "#D8B088",
        }
    }
    this.template = "<div [ngStyle]='style.template' class='template' style='width: 1080px; height: 1920px; background-color: #212121;'><div class='clr-row' style='padding:45px 0px 10px 25px; position: relative; width: inherit;'><div class='clr-col-5'><div style='width: 230px; height: 60px; background-image: url(*LINK_TO_IMAGE*); background-repeat: no-repeat; float: left;'></div></div></div></div>"
}

我已经使用 [ngStyle]='style.template' 将样式对象添加到模板中,由于某种原因样式没有被加载,所以我尝试使用 camelCasing 代替,但仍然没有成功。

那么有人知道如何让 CSS 在这种情况下工作,并最终使用用户定义的样式吗?

提前致谢。

编辑 1:

我还在 app.module.ts 中包含了 Sanitize 管道:

@NgModule({
    declarations: [
        ...,
        SanitizeHtmlPipe
    ],
    ...
});

(对于那些想知道的人)

编辑 2:

所以我一直在研究我想用这些模板做什么:

用户可以注册多个设备来显示 Office 365 中的预订。用户可以通过 2 种方式设置模板,但这并不重要。当用户想要显示特定设备的模板时,他们会转到 /device/:deviceid/template/:templateid。 这样,组件将加载到该设备的模板中。 因此,首先我们加载包含模板用户样式的设备设置。之后,我们从 office365 中加载必须在模板中显示的数据,最后加载带有模板样式的模板。 所以会有3个请求到服务器。 DeviceSettings -- Data Office365 -- 模板

到目前为止,我已经能够加载数据并将其放置在模板中,但是模板在本地可用,而不是从服务器上可用。 我想要从服务器请求模板的原因是会有一个管理门户,这些模板将在其中制作和管理。 这些模板将具有名称、HTML 和 CSS。

【问题讨论】:

  • 只为div添加类并使用scss/css文件添加样式不是更合乎逻辑吗?目前模板不可读。也可以解决问题。
  • 您已经在使用 style 属性设置 div 的样式。同样,您使用了 ngStyle。可能是因为它没有加载。
  • @Billy Cottrell 是的。我明白那个。但是您可以只更改类名并将所有样式代码存储在单独的 css/scss 文件中。它使代码更干净。拥有多个模板也没有问题。在项目中,我现在正在使用哪个模板,现在有 4 个模板,但我可以很容易地添加新模板。所以我建议在要放入经过消毒的 InnerHtml 的元素上使用 NgClass。
  • @Billy Cottrell 如果仅基于用户设置您只更改类,并且其他所有内容都存储在 scss / css 中,应用程序应该运行得很快。在这种情况下,您将拥有数百个特定于 css 的类。我会写一个答案,我会怎么做。
  • 无论哪种方式,现在您只使用 TrustHtml。但是知道您的模板也包含样式,您还需要使用 TrustStyles。不知道两者是否可以在同一个html上使用。但我认为不是。所以事情应该做不同的事情。

标签: javascript html css angular


【解决方案1】:

【讨论】:

  • 门户设置看起来很有趣,可能是要走的路,我一定会更多地研究这个问题。您能否仅更新 stackblitz 链接,因为它似乎不起作用我总是收到 Cannot GET /api/angular/v1 错误。感谢大家的帮助!
  • 如果我要使用门户,我需要为我想使用的每个模板制作一个组件。那么,如果我有大约 500 个模板,我需要制作 500 个组件吗?那简直是疯了。我只希望像常规数据一样保存模板,并通过对服务器的请求获得某个模板。我猜 Angular 不支持从服务器请求加载模板。
【解决方案2】:

我不会在已清理的 HTML 中使用 [ngStyle],而是只更改 dom 元素的类,将已清理的 HTML 插入其中:

<div [ngClass]="templateClass" [innerHTML]="templateHtml"></div>

通过这种方式,代码更具可读性,并且样式代码与 HTML 分离。

模板的 CSS 看起来像这样:

.template-class-1 {
    background-color: #f44336;
}
.template-class-2 {
    background-color: #4caf50;
}

【讨论】:

  • 好的,我想我明白你在说什么,但是 ngClass 会在 templateHtml 变量中工作吗,我只是尝试在那里使用它,但似乎没有用?所以在 .ts 文件中是这样的:this.templateHTML = "&lt;div [ngClass]='templateClass'&gt;&lt;/div&gt;";
  • 这就是我需要在 templateHtml 中应用样式的问题,如果样式表是从 api 下载的,那么我应该如何应用它呢?
  • @Billy Cottrell 尝试查看这个问题的答案:stackoverflow.com/questions/38888008/…
【解决方案3】:

2020 年 14 月 10 日更新:

之前的解决方案需要包含编译器,这样您就无法在生产模式下构建项目。感谢 Owen Kelvins 的回答,现在可以添加动态 html 和 css,同时仍然可以构建到生产环境,因为它不需要编译器:

Angular multiple templates in one component based on id (with template store)

要添加自定义 CSS,您可以使用 Owen Kelvins 方法或在 html 末尾附加“”标签,然后将自定义 CSS 与结束标签一起添加。

原答案:

我已经找到了解决这个问题的方法。感谢 discord 服务器“The Coding Den”中的某个人,他给我发了消息,并在 Github 上给了我一个指向 Dynamically load template for a component 的链接。滚动浏览这篇长帖子后,我找到了answer of Alarm9k。这就是我使用它创建一个组件的方式,该组件可以通过服务器请求根据给定的 id 显示不同的模板,我还添加了一些 cmets 来解释它。

import { Component, AfterViewInit, Compiler, NgModule, ViewChild, ViewContainerRef, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BookingService } from 'src/app/services/booking.service';
import { ApplicationModel } from 'src/app/models/application.model';
import { Booking } from 'src/app/models/vo/booking';
import { Subscription } from 'rxjs';
import { SplitStringPipe } from '../../utils/split-string.pipe';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { BrowserModule } from '@angular/platform-browser';

@Component({
    selector: 'app-bookings-template',
    templateUrl: './bookings-template.component.html',
    styleUrls: ['./bookings-template.component.css']
})
export class BookingsTemplateComponent implements AfterViewInit {

    public template: string;
    public date: Date;
    public locale: string;
    public id: string;

    @ViewChild('container', { read: ViewContainerRef, static: false }) container: ViewContainerRef;

    constructor(private compiler: Compiler, private bs: BookingService, private apm: ApplicationModel) { }

    ngAfterViewInit() {
        // Must clear cache.
        this.compiler.clearCache();
        // fill in template from server request
        this.template = "<div class="test">{{test}}</div>;
        var styles = ".test{color:red}";
        // Define the component using Component decorator.
        const component = Component({
            template: this.template + "<div>Hard Coded html for error checks and loading spinner</div>",
            styles: [styles]
        })(class implements OnInit {
            //example properties
            public date: Date;
            public bookings: Array<Booking>;
            public isLoading: boolean = true;
            public hasError: boolean = false;
            public errorMessage: string;
            public errorMessageSub: Subscription;
            public bs: BookingService;
            public apm: ApplicationModel;
            // Do not pass any parameters in the constructor or it will break!
            // Instead pass it within the factory method down below as a property!
            constructor() {
                // refresh template every minute
                setInterval(() => {
                    this.ngOnInit();
                }, 60000);
                // refresh date every second
                setInterval(() => {
                    this.date = new Date();
                }, 1000);
            }

            ngOnInit() {
                // get data to fill in template
            }
            ngOnDestroy() {
                //remove error subscription
                this.errorMessageSub.unsubscribe();
            }
        });

        // Define the module using NgModule decorator.
        //Modules can be changed based on your needs
        const module = NgModule({
            imports: [
                CommonModule,
                BrowserAnimationsModule,
                BrowserModule,
                HttpClientModule],
            declarations: [component, SplitStringPipe],
            providers: [BookingService]
        })(class { });

        // Asynchronously (recommended) compile the module and the component.
        this.compiler.compileModuleAndAllComponentsAsync(module)
            .then(factories => {
                // Get the component factory.
                const componentFactory = factories.componentFactories[0];
                // Create the component and add to the view.
                const componentRef = this.container.createComponent(componentFactory);
                // pass parameters that would go in the constructor as properties
                // subscriptions should also work.
                componentRef.instance.bs = this.bs;
                componentRef.instance.apm = this.apm;
                componentRef.instance.errorMessageSub = this.apm.getMessageError().subscribe(me => componentRef.instance.errorMessage = me);
            });
    }

}

BookingsTemplateComponent 充当匿名组件类的父类,匿名组件类充当子类。这样,由于指定了容器名称并与父 html id 匹配的 @ViewChild,可以将子添加到父级: &lt;div #container&gt;&lt;/div&gt;(在这种情况下)。

您还需要在应用模块中添加一些内容:

import { NgModule, CompilerFactory, Compiler, COMPILER_OPTIONS } from '@angular/core';
import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
import { CommonModule } from '@angular/common';

export function createCompiler(compilerFactory: CompilerFactory) {
    return compilerFactory.createCompiler();
}
@NgModule({
    declarations: [
        // components and pipes
        ...
    ],
    imports: [
        CommonModule, // required
        ... //other modules
    ],
    providers: [
        // different services
        ...,
        // these are need to add the compiler manually to the project
        { provide: COMPILER_OPTIONS, useValue: {}, multi: true },
        { provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
        { provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

警告:

其中最重要的因素是您无法在生产模式下构建项目。这样做的原因是因为 JIT 编译不起作用,您将收到以下错误: 这是因为生产环境中不包含 Angular 编译器,即使您尝试手动添加也是如此。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多