簡體   English   中英

Angular 基於id的一個組件中的多個模板(帶模板存儲)

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

我有一個特殊的項目,但我無法找到有關如何實現這一目標的任何信息。

所以在這個網站上公司可以注冊和登錄。 當一家公司登錄時,他們會看到設備和組的概覽,其中設備可以分為不同的組以便於識別。 現在網站的難點是模板管理。 每個設備將顯示一個模板,該模板可以是通用指定模板、分配給特定組或單個設備的模板。 選用的模板有標准提供的模板,有公司定制的模板,也有我自己定制的定制模板。 (最后2個選項只對公司本身可見)

這樣做的主要原因是顯示不同的模板,我的意思是結構差異,如表格、卡片甚至自定義結構。

所以目前我可以根據公司的 id 顯示模板。 這些模板集成在 angular 應用程序中。 所以現在它看起來像這樣(這只是一個小例子):

    this.companyName = this.route.snapshot.params['company'];
    if(this.companyName == "Google"){
        this.template = `<div [ngStyle]="{'border-left':(tr.state=='busy')?'10px solid #D4061C':'10px solid #2CC52E'}">{{data}}</div>`;
        this.styles = "div{color: red}";
    }

之后發生的事情是通過將編譯器保留在構建中來動態創建組件。 所以這意味着這個項目不能在生產模式下構建,因為需要編譯器。 這意味着部署項目很糟糕,因為代碼在瀏覽器中可見,而且大小要大得多,所以加載所有內容需要花費太多時間。我有點想放棄這種方法,使用其他更容易的方法采用

所以我想知道的是:

  • 是否可以從數據庫中的數據或 HTML 文件加載 html。
  • 這可以通過將任何其他庫與 Angular 一起使用來實現嗎?
  • 有沒有一種方法可以創建我提供給也顯示該模板預覽的公司的模板概覽?
  • 有沒有辦法檢索顯示模板的設備的 ip 和 mac 地址。

如果不能為此使用 Angular 什么環境,如語言、框架等,您建議改用它嗎?

如果需要更多信息,請隨時提出!

提前致謝!

編輯 1:

我曾嘗試使用 [innerHTML] 加載模板,但這不能正常處理數據綁定或數據插值字符串。

我會給你一個 HTML 的例子,我想載入:

    <div class='exellys' style='width: 1080px ;height: 1920px;background-color: #212121;'>
        <div class='clr-row' style='padding:45px 0px 10px 25px; position: relative; width: inherit; height: 115px;'>
            <div class='clr-col-5' style='float: left;'>
                <div style='width: 230px; height: 60px; background: url(/assets/exellys/exellys.png); background: url(https://www.exellys.com/App_SkinMaster/images/sprite-new.svg), linear-gradient(transparent, transparent); background-repeat: no-repeat; float: left;'></div>
            </div>
            <div class='clr-col-7' style='padding-top: 10px; float: right;'>
                <div class='exellys medium' style='text-align: right;'>{{date | date: 'EEEE d MMMM y'}}</div>
            </div>
        </div>
        <div class='clr-row' style='position: relative; width: inherit;'>
            <div class='exellys medium' style='width: inherit;padding-right:10px; text-align: right;'>{{date | date: 'HH:mm'}}</div>
        </div>
        <div class='clr-row' style='position: relative; width: inherit;'>
            <div class='exellys large' style='padding-top: 150px; width: inherit; text-align: center; font-weight: bold;'>WELCOME TO EXELLYS</div>
        </div>
        <div class='clr-row' style='position: relative; width: inherit;'>
            <div class='exellys medium-large' style='padding-top: 75px; width: inherit; text-align: center;'>Training Schedule</div>
        </div>
        <div class='clr-row' style='position: relative; width: inherit;'>
            <table class='table table-noborder exellys' style='table-layout: fixed; padding: 100px 20px 0px 35px;'>
                <thead>
                    <tr>
                        <th class='left exellys hcell' style='font-weight: bold; font-size: 37px; width: 15%; padding-left: 0px;'>HOUR</th>
                        <th class='left exellys hcell' style='font-weight: bold; font-size: 37px; width: 40%;'>ROOM</th>
                        <th class='left exellys hcell' style='font-weight: bold; font-size: 37px;'>SUBJECT</th>
                    </tr>
                </thead>
            </table>
            <table class='table table-noborder exellys' style='table-layout: fixed; border-collapse: separate; border-spacing: 0 5px; padding: 0px 20px 0px 35px; margin-top:0px;'>
                <tbody style='padding-left: 0px;'>
                    <tr *ngFor='let tr of bookings'>
                        <td class='left exellys dcell' style='font-size: 37px; padding-left: 10px; width: 15%;' [ngStyle]="{'border-left': (tr.state=='busy')? '10px solid #D4061C' : '10px solid #2CC52E'}">{{tr.localeStart | date: 'HH:mm'}}</td>
                        <td class='left exellys dcell' style='font-size: 37px; width: 40%;' [innerHTML]="tr.scheduleLocation"></td>
                        <td class='left exellys dcell' style='font-size: 37px;'>{{tr.subject}}</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

在這個 HTML 旁邊,我還在加載以下 styles:

    .main {
        color: #b0943c;
        font-family: 'Omnes-Medium', Helvetica, sans-serif;
        width: 1080px;
        height: 1920px;
        background-color: #212121;
    }
    
    .exellys {
        color: #b0943c;
    }
    
    .exellys.medium {
        font-size: 43px;
        font-family: 'Omnes-Regular', Helvetica, sans-serif;
    }
    
    .exellys.medium-large {
        font-size: 55px;
    }
    
    .exellys.large {
        font-family: 'Refrigerator-Deluxe-Regular', Helvetica, sans-serif;
        font-size: 75px;
    }
    
    .exellys.dcell {
        line-height: 45px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        padding-left: 0px;
    }
    
    .exellys.hcell {
        padding: 0px 0px 20px 0px;
    }
    
    table.table.table-noborder th {
        border-bottom: 5px solid #996633;
    }
    
    table td {
        border-top: 2px dashed #996633;
    }

由於 XSS 保護,輸入這種模板很容易產生問題,尤其是在 innerHTML 中。 所以我想知道是否有不同的解決方案,因為可能有數百個客戶使用數百個不同的模板。

一個模板看起來像的例子: 模板示例

編輯 2:

我的意思是:

這可以通過將任何其他庫與 Angular 一起使用來實現嗎?

如果無法使用標准方法實現此目的,是否有任何庫可以使我無論如何實現此目的。

編輯 3:

所以模板建議系統的想法非常好,但是客戶想要創建它並直接添加它而其他客戶看不到。

這樣我需要能夠在后端保存 HTML 個文件(無論是模板還是整頁都無所謂)並將其加載到 angular 應用程序中。

據我了解以下所有答案,這在 Angular 中是不可能的。

我現在的問題是在什么環境或者語言下可以實現這個模板機制? 還是Angular中還有未知的安全生產方法?

提前致謝!

2020 年 12 月 15 日更新:

在實施 Owen Kelvins 的想法后,我發現了一些問題。 使用 ngFor 循環遍歷數據是行不通的。 在插值字符串中添加管道也不起作用。

要解決管道問題,您可以通過更改 prev.toString() 行來解決此問題:

    templateString$ = combineLatest([this.templatingInfo$, this.template$]).pipe(
        map(([info, template]) =>
            Object.entries(info).reduce((prev, next) => {
                var value = next[1].toString();
                var pipe = "";
                var pipevalue = "";
                var match = prev.toString().match(new RegExp(`{{\\s${next[0]}\\s(\\|\\s\\w*\\:\\s\\'\.*\\'\\s)+}}`));
                if (match != null) {
                    pipe = match[1].substring(2);
                    if (pipe.split(":")[0] == "date") {
                        pipevalue = pipe.substr(5).replace(/['"]/g, "");
                        value = formatDate(value, pipevalue, this.locale);
                        return prev.toString().replace(new RegExp(`{{\\s${next[0]}\\s(\\|\\s\\w*\\:\\s\\'\.*\\'\\s)+}}`), formatDate(next[1].toString(), pipe.substring(5).replace(/['"]+/g, ""), this.locale));
                    }
                }
                return prev.toString().replace(new RegExp(`{{\\s${next[0]}\\s}}`), next[1].toString());
            }, template)
        ),
        map(template => this._sanitizer.bypassSecurityTrustHtml(template))
    );

當然,這種方法並不完全有效,因為在某些情況下它仍然無法正確顯示。 就像你有: <div>{{date | date: 'EEEE d MMMM y' }} - {{date | date: 'HH:mm' }}</div> <div>{{date | date: 'EEEE d MMMM y' }} - {{date | date: 'HH:mm' }}</div> <div>{{date | date: 'EEEE d MMMM y' }} - {{date | date: 'HH:mm' }}</div> ,因為在這種情況下只有第一個是正確的。

我想知道如何修復 ngFor 循環作為管道問題。

提前致謝!

您應該加載不同的組件,而不是不同的模板。 (仍然可以為一個組件應用不同的模板,但這很難做到,而且它會使您的應用程序性能變差,而且更難維護。如果您仍然需要此選項,請尋找動態編譯)

您可以將一組組件注冊為一些令牌,然后顯示它們

{
 provide: COMPONENTS_OF_CHOICE,
 multi: true,
 useValue: OneOfMyComponents
}

要么

{
 provide: COMPONENTS_OF_CHOICE,
 useValue: [OneOfMyComponents, SecondOfMyComponents]
}

無法檢索設備的 ip 和 mac 地址。 它不安全並且瀏覽器不會公開該數據

我相信最簡單的解決方案是綁定到 [innerHTML],正如@capc0 之前提到的那樣

您提出了以下問題

嗨 @capc0 你的回答是完全正確的。 但是,是的,有一個但是,我在我的 html 中使用插值字符串。innerHTML 工作正常,但那是 static HTML。 我說的是 HTML,它的數據插值字符串不能與 innerHTML 一起正常工作

考慮以下方法來處理這個問題

假設我們要從下面的 object 中插入titlecost

  templatingInfo$ = of({
  title: 'Template Title',
    cost: 200
  });

我還將假設以Observable的形式接收模板

  templates$ = of([
    { 
      id: 1,
      name: 'Alpha',
      value: `
        <div class='red'> 
          <h1>{{ title }}</h1>
          <p> This is my lovely Template! Purchase it at \${{ cost }} </p>
        </div>
      `
    },
    { 
      id: 2,
      name: 'Beta',
      value: `
        <div class='blue'> 
          <h1>{{ title }}</h1>
          <p> This is my lovely Template! Purchase it at \${{ cost }} </p>
        </div>
      `
    },
   ...

現在唯一的挑戰是用正確的信息替換插值部分

我將用下面的方法解決這個問題

定義變量以跟蹤所選模板

  selected = 1;
  selectedTemplateSubject$ = new BehaviorSubject(this.selected);
  selectedTemplate$ = this.selectedTemplateSubject$.asObservable();

使用combineLatest將變量與模板組合

  template$ = combineLatest([this.templates$, this.selectedTemplate$]).pipe(
    map(([templates, selected]) => templates.find(({id}) => id == Number(selected)).value),
    )
  templateString$ = combineLatest([this.templatingInfo$, this.template$ ]).pipe(
    map(([info, template]) => 
      Object.entries(info).reduce((prev, next) => 
        prev.toString().replace(new RegExp(`{{\\s${next[0]}\\s}}`), next[1].toString())
            , template)
        ),
    )

不幸的是,上述作品 styles 將無法應用。

選項 1我們可以使用encapsulation: ViewEncapsulation.None,在我們的@Component({}) object 參見Angular 2 - innerHTML 樣式

注意:我們實際上停用了 ANGULAR 對 XSS 攻擊的保護

綜上所述,您現在有幾個選擇

  • 在將模板字符串保存到數據庫之前對其進行清理
  • 在呈現之前手動清理模板字符串
  • 只為發布模板的個人用戶提供模板。 這樣他們可能只會攻擊自己:)

看這個樣本

選項 2另一種選擇是在這篇文章中使用DomSanitizer作為解釋器

讓我們假設用戶已經包含內聯 styles,如下所示

  templates$ = of([
    {
      id: 1,
      name: "Alpha",
      value: `
        <div> 
          <h1 style='color: red'>{{ title }}</h1>
          <p style='color: blue'> This is Alpha! Purchase it at \${{ cost }} </p>
        </div>
      `
    },
    {
      id: 2,
      name: "Beta",
      value: `
        <div> 
          <h1 style='color: brown'>{{ title }}</h1>
          <p style='color: green'> This is Alpha! Purchase it at \${{ cost }} </p>
        </div>
      `
    },
    ...

我們可以將行map(template => this._sanitizer.bypassSecurityTrustHtml(template))添加到 map 將結果字符串添加到可信字符串。 代碼看起來像

import { Component } from "@angular/core";
import { of, BehaviorSubject, combineLatest } from "rxjs";
import { map } from "rxjs/operators";

import { DomSanitizer } from "@angular/platform-browser";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  constructor(private _sanitizer: DomSanitizer) {}
  templatingInfo$ = of({
    title: "Template Title",
    cost: 200
  });
  selected = 1;

  selectedTemplateSubject$ = new BehaviorSubject(this.selected);
  selectedTemplate$ = this.selectedTemplateSubject$.asObservable();
  templates$ = of([
    {
      id: 1,
      name: "Alpha",
      value: `
        <div> 
          <h1 style='color: red'>{{ title }}</h1>
          <p style='color: blue'> This is Alpha! Purchase it at \${{ cost }} </p>
        </div>
      `
    },
    {
      id: 2,
      name: "Beta",
      value: `
        <div> 
          <h1 style='color: brown'>{{ title }}</h1>
          <p style='color: green'> This is Alpha! Purchase it at \${{ cost }} </p>
        </div>
      `
    },
    {
      id: 3,
      name: "Gamma",
      value: `
        <div> 
          <h1 style='color: darkred'>{{ title }}</h1>
          <p style='color: green'> This is Alpha! Purchase it at \${{ cost }} </p>
        </div>
      `
    }
  ]);

  template$ = combineLatest([this.templates$, this.selectedTemplate$]).pipe(
    map(
      ([templates, selected]) =>
        templates.find(({ id }) => id == Number(selected)).value
    )
  );
  templateString$ = combineLatest([this.templatingInfo$, this.template$]).pipe(
    map(([info, template]) =>
      Object.entries(info).reduce(
        (prev, next) =>
          prev
            .toString()
            .replace(new RegExp(`{{\\s${next[0]}\\s}}`), next[1].toString()),
        template
      )
    ),
    map(template => this._sanitizer.bypassSecurityTrustHtml(template))
  );
}

在 Stackblitz 上查看下面的演示

是否可以從數據庫中的數據或 HTML 文件加載 html。

是的。 例如,您可以創建一個“模板編輯器”,客戶可以在其中構建模板並將該視圖存儲在數據庫中。 這不是很簡單但可能。 您可以從數據庫中提取 HTML 並通過<div [innerHTML]="data"></div>顯示它。 但是,由於注入安全風險 (xss),您必須清理用戶輸入等。 如果您定義一組“構建塊”,公司可以將這些塊中的多個組合成一個模板,並且您動態構建該 UI(並且不在數據庫中存儲任何內聯 HTML),這可能會更好。

這可以通過將任何其他庫與 Angular 一起使用來實現嗎?

什么樣的圖書館,你能指定嗎? 一般來說,我看不出有什么問題。

有沒有一種方法可以創建我提供給也顯示該模板預覽的公司的模板概覽?

是的。 如上所述,如果您將所有模板存儲在數據庫表中,例如templates ,您可以查詢所有模板(可能使用companyId上的鍵)並使用虛擬數據顯示它們。

有沒有辦法檢索顯示模板的設備的 ip 和 mac 地址。

我不知道,但正如@Andrei 提到的,我認為這是不可能的。

據我從問題中了解到,您需要為不同的公司定制模板,但是如果您將模板綁定到innerHTML以及可能導致頁面加載緩慢的大包,您將面臨 XSS 攻擊的風險。

這就是我如何 go 關於這個問題

  • 定義一個 Mixin 來保存每個公司的一般信息,例如,如果每個模板都有組和設備,我們可能有一個如下所示的 mixin
export type Constructor<T = {}> = new (...args: any[]) => T;
export const templateMixin = <T extends Constructor>(BaseClass: T = class { } as T) =>
  class extends BaseClass {
    devises$ = Observable<any[]>;
    groups$ = Observable<any[]>;
    data: any = { };
    // All other variables and functions that may be common in template file
  };
  • 將模板創建為 angular 組件...我知道這聽起來很奇怪,因為包的大小,但我們接下來會解決這個問題
@Component({
  selector: 'app-alpha-template',
  template: `
    `<div 
       [ngStyle]="{'border-left':(tr.state=='busy')?'10px solid #D4061C':'10px solid #2CC52E'}">
     {{data}}
    </div>`
  `,
  styleUrls: ['./e-learning-edit-course.component.css']
})
export class AlphaTemplate extends templateMixin { };

以上只是一個例子,如果你有比希臘字母更多的模板,你可能需要更好的命名風格 現在我們已經解決了 XSS 攻擊的問題。 下一個問題是包大小

  • 我們注意到不同的人群會加載不同的模板,所以最好的方法是使用延遲加載

您可以定義一個路由並將子路由設置為延遲加載的 templateComponents

如果我沒看錯,您想為最終用戶創建網站構建器平台之類的東西,以便他們可以添加自己的設計。

如果是,我會說添加一些設計(通過特定部分的多個組件)並讓他們選擇添加已經在您的應用程序中的特定設計。

這樣您就不需要使用 innerHTML 並且也將使用 angular 安全性。

順便說一下,我不認為這個問題與 angular 有關。它應該是你設計的一部分

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM