Как использовать ng-template внутри innerHTML в Angular

Может ли кто-нибудь помочь мне, как использовать ng-template внутри тега innerHTML,

Демонстрация Stackblitz: https://stackblitz.com/edit/angular-xgnnj4

Что я хотел сделать, так это

В компонентном HTML

<div [innerHTML]="htmlString"><div>

В Компонент TS

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

htmlString = `<ng-template #dynamicComponent></ng-template>`;

  ngAfterViewInit(): void {
    const factory = this.componentFactoryResolver.resolveComponentFactory(CommentComponent);
    const ref = this.myRef.createComponent(factory);
    ref.changeDetectorRef.detectChanges();
  }

Мне действительно нужно добиться этого, потому что я хотел получить содержимое HTML из API и использовать это значение для динамического отображения содержимого HTML.

Поскольку я получаю сообщение об ошибке Не могу прочитать свойство createComponent из undefined, но если я вынесу за пределы innerHTML, он отлично работает

Я прошел через https://angular.io/guide/dynamic-component-loader но это не помогает моему scneario


comment
У вас есть ручка, на которую мы можем взглянуть? Что именно отображается или возникает ошибка шаблона?   -  person Samuel Middendorp    schedule 20.01.2020
comment
@SamuelMiddendorp, у меня нет ручки, но я обновил вопрос   -  person Ron    schedule 20.01.2020


Ответы (2)


То, что вы в основном пытаетесь сделать, - это динамически загрузить компонент внутри динамически загружаемой строки. Любой шаблон, предоставленный встроенным или отдельно в файле HTML, обрабатывается механизмом рендеринга Angular. (Ivy - это новый и оптимизированный движок рендеринга, начиная с Angular 9, с Angular 4 по 8 механизм рендеринга по умолчанию называется Renderer2, который является преемником Renderer, который был по умолчанию с Angular 2 по 4).

Это в основном то, что вы пытаетесь сделать в прикрепленном stackBlitz,

@ViewChild("dynamicComponent", { static: false, read: ViewContainerRef }) myRef: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
    //Simulate the network call
    setTimeout(() => {
      this.htmlChildString = `<ng-template #dynamicComponent></ng-template>`;
    }, 2000);
  }
  ngAfterViewInit(): void {
    const factory = this.componentFactoryResolver.resolveComponentFactory(
      ChildViewComponent
    );
    const ref = this.myRef.createComponent(factory);
    ref.changeDetectorRef.detectChanges();
  }

Первое, что здесь не так, это то, что, очевидно, ngAfterViewInit будет вызываться перед вашим setTimeout методом, записанным в constructor, из-за определенного вами тайм-аута 2000ms, поэтому myRef всегда будет неопределенным, потому что его экземпляр еще не создан, следующая проблема, которую я вижу, касается вашего шаблона,

<div [innerHTML]="htmlChildString"></div>

innerHTML не должен содержать никаких угловых символов, он может содержать только простой HTML, который может быть непосредственно встроен в DOM, поскольку они не будут обрабатываться механизмом рендеринга Angular, на что @Andriy уже указал.

Теперь переходим к причине, по которой то, что вы пытаетесь сделать, невозможно. Предположим, вы определяете компонент с именем AComponent, имеющий шаблон:

<span>I am {{name}}</span>

angular компилятор, просматривая каждый из ваших (статически определенных) компонентов и шаблонов, прикрепленных к этим компонентам, он создает фабрику для каждого компонента. Когда компилятор angular проходит через вышеуказанный шаблон, он генерирует фабрику, подобную следующей:

function View_AComponent_0(l) {
    return jit_viewDef1(0,
        [
          jit_elementDef2(0,null,null,1,'span',...),
          jit_textDef3(null,['I am ',...])
        ], 
        null,
        function(_ck,_v) {
            var _co = _v.component;
            var currVal_0 = _co.name;
            _ck(_v,1,0,currVal_0);

обратите внимание на следующую строку?

jit_elementDef2(0,null,null,1,'span',...),

Angular использует JS для создания элементов, Angular использует эту фабрику для создания экземпляра определения представления, которое, в свою очередь, используется для создания представления компонента. Под капотом Angular представляет приложение в виде дерева представлений. Для каждого типа компонента существует только один экземпляр определения представления, который действует как шаблон для всех представлений. Но для каждого экземпляра компонента Angular создает отдельное представление.

Вышеуказанная фабрика описывает структуру представления компонента и используется при создании экземпляра компонента. jit_viewDef1 - это ссылка на функцию viewDef, которая создает определение представления. Определение представления получает узлы определения представления в качестве параметров, которые напоминают структуру html, но также содержат много специфических деталей angular.

эта фабрика - это то, что возвращается из метода resolveComponentFactory, поскольку вы статически объявили ChildViewComponent в своем модуле, а также пометили его как entryComponent, поэтому angular решил предварительно сгенерировать свою фабрику, даже если он знает, что в настоящее время он не используется маршрутизатором или в шаблон другого компонента.

Аналогичным образом ViewContainerRef - это ссылка на контейнер представления (ng-template или ng-container), который должен быть либо статически определен непосредственно в шаблоне, либо который должен быть обнаружен с помощью комбинации директив ngSwitch или ngIf, но в любом случае механизм компилятора / рендеринга имеет заранее знать о наличии этого контейнера во время компиляции.

В вашем случае, когда вы загружаете эти угловые символы динамически через API, angular знает только, что это простая строка, и не знает заранее, что это что-то, что может содержать представление во время выполнения. Если вы когда-нибудь смотрели на сборку, сгенерированную Angular, она содержит только файлы JS и не содержит шаблонов HTML или файлов стилей CSS, соответствующих вашим компонентам. Каждый шаблон, содержащийся в этих HTML-файлах, преобразуется в фабричные методы, которые в конечном итоге используются во время выполнения.

Я бы порекомендовал вам,

  1. Либо загрузите только чистый HTML-контент поверх остального API, либо вытащите все символы angular в свое приложение angular и загрузите их условно, например взгляните на этот stackBlitz В нем ничего нестандартного не происходит, он просто использует портал CDK, который в основном представляет собой просто абстракцию над методами resolveComponentFactory и createComponent и статически определенный templateRef, который динамически загружается в viewContainerRef одним нажатием кнопки.

введите описание изображения здесь

  1. Или создайте другое приложение JS или Angular и вставьте туда ChildViewComponent, и iframe загрузит контент на основе любого условия, если это необходимо,
  2. Загрузите скомпилированную версию вашего шаблона аналогично тому, как angular lazy загружает свои модули, и каким-то образом загружает его в розетку маршрутизатора программно. Я не знаю, возможно ли это вообще или нет, это просто идея в моей голове, но я не ожидаю, что это будет прямолинейно.
person Saif    schedule 24.01.2020

Вы можете использовать DomSanitizer для передачи любого HTML-кода в шаблон:

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

...

export class HelloComponent  {
  htmlString = this.sanitizer.bypassSecurityTrustHtml(`
    <ng-template>
      <div style="width: 10px; height: 10px; background: red;"></div>
    </ng-template>
  `);

  constructor(private sanitizer: DomSanitizer) {}
}

STACKBLITZ: https://stackblitz.com/edit/angular-t9roxg?file=src%2Fapp%2Fhello.component.ts

имейте в виду, что таким образом HTML передается как простой HTML без какой-либо обработки Angular

person Andriy    schedule 20.01.2020
comment
Спасибо за ответ, я забыл упомянуть, что использовал дочерний компонент внутри ng-template, и я обновил вопрос: «Можете ли вы помочь с этим, что не так? - person Ron; 20.01.2020
comment
Попробуйте следить за исходной демонстрацией Angular по адресу stackblitz.com/angular/kbayapnxprb. Я посмотрю на это позже сегодня вечером - person Andriy; 20.01.2020
comment
Спасибо @Andriy, я уже пробовал, проблема была в приведенном выше руководстве, не загружается дочерний элемент, как я загружаю - person Ron; 20.01.2020
comment
Итак, попробуйте поделиться своим примером stackblitz, чтобы я мог наблюдать за ним и попытаться помочь - person Andriy; 20.01.2020
comment
Хорошо, я создам его сейчас - person Ron; 20.01.2020
comment
Мне жаль, что я забыл создать stackblitz. Но теперь я создал его и вижу ошибку в консоли. Не могли бы вы взглянуть на stackblitz.com/edit/angular-xgnnj4 - person Ron; 22.01.2020