Невозможно использовать асинхронный канал в шаблоне динамически сгенерированного компонента в динамически сгенерированном модуле Angular в режиме AOT

Отказ от ответственности: этот вопрос был навеян "Вот что вам нужно знать о динамических компонентах в Angular" на Medium.

Там автор показывает, как можно динамически сгенерировать и модуль, и компонент, поместить последний в первый, скомпилировать модуль на лету, получить фабрику компонентов и использовать ее для генерации компонента через ViewContainerRef.

Это прекрасно работает в базовой настройке.

Теперь давайте предположим, что мы хотим динамически генерировать компонент, но на этот раз с шаблоном, который использует async труба.

Далее нам нужно, чтобы эта штука работала в производственной среде, что означает две вещи:

  • AOT включен
  • enableProdMode() был вызван при начальной загрузке приложения

Поскольку компилятор Angular недоступен, мы можем предоставить экземпляр jit-компилятора. для токена компилятора в корневом модуле приложения.

app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import {Compiler, NgModule} from '@angular/core';

import {AppComponent } from './app.component';
import {JitCompilerFactory} from '@angular/platform-browser-dynamic';

export function jitCompiler() {
  /*
   * Cast JitCompilerFactory to any because
   * jit compiler factory constructor does not
   * accept any arguments according to the @angular/platform-browser-dynamic/src/compiler_factory.d.ts
   */
  const klass = (JitCompilerFactory as any);
  return new klass([]).createCompiler([
    {
      useJit: true
    }
  ]);
}

@NgModule({
  declarations: [
    AppComponent
  ],
  providers: [
    {
      provide: Compiler,
      useFactory: jitCompiler
    }
  ],
  imports: [
    BrowserModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import {
  AfterViewInit,
  ChangeDetectionStrategy,
  Compiler,
  Component,
  NgModule,
  ViewContainerRef
} from '@angular/core';
import {Subject} from 'rxjs';
import {CommonModule} from '@angular/common';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements AfterViewInit {
  constructor(
    private readonly compiler: Compiler,
    private readonly vcr: ViewContainerRef
  ) {}
  ngAfterViewInit() {
    const template = '<span>Hello, {{text$ }}</span>';
    const cmp = Component({
      template
    })(class DynamicComponent {
      text$: Subject<string>;
    });

    const module = NgModule({
      declarations: [cmp],
      imports: [CommonModule],
      providers: []
    })(class DynamicModule {});

    this.compiler.compileModuleAndAllComponentsAsync(module)
      .then(compiledModule => {
        const dynamicComponent = this.vcr.createComponent(compiledModule.componentFactories[0]);

        const sub = new Subject();
        sub.next('World!');
        dynamicComponent.instance.text$ = sub;

        // Just to simulate some server updates
        setTimeout(() => dynamicComponent.instance.text$.next('Another'), 3e3);
      });
  }
}

Если мы запустим это и откроем DevTools, мы увидим следующую ошибку:

ОШИБКА Ошибка: Неожиданное значение "function(){}" импортировано модулем "function(){}". Пожалуйста, добавьте аннотацию @NgModule.

Проблема возникает только в режиме разработки. Угловая версия 7.2.0.

Отсюда вопрос - что надо сделать, чтобы он заработал в режиме АОТ?


person Viacheslav Moskalenko    schedule 17.04.2019    source источник
comment
Это означает, что вам нужны JIT и AOT одновременно. Да?   -  person R. Richards    schedule 18.04.2019
comment
Я понимаю что ты имеешь ввиду. В статье говорится, что угловой компилятор вызывается JIT в режиме разработки и AOT при предварительной компиляции всех ресурсов. Цель состоит в том, чтобы иметь возможность динамически создавать как угловой модуль, так и компонент в режиме AOT, будь то с помощью JIT или другого решения.   -  person Viacheslav Moskalenko    schedule 18.04.2019
comment
В статье говорится о компиляторе: Если он используется во время выполнения в браузере, это JIT. Если вы компилируете свои компоненты до того, как код будет выполнен в браузере, это компиляция AOT. Последний метод является предпочтительным.... Во время выполнения в браузере кажется другим, чем в режиме разработки, как вы выразились. Похоже, вы хотите создавать и компилировать модули/компоненты во время выполнения, в браузере, JIT, но также хотели бы скомпилировать в AOT. Это невозможно.   -  person R. Richards    schedule 18.04.2019
comment
Позвольте мне попытаться прояснить это немного. Идея состоит в том, чтобы удовлетворить чисто академический интерес возможностью динамической компиляции модулей и компонентов во время выполнения в браузере в приложении, построенном в режиме AOT. В статье говорится, что мы можем легко загрузить экземпляр компилятора во время выполнения и использовать его, что я и сделал. Я просто не уверен, что еще нужно сделать, чтобы иметь возможность использовать асинхронный канал внутри шаблона динамически генерируемого компонента. Надеюсь, немного рассеял пыль.   -  person Viacheslav Moskalenko    schedule 18.04.2019
comment
Просто для ясности - ответ на этот вопрос показывает, что это возможно (stackoverflow.com/questions/49249919/), однако я не вижу очевидного исправления, чтобы можно было использовать асинхронный канал внутри шаблона динамически сгенерированный компонент.   -  person Viacheslav Moskalenko    schedule 18.04.2019
comment
Этот вопрос смешивает несколько концепций вместе. Можно ли динамически создать компонент с помощью канала в режиме JIT? Во-вторых, режим AOT предназначен для компонентов, о которых вы говорите во время сборки. Следовательно, нет смысла компилировать код AOT, который создает компонент на лету, если вы заранее знаете, какой компонент вам нужен, выделите его в отдельный файл и работайте с ним, как с любым другим компонентом.   -  person Max Koretskyi    schedule 29.04.2019
comment
@MaxKoretskyiakaWizard, позвольте мне немного прояснить ситуацию: 1) Да, я могу создать динамический компонент в режиме JIT, используя канал в шаблоне компонента 2) Правильно ли я понимаю, что AOT неприменим в случае, если приложение получает некоторые шаблоны компонентов из базы данных (скажем, через вызов REST) ​​во время выполнения (например, при переходе по определенному маршруту)? Да, я знаю, что это уродливо и грязно, но это печальная реальность некоторых корпоративных проектов, где одна небольшая команда пользовательского интерфейса отвечает только за само веб-приложение, в то время как шаблоны готовятся другой командой другого поставщика.   -  person Viacheslav Moskalenko    schedule 30.04.2019
comment
@ViacheslavMoskalenko, вы должны скомпилировать эти компоненты AOT перед их получением и напрямую работать с фабриками. По крайней мере я так делал год назад, может что-то изменилось   -  person Max Koretskyi    schedule 07.05.2019
comment
@MaxKoretskyiakaWizard, поэтому алгоритм выглядит следующим образом: * AOT компилирует все компоненты, шаблоны которых хранятся в базе данных * используют созданные фабрики для динамического создания компонента во время выполнения, используя, скажем, представление ссылки на контейнер. Я правильно понял?   -  person Viacheslav Moskalenko    schedule 09.05.2019
comment
@ViacheslavMoskalenko, да, у вас будут фабрики компонентов напрямую, а viewcontainerref принимает фабрику в качестве параметра   -  person Max Koretskyi    schedule 12.05.2019
comment
@MaxKoretskyiakaWizard, понял. Я попробую это, спасибо. А пока предлагаю оставить вопрос открытым.   -  person Viacheslav Moskalenko    schedule 12.05.2019