Angular: Входные компоненты отложенной загрузки NgModule недоступны вне модуля

Созданный на 6 февр. 2017  ·  122Комментарии  ·  Источник: angular/angular

Я отправляю ... (отметьте один знаком "x")

[ ] bug report => search github for a similar issue or PR before submitting
[X] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Дополнение к #12275 по просьбе @DzmitryShylovich

Текущее поведение
entryComponents лениво загруженного NgModule не может быть отображен с использованием ComponentFactoryResolver . Сообщение об ошибке: No component factory found for {{entryComponent}}

Ожидаемое поведение
entryComponents должны быть доступны, как если бы модуль был импортирован

Минимальное воспроизведение задачи с инструкциями
http://plnkr.co/edit/9euisYeSNbEFrfzOhhzf?p=preview

Я создал простую настройку, похожую на ту, что мы используем внутри. Компонент Main предоставляет метод для рендеринга Type . EntryComponent объявляется как entryComponent в Page1Module . Однако после загрузки Page1Module при попытке рендеринга EntryComponent через ComponentFactoryResolver No component factory found for EntryComponent .

Какова мотивация / вариант использования для изменения поведения?
Рендеринг entryComponents дочерних модулей на корневом уровне. Примеры использования этого подхода:

  • Модальные окна, которые должны отображаться поверх всего
  • Уведомления
  • и т.п.

Расскажите, пожалуйста, о вашей среде:
В настоящее время мы используем Angular 2.1.1, но это также влияет на последнюю версию Angular (2.4.6) (см. plnkr).

  • Язык: TypeScript ^ 2.0.0
core feature

Самый полезный комментарий

Хорошо, в этом случае я превращаю эту проблему в запрос функции.

IMO, эти варианты использования должны обрабатываться самим Angular, а не перемещаться по DOM вручную.

Все 122 Комментарий

После некоторого расследования я понял, что в вашем работает так, как задумано.
Компоненты входа следуют тем же правилам, что и провайдеры в модулях с отложенной загрузкой. https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#! #q-lazy-loaded-module-provider-visibility
Они видны только внутри лениво загруженного модуля.

Хорошо, если это предполагаемое поведение, как можно выполнить конкретные задачи, подобные приведенным выше вариантам использования?

Я не думаю, что взорвать основной модуль - правильное решение.

Если вы хотите использовать компонент записи в MainComponent , вы должны предоставить его в AppModule

Итак, ваше решение действительно состоит в том, чтобы переместить все в основной модуль?

IMO, это полностью нарушает концепцию модуля Angular, которая должна позволять группировать функциональные блоки в один модуль.

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

Вам нужно использовать API, аналогичный материалу https://material.angular.io/components/component/dialog .
Тогда это должно работать

Вау, значит, вы считаете, что нам лучше переместить DOM-контент, как это делает Angular Material. Смотрите здесь: https://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts#L30 -L55

Если это действительно лучшая практика для обработки этого в Angular, в основном мы можем сбросить Angular и снова начать использовать JQuery.

Извините, но я не могу воспринимать этот подход всерьез.

Я не знаю, какой подход лучше всего подходит для модальных окон и отложенной загрузки, но в любом случае в вашем plunkr он работает так, как задумано, и здесь нет ошибки angular.
Для обсуждения модальных окон лучше использовать каналы поддержки, такие как gitter и stackoverflow.

Хорошо, в этом случае я превращаю эту проблему в запрос функции.

IMO, эти варианты использования должны обрабатываться самим Angular, а не перемещаться по DOM вручную.

Сегодня столкнулся с той же проблемой - компонент для модального режима, определенный в лениво загруженном модуле, не обрабатывается модальной службой приложения (он не может найти его, несмотря на использование entryComponents ). Вынужден переместить его в основной пакет, что нарушает структуру ленивых модулей и логику использования :confused:

Та же проблема... Невозможно использовать entryComponents в модуле lazyLoaded, не нарушая логику ленивых модулей: мой модуль LazyLoaded зависит от entryComponent, объявленного в моем AppModule :(

Столкнувшись с этой же проблемой, я пришел к решению после просмотра модального исходного кода ng-bootstrap. По сути, проблема (насколько я понимаю, поправьте меня, если я ошибаюсь) заключается в том, что ваши ленивые модули не включаются в начальный инжектор, когда ваше приложение загружается, и первоначальный инжектор не может быть изменен после его установки. Вот почему вы получаете ошибку в OP. Когда загружается один из ваших ленивых модулей, он получает свой собственный инжектор со ссылками на то, что вы объявляете или предоставляете в своем ленивом модуле. При этом, если везде, где вы создаете свой динамический компонент, есть ссылка на правильные injector и componentFactoryResolver , вы действительно можете ссылаться на entryComponent вне лениво загруженного модуля.

Итак, что я сделал, так это создал одноэлементную службу для хранения injector и componentFactoryResolver динамического компонента, который я хочу создать. Этот сервис должен быть вне ваших ленивых модулей. Затем всякий раз, когда я перехожу к динамическому созданию компонента, я могу вызвать эту службу, чтобы получить то, что мне нужно.

Код ниже будет везде, где вы создаете свой динамический компонент за пределами вашего ленивого модуля.

@ViewChild('parent', {read: ViewContainerRef}) parent: ViewContainerRef;
  private componentRef: ComponentRef<any>;
...
const childComponent = this.componentFactoryResolver.resolveComponentFactory(yourComponentRef);
this.refInjector = ReflectiveInjector.resolveAndCreate([{provide: yourComponentRef, useValue: yourComponentRef}], this.injector);
this.componentRef = this.parent.createComponent(childComponent, 0, this.refInjector);

```html

Then in your parent component for your entryComponent you'll inject `injector` and `componentFactoryResolver` and set their values in the shared service:
```js
constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}

Дайте мне знать, если это не имеет никакого смысла, и я могу уточнить. Изменить . Я забыл упомянуть, что использую Angular 2.4.9.

Спасибо @jmcclanahan. Не могли бы вы рассказать нам немного больше о том, как выглядит этот сервис и как он настроен? Я борюсь с аналогичной проблемой, пытаясь динамически загрузить компонент из одного модуля с ленивой загрузкой в ​​компонент из другого модуля с ленивой загрузкой, и хотя (я думаю) я добавил компоненты входа в нужное место, мне говорят, что я нет. Спасибо

@gcorgnet - Конечно. Итак, мой пример использования: у меня есть общая панель инструментов на каждой странице моего приложения, но на панели инструментов я хотел добавить кнопки и инструменты для дополнительных функций в зависимости от того, на какой странице я был. Для этого я создал компонент для хранения этих кнопок/инструментов и их логики, и я хотел сохранить эти компоненты внутри модуля, с которым они связаны. Таким образом, по сути, ничто за пределами моего модуля ничего не знает об этих конкретных функциях панели инструментов, но при этом может отображать их на общей панели инструментов. Ниже приведено мое рабочее решение, которое, я надеюсь, поможет вам:

Все, что я делаю с сервисом панели инструментов, — это создание наблюдаемого объекта, поэтому ваш ленивый компонент может передавать свои ссылки componentFactoryResolver и injector на общий компонент панели инструментов, который прослушивает receiveContext Событие.

панель инструментов.service.ts

@Injectable()
export class ToolbarService {
    contextReceivedSource = new Subject<any>();
    contextReceived$ = this.contextReceivedSource.asObservable();

    receiveContext(componentFactoryResolver: ComponentFactoryResolver, injector: Injector) {
        this.contextReceivedSource.next({ resolver: componentFactoryResolver, injector: injector });
    }
}

В вашем лениво загруженном компоненте вы захотите внедрить componentFactoryResolver и injector и запустить событие в сервисе панели инструментов.

ленивая загрузка компонента панели инструментов

...
constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}
...
ngOnInit() {
  this.toolbarService.receiveContext(this.componentFactoryResolver, this.injector);
}
...

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

...
@ViewChild('toolbarTarget', {read: ViewContainerRef}) toolbarTarget: ViewContainerRef;
component: Type<Component>;
refInjector: ReflectiveInjector;
resolverSub: Subscription;
refInjector: ReflectiveInjector;
componentFactoryResolver: ComponentFactoryResolver;
injector: Injector;

ngOnInit() {
  this.resolverSub = this.toolbarService.contextReceived$.subscribe(resolver => {
      this.componentFactoryResolver = resolver.resolver;
      this.injector = resolver.injector;
    });
}

updateToolbar(data: any) {
    this.clearComponent();
    this.component = data['toolbar'];
    if (this.component) {
      const childComponent = this.componentFactoryResolver.resolveComponentFactory(this.component);
      this.refInjector = ReflectiveInjector
          .resolveAndCreate([{provide: this.component, useValue: this.component}], this.injector);
      this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);
    }
  }

  clearComponent() {
    this.toolbarTarget.clear();
    if (this.componentRef) { this.componentRef.destroy(); };
  }

  ngOnDestroy() {
    this.resolverSub.unsubscribe();
    this.clearComponent();
  }

Затем ваш динамически созданный компонент будет размещен там, где вы поместите соответствующий #toolbarTarget

<div #toolbarTarget></div>

Кроме того, я использую эту строку this.component = data['toolbar']; в компоненте общей панели инструментов, чтобы получить ссылку на лениво загруженный компонент из маршрута. Если вы хотите увидеть полный код для этого, я могу опубликовать его, но это выходит за рамки этого обсуждения.

{ path: '', component: yourComponent, data: { toolbar: yourToolbarComponentToInject } }

Дайте мне знать, если у вас возникнут дополнительные вопросы!

@jmcclanahan У меня есть один вопрос (возможно, глупый) о вашем коде: когда вы упоминаете «ленивую загрузку компонента панели инструментов», вы помещаете код вызова службы в раздел ngOnInit, но как вам удается его инициализировать (компонент) без необходимости добавлять его в код HTML и скрывать позже в DOM? Поскольку этот компонент будет использоваться только на верхней панели, я не загружал его нигде раньше.

@Dunos - я понял, что полностью исключил эту часть из моего примера выше, извините за это! Вы захотите добавить динамический компонент как entryComponent в модуль с ленивой загрузкой, с которым он связан.

Затем в самом компоненте панели инструментов вы определите ViewContainerRef , который будет ссылаться на место в вашем HTML-шаблоне, где вы хотите отображать динамический компонент. Затем метод updateToolbar() позаботится о создании вашего компонента, размещении его в указанном месте и отображении компонента на панели инструментов.

@ViewChild('toolbarTarget', {read: ViewContainerRef}) toolbarTarget: ViewContainerRef;
...
updateToolbar(data: any) {
    this.clearComponent();
    this.component = data['toolbar'];
    if (this.component) {
      const childComponent = this.componentFactoryResolver.resolveComponentFactory(this.component);
      this.refInjector = ReflectiveInjector
          .resolveAndCreate([{provide: this.component, useValue: this.component}], this.injector);
      this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);
    }
  }

Затем ваш динамически созданный компонент будет размещен везде, где вы поместите соответствующий #toolbarTarget

<div #toolbarTarget></div>

Метод clearComponent() вызывается всякий раз, когда запускается ngOnDestroy(), который заботится о сокрытии компонента, когда вы покидаете страницу.

clearComponent() {
    this.toolbarTarget.clear();
    if (this.componentRef) { this.componentRef.destroy(); };
  }

  ngOnDestroy() {
    this.resolverSub.unsubscribe();
    this.clearComponent();
  }

Я также обновил свой оригинальный пост выше. Я получаю много вопросов по этому поводу, поэтому я добавлю рабочий пример с использованием этой динамической панели инструментов, как только смогу, и сообщу вам ссылку.

@jmcclanahan , который я получаю (я думаю), но я до сих пор не понимаю, как вызов службы выполняется из динамического компонента (часть вызова this.toolbarService.receiveContext ), поскольку ngOnInit никогда не вызывается (или я не посмотри как это сделать). Я добавил в entryComponents, но мне нужно немного его инициализировать, чтобы onInit заработал.

@Dunos - Может быть, лучше всего будет пример. Скажем, я перехожу со страницы индекса своего приложения на страницу А. У вас будет соответствующий маршрут для страницы А, который будет определять модуль/компонент страницы А. На странице A также будет определен отдельный компонент, который я хочу отобразить на глобальной панели инструментов. Итак, внутри основного компонента страницы A вы вводите ComponentFactoryResolver и Injector , а затем запускаете событие receiveContext . Этот бит кода:

constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}
...
ngOnInit() {
  this.toolbarService.receiveContext(this.componentFactoryResolver, this.injector);
}

Извините, я видел, как это может сбивать с толку. Вы не помещаете этот код в компонент, который хотите отобразить на панели инструментов, а скорее в компонент, связанный со страницей, на которой вы находитесь.

@jmcclanahan Ах, отлично, большое спасибо!!! Теперь работает :)

@jmcclanahan Я только что решил ту же проблему, прежде чем нашел этот пост. Я заметил одну вещь: если вы хотите сохранить несколько символов в своих объявлениях функций, вы можете получить ссылку на ComponentFactoryResolver с помощью инжектора, который вы передаете. вы можете просто сделать:
injector.get(ComponentFactoryResolver);

И это должно вернуть нужную вам ссылку. Но в итоге это дает тот же результат, что и передача ComponentFactoryResolver в качестве параметра функции.

Это также решает мою проблему.

В общем, все, что вам нужно знать, это то, что если вы хотите динамически создать компонент, объявленный в отложенном модуле, вам нужно фактически использовать ComponentFactoryResolver из компонента, объявленного в этом отложенном модуле. Корневой инжектор (который содержит корневой ComponentFactoryResolver) не будет знать ни о каких entryComponents, объявленных в загруженном lazyModule.

Выглядит немного странно, но так устроен Angular (что имеет смысл), но на самом деле это создает дополнительную сложность при работе с фреймворком, имхо.

Как команда Angular может использовать это: всякий раз, когда маршрут активируется, корневой инжектор должен автоматически добавлять entryComponents, перечисленные в этом lazyModule, и всякий раз, когда маршрут изменяется, ранее добавленные entryComponents должны автоматически уничтожаться, это обеспечит меньшую сложность и предотвратит переполнение. корневой инжектор с дополнительными фабриками компонентов.

Я отправлю запрос функции для этого.

Спасибо!

Большое спасибо @jmcclanahan

@jmcclanahan большое спасибо!! @renatoaraujoc вы представили эту функцию команде Angular?

@rraya Я отправил запрос функции относительно этой «проблемы» (на самом деле это не проблема, если подумать), см. # 17168.

Просто чтобы добавить к этому. Мой вариант использования заключается в том, что я создал ModalManagerService в своем AppModule, у которого есть метод open, который вызывается всеми другими частями приложения для открытия модального окна. Ему передается компонент, который затем открывается как модальный. У меня также есть метод closeAll, который используется, когда модальные окна должны быть закрыты каким-либо действием, кроме того, что пользователь закрывает их, например, изменение маршрута из-за тайм-аута сеанса.

@jmcclanahan, но я все еще не понимаю вашего, большое спасибо, если есть какие-то предложения. Кажется, произошла какая-то ошибка в «this.componentFactoryResolver.resolveComponentFactory»

@Dunos, не могли бы вы помочь мне? Я несколько недель страдал от одной и той же проблемы, и это действительно головная боль.

@вертс ,

Все, что вам нужно понять, это то, что всякий раз, когда вы переходите к ленивому загруженному маршруту, активированный маршрут будет порождать дочерний инжектор, который содержит entryComponents для этого lazyModule. Если у вас есть глобальный modalService, определенный в AppModule (который является корневым инжектором приложения), componentFactoryResolver не будет знать никаких новых entryComponents из любого lazyModule. Итак, один из способов решить эту проблему — получить экземпляр комплементаFactoryResolver из компонента lazyModule и как-то сказать modalService: «Эй, используйте этот инжектор/componentFactoryResolver для рендеринга нужного entryComponent».

Кажется немного сложным для понимания, но это не так.

@renatoaraujoc спасибо за ваше объяснение, в моем случае использования я хочу загрузить компонент по ссылке, то есть я нажимаю меню на левой боковой панели, и я извлеку модуль из лениво загруженного модуля, а затем я создам динамический компонент, потому что я хочу создать приложение, похожее на вкладку, поэтому в правой области основного содержимого есть некоторый компонентный контент, если я нажму на триггер вкладки, содержимое отобразится. или есть ли какая-либо вкладка для создания решения - как приложение через angular2/4?

в любом случае, компонент отложенной загрузки никогда не может быть инициализирован, как я могу сообщить службе его componentFactoryResolver?

для моего приложения есть AppModule, и он содержит CoreModule, поэтому мой ContentTabModule (используемый для загрузки набора вкладок) принадлежит CoreModule, все компоненты создания обрабатываются ContentTabModule, у него есть служба, компонент, директива, канал, при нажатии меню ссылка, ContentTabService создаст динамический компонент из параметров меню. Я видел, как какой-то транк (я использую веб-пакет), загруженный на сетевой панели. Затем я хочу создать компонент, который демонстрировался в модуле отложенной загрузки.

@renatoaraujoc , как я могу получить экземпляр дополненияFactoryResolver из компонента lazyModule? Я думаю, это намного сложнее, потому что компонент lazymodule никогда не инициализируется (или я ошибаюсь)

@werts проверьте разговор, который у меня был с @jmcclanahan выше, мы говорили о чем-то похожем с панелью инструментов.

@Dunos, я не могу понять пример от @jmcclanahan , я думаю, что страдаю тем же. Компонент ленивой загрузки никогда не запускается.
this.toolbarService.receiveContext() вызывается из модуля отложенной загрузки?

@werts вам нужно загрузить основной компонент ленивого модуля с маршрутами, и этот компонент вызывает вспомогательную службу с помощью инжектора и ComponentFactoryResolver.

@Dunos спасибо, не могли бы вы помочь мне, если это удобно для вас, какой-то другой вариант использования, я хочу отложенно загрузить модуль по щелчку ссылки, а затем создать динамический компонент. Возможно ли это?

@werts ... да, это возможно. Посмотрите https://github.com/Toxicable/module-loading для отправной точки.

хорошо, попробуйте сейчас, спасибо. @mlc-mlapis

@werts и др.: Могу ли я порекомендовать вам перенести эту ветку поддержки на другой канал? StackOverflow или Gitter могут быть более подходящими. Как бы то ни было, я подписан на эту тему (как, я уверен, и многие другие), и прямо сейчас она создает много шума, который становится немного не по теме.

Из плюсов: приятно видеть такое полезное сообщество!

Я не могу поверить, что эта проблема также является еще одним препятствием, с которым мне приходится сталкиваться при ленивой загрузке модулей. Я отчаянно искал решение, позволяющее сохранить анонимность дочерних элементов моего ленивого модуля от его родителя (app.module), чтобы я мог легко удалить/экспортировать ленивый модуль, если это необходимо. Для чего-то столь же простого, как приложение <router-outlet name="app-popup"> в app.component.html (от которого я отказался в надежде, что ComponentFactory сможет заменить его работу), просто загружать ленивые дочерние компоненты было очень сложно. Только для того, чтобы теперь столкнуться с проблемой входных компонентов с ленивыми модулями входных детей. Просто невозможно правильно изолировать мои модули, если мне нужен корневой модуль для регистрации ленивых дочерних компонентов. Я просто надеюсь, что команда Angular скоро осознает это как проблему и решит ее. Я все еще жду, когда они ответят на мою проблему с выходом маршрутизатора для ленивых модулей для всего приложения. https://github.com/angular/angular/issues/19520

В начале не было ничего,
«Давайте создадим представление! ", говорит команда jQuery,
«Давайте составим список! ', - говорит команда Angular.

@jmcclanahan теперь у нас есть ngComponentOutlet, это намного упрощает работу.

<ng-container  *ngComponentOutlet="yourComponentClass;injector: yourInjector;"></ng-container>

@crysislinux ngComponentOutlet великолепен, но пока не позволяет нам передавать данные компоненту, поэтому это не решение.

Есть новости об этой функции? знак равно

Я только что столкнулся с этой проблемой. Пробовал пару предложений, безуспешно. У меня все же заработало. На самом деле оказалось, что в моем лениво загруженном модуле отсутствовал импорт другого зависимого модуля — в данном случае модального модуля NGX — но ошибка обвиняла мой компонент в неразрешимости.

Мне любопытно: решается ли эта проблема с помощью ui-router? Или, может быть, даже собственный маршрутизатор angular с несколькими выходами? Они делают эту логику за кулисами? Это очень распространенный случай для меня в angular 1.x, и я пытаюсь перейти на angular 2+.

В примере с панелью инструментов представление пользовательского интерфейса (вероятно, именованное представление или выход маршрутизатора в случае углового маршрутизатора) будет настроено в модуле более высокого уровня. Если состояние модуля с ленивой загрузкой заменяет пользовательский интерфейс панели инструментов своим собственным содержимым, он должен просто «работать», иначе ui-router не будет работать в среде с ленивой загрузкой, где необходимо заменить представления более высокого уровня за пределами текущего модуля. . Это было бы чрезвычайно ограниченным по сравнению с angular 1.x.

Приведенный выше пример выглядит так, как будто он решает то же самое, что и ui-router/angular router. Я что-то пропустил?

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

При использовании приведенного выше примера @jmcclanahan он хорошо работает для моего варианта использования.

Мне пришлось добавить this.componentRef.changeDetectorRef.markForCheck(); после this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);.

Это устранило проблему, с которой не срабатывали свойства HostBinding.

Я решил попробовать потенциальное решение, которое сработало для меня. Это форк оригинального plunkr.
http://plnkr.co/edit/x3fdwqrFDQr2og0p6gwr?p=preview

Чтобы обойти это, вам необходимо разрешить фабрику компонентов из службы (ResolverService), предоставленной в модуле, который имеет entryComponent (Page1Module). Затем он передается второй службе (ChangerService), которая использует разрешенную фабрику и добавляет компонент в представление MainComponent.

Чего я серьезно не понимаю, так это этого проклятого маршрутизатора. Проверьте этот плункер (разветвлен из примера @patkoeller )
http://plnkr.co/edit/keMuCdU9BwBDNcaMUAEU?p=preview @ src/page1.ts

Что я в основном хочу сделать, так это внедрить некоторый компонент в приложение верхнего уровня и использовать директиву [routerLink] с командами, относящимися к ее определяющему ленивому загруженному модулю, например: [routerLink]="['./', 'sub', 'component'] для генерации /page-1/sub/component . Если я передам инжектор на createComponent с некоторыми ActivatedRoute , и это сработает в моем случае, это испортит все остальные маршрутизаторы, но если я переназначу router.routerState.snapshot на state из разрешения и передайте инжектор с этим экземпляром маршрутизатора, похоже, он работает так, как задумано ... очень запутанно!

Должен ли я сообщить об ошибке?

Я столкнулся с той же проблемой с входными компонентами для моего диалогового одноэлементного сервиса. Модальные компоненты, связанные с функциями, должны оставаться в соответствующем модуле без необходимости использования обходного пути @jmcclanahan .

Есть ли новое ETA по этому поводу? Или хотя бы ответ от команды Angular? Отсутствие этой функциональности является настоящим недостатком, поскольку инкапсуляция модуля просто выброшена из окна. Все диалоги для @angular/material необходимо импортировать в корневой модуль.

@ xban1x Я не думаю, что у нас есть какой-то прогресс в этом. Приходится делать обход :(

@jmcclanahan можно ли получить ссылку на введенный компонент, используя ваш подход? Я успешно внедрил компонент, кстати, это дерево, поэтому теперь мне нужно подписаться на события щелчка дерева в компоненте, который внедрил дерево. Есть ли способ сделать это?

В выпуске № 23819 я спрашивал о том, как сделать так, чтобы входные компоненты вели себя так же, как сервисы с providesIn: 'root' . Закрыл эту проблему, потому что кажется, что обе проблемы предназначены для решения одной и той же проблемы. Я надеюсь, что эта проблема будет решена в ближайшее время, потому что это важная функция :)

Обновлять

Из того, что я вижу в неминифицированном коде, когда в сервисе определен providesIn , он регистрирует фабрику, чтобы ее уже можно было использовать (без необходимости импортировать ее в модуль), если я понял правильно:

var Injectable = makeDecorator('Injectable', undefined, undefined, undefined, function (injectableType, options) {
    if (options && options.providedIn !== undefined &&
        injectableType.ngInjectableDef === undefined) {
        injectableType.ngInjectableDef = defineInjectable({
            providedIn: options.providedIn,
            factory: convertInjectableProviderToFactory(injectableType, options)
        });
    }
});

Я предполагаю, что есть способ применить аналогичную логику в компоненте с некоторой опцией, такой как @Component({ ... entryComponent: true }) , чтобы при загрузке чанка, который его содержит, фабрика определялась в распознавателе (должен быть модуль, который включает компонент в entryComponents с зависимостями компонентов, иначе это вызовет ошибку времени компиляции).

@lucasbasquerotto ... мы используем следующую логику для загрузки модуля и использования его типов компонентов (с нашим собственным сопоставлением между строковыми ключами <-> типами компонентов, хранящимися непосредственно в этом модуле):

this._loader.load(url)
    .then((_ngModuleFactory: NgModuleFactory<any>) => {
        const _ngModuleRef: NgModuleRef<any> = _ngModuleFactory.create(this._injector);
        const _cmpType: Type<any> = _ngModuleRef.instance.cmps.get('dynamic');
        const _cmpFactory: ComponentFactory<any> = _ngModuleRef
            .componentFactoryResolver
            .resolveComponentFactory(_cmpType);
        this._cmpRef = this.vc.createComponent(_cmpFactory, 0, this._injector, []);
    });

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

Всем привет,

Просто столкнулся с той же проблемой, и я использую обходной путь в качестве начальной загрузки для своего решения.

Чтобы пойти дальше и уменьшить потребность в глобальной службе, можно было бы открыть закрытую переменную loadingConfig события RouteConfigLoadEnd. Эта частная переменная типа LoadedRouterConfig содержит загруженный модуль, его инжектор и его компонентFactoryResolver.

Должен ли я открыть новую проблему, чтобы предложить это как запрос функции?
--------- После редактирования

Не обращайте внимания на мой вопрос, я нашел этот запрос функции https://github.com/angular/angular/issues/24069.

Я думаю, что у меня та же проблема, но мой дополнительный модуль не lazy loaded . Мне нужно переместить мой entryModules с SharedFeatureModule (который загружается только в случае необходимости) на CoreModule .
Теперь это работает. Но я переместил компонент, связанный с функциями, в ядро, что, на мой взгляд, недопустимо.

Этот вопрос до сих пор не решен?

Решение @jmcclanahan

{ path: '', component: yourComponent, data: { toolbar: yourToolbarComponentToInject } }

Добавляет в конец дополнительный пустой маршрут. Когда я пытаюсь создать динамическую цепочку, это становится проблемой.

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

Есть новости по этому поводу?

Наше специальное решение включает внедрение конструктором ComponentFactoryResolver и Injector в каждый компонент, который может открыть нашу глобальную боковую панель. Затем вызывающий компонент передает эти ссылки (свои) службе вместе с желаемым Component . Служба использует эти экземпляры для создания компонента. Слишком сложно и многословно.

Просто поместите свои входные компоненты в их собственный подмодуль и импортируйте их в приложение и никуда больше.

@broweratcognitecdotcom Я думаю, что смысл этой проблемы в entryComponents в модулях с ленивой загрузкой . Включение всех подмодулей каждого компонента, который вы хотите использовать в качестве компонента входа, в AppModule заставит все такие компоненты загружаться с нетерпением, что может вызвать проблемы с производительностью при запуске .

Не сказать, что эти компоненты должны быть независимы от компонентов, которые их используют (потребители), таким образом, чтобы их модули не знали, используются ли они в качестве входных компонентов или обычных компонентов (объявленных в html), только его потребитель должен знать (поэтому размещение их в AppModule затруднит понимание того, почему они там, вам нужно будет искать в других местах, чтобы узнать).

@lucasbasquerotto ... загружайте такие ленивые модули программно ... тогда у вас есть полный контроль, и это просто работает.

Я наткнулся на этот поток после того, как столкнулся со странной проблемой, из-за которой я не мог разрешить компонент из корневого распознавателя с помощью карты строковых ключей и соответствующих компонентов. Я буквально вижу компонент на карте _factories, но почему-то он не сравнивается правильно и возвращает значение undefined.

@jmcclanahan @Dunos Вам не кажется, что вместо того, чтобы функциональный модуль передавал ссылку на свой инжектор (регистрируя свой контекст в основном модуле приложения), было бы лучше, если бы он предоставлял сервис, скажем, FactoryResolver, который фактически вернет правильно разрешенную фабрику для своего компонентов, так как он будет иметь правильную ссылку на инжектор своего собственного модуля. Таким образом, это будет похоже на раскрытие общедоступного API для ваших компонентов.

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

@h-arora Я заметил ту же проблему, увидев комп. в _factories, но проблема заключалась в том, что на самом деле модуль имел ошибку сборки, которая не отображалась

Копирование от #17168 (закрыт как дубликат этого)

Текущее поведение
Если у вас есть одноэлементная служба, которая использует ComponentFactoryResolver, предоставляемый RootInjector приложения, для динамического создания компонентов, и вы хотите создать entryComponent, объявленный в ленивом модуле, это не удастся, поскольку корневой инжектор не знает об этом.

Ожидаемое поведение
На самом деле это ожидаемое поведение, учитывая, что корневой инжектор не будет знать о компонентах входа, известных дочернему инжектору.

Какова мотивация / вариант использования для изменения поведения?
Мотивация состоит в том, чтобы использовать сложность для создания динамических компонентов, хотя модульный дизайн Angular является правильным.

Угловая версия: 2.4.9

Предлагаемое решение: несмотря на то, что это противоречит модульному дизайну, принятому самим Angular, я думаю, что сложность, предоставляемая для создания динамических компонентов, может быть смягчена простым добавлением entryComponents LazyModule к entryComponents RootInjector и во избежание переполнения RootInjector всякий раз, когда вы выходите из него. (который уничтожает LazyModule), ранее введенные компоненты entryComponents будут стерты из массива.

Просто говоря:

Страница загружается -> создается RootInjector.
Создает навигацию, которая активирует LazyModule -> ChildInjector.
RootInjector.entryComponents.append(childInjector.entryComponents)
Выдает навигацию в другое место -> LazyModule больше не нужен, поэтому уничтожается.
RootInjector.entryComponents.destroyLastAppendedEntryComponents()
Надеюсь, я мог предложить что-то хорошее.

Я потратил пару дней на то, чтобы разобрать проект моей компании и внедрить ленивую загрузку. Только в конце я понял, что наши модальные окна не работают из-за того, что они являются записями в соответствующих модулях. Мы вернулись и ждем исправления...

Это то, что запланировано, или мы должны изучить обходной путь. Как это делают люди выше.

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

Я потратил пару дней на то, чтобы разобрать проект моей компании и внедрить ленивую загрузку. Только в конце я понял, что наши модальные окна не работают из-за того, что они являются записями в соответствующих модулях. Мы вернулись и ждем исправления...

Это то, что запланировано, или мы должны изучить обходной путь. Как это делают люди выше.

Та же проблема ЗДЕСЬ

@tmirun ... если вы загружаете такие ленивые модули программно ... тогда у вас есть полный контроль ... это означает, что вы можете наконец получить ссылку на модуль и вызвать moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponentType)

Была та же проблема, что и у многих выше, и она была решена путем создания автономного модуля, объявляющего и экспортирующего все entryComponents, в том числе в appModule, а также экспортирующего его, чтобы каждый отдельный модуль lazyLoaded загружал эти компоненты.

Единственным возможным решением, которое я нашел, было также создание диалогов путем предоставления пользовательского ComponentFactoryResolver в соответствии с модулем, в котором размещается entryComponent .

ВНИМАНИЕ : помните, что одноэлементные модули, представленные в корневом каталоге, будут видеть только компоненты входа в основной модуль , поэтому я решил придумать первое решение, иначе вы можете подумать о создании диалогового генератора для каждого подмодуля вашего приложения и, в этот подмодуль, в конечном итоге включает общие модули, которые имеют другие entryComponents , предоставляя службе, порождающей модальные окна, ComponentFactoryResolver указанных модулей.

... должно быть доступно общее поддерживаемое решение (некоторые новые API + поддержка в Ivy) ... из-за презентации Джейсона http://www.youtube.com/watch?v=2wMQTxtpvoY&t=131m24s ... в Angular 7.2. .. но я не уверен, что версия 7.2 была упомянута правильно.

У меня такая же проблема с наложением CDK . У меня есть плагин галереи , созданный поверх наложения CDK, но из-за этой проблемы мне приходится импортировать его в корневой модуль.

Кстати, есть ли обходной путь для моего случая?

@MurhafSousli ... лол, я должен еще раз сказать, что это задумано. Вещь была объяснена сто раз ... есть один корневой инжектор, и любой лениво загруженный модуль имеет один уровень глубины, поэтому вы не можете предположить, что можете коснуться его компонентов с корневого уровня. Но, как было сказано много раз, вы можете загрузить этот модуль программно... так что вы получите ссылку на этот модуль... и тогда вы сможете получить доступ к его компонентам.

Добавляйте и меня в список 😄. У меня есть модальный компонент, в котором я динамически загружаю свои компоненты, и я не хочу загружать их все в приложение, что было бы плохим дизайном, поскольку они не являются частью компонента приложения. Но это то, что я должен сделать, я думаю, после прочтения всего этого.

@msarica указал мне сегодня, что если вы создадите лениво загружаемую службу и расширите глобальную службу, открывающую всплывающие окна, а затем используете лениво загруженную службу для открытия всплывающего окна, она будет работать так, как ожидалось:

Глобальный PopupService

@Injectable({ providedIn: 'root' })
export class PopupService {
  constructor(
    private injector: Injector,
    private overlay: Overlay,
  ) {}

  open<T = any>(component: ComponentType<T>) { ... }
}

Конкретная служба лениво загружается только в MyFeatureModule

@Injectable()
export class MyFeaturePopupService extends PopupService {}

Использование в компоненте, который является частью лениво загружаемого MyFeatureModule

@Component({ ... })
export class MyFeatureComponent {
  constructor(
    private myFeaturePopupService: MyFeaturePopupService,
  ) {}

  openPopup() {
    this.myFeaturePopupService.open(MyFeaturePopupComponent);
  }
}

Пример определения лениво загружаемого модуля

@NgModule({
  imports: [MyFeatureRoutingModule],
  declarations: [
    MyFeatureComponent,
    MyFeaturePopupComponent,
  ],
  entryComponents: [MyFeaturePopupComponent],
  providers: [MyFeaturePopupService],
})
export class MyFeatureModule {}

Проверено в Angular 7.

Примечание. В нашем коде используется Overlay Angular CDK, но концепция та же (согласно другим частям этой темы, беспокоит Injector ).

... должно быть доступно общее поддерживаемое решение (некоторые новые API + поддержка в Ivy) ... в Angular 7.2

... @mlc-mlapis, не могли бы вы рассказать, каким будет общее поддерживаемое решение?

@cedvdb ... я только что упомянул важный момент в той презентации, которая на самом деле выглядит как решение ... это означает поддерживаемую возможность программно загрузить лениво загруженный модуль, а затем использовать любую его часть, например, компонент, директиву, ...

Этот способ доступен даже сегодня... но с Angular CLI вам всегда нужно определять необходимые маршруты... даже если вы никогда не используете их через маршрутизатор.

Вот это да. Это было очень неожиданно.

Я реализовал собственную диалоговую систему на основе порталов и оверлеев @angular/cdk: https://stackblitz.com/edit/cdk-dialog-example-p1. Получил и эту проблему.

Обходной путь, упомянутый @CWSpear , работает: мне пришлось расширить службу в моем функциональном модуле, и это сработало для меня. Как странно!

Есть ли более элегантное решение для этого?

Есть ли более элегантное решение для этого?

Что-то, с чем я экспериментировал, - это повторно предоставить оболочку запуска на уровне ленивого модуля. Пусть эта оболочка возьмет инжектор в конструкторе и передаст его реальному лаунчеру (который вводится из корня). Это исправление в одну строку. Хотя я не уверен, насколько он стабилен,

@NgModule({
  declarations: [LazyRootComponent, LazyEntryComponent],
  entryComponents:[LazyEntryComponent],
  providers:[LauncherWrapper],  //<---- reprovision here 
  imports: [
    CommonModule,
    LazyModRoutingModule
  ]
})
export class LazyModModule { }
@Injectable()
export class LauncherWrapper {
    constructor(private injector: Injector,  private launcher:ActualLaucherClass) {
    }

Моя проблема была вызвана тем, что я использовал @Injectable({providedIn: 'root'}) . Изменение его на @Injectable({providedIn: MyModule}) решило это. 😄

Это имеет смысл, потому что сервису нужен доступ к компонентам ввода модулей с отложенной загрузкой. При наличии в корне он не может получить к ним доступ, поскольку модуль временно отключен от корневого инжектора.

Большое спасибо @jmcclanahan и @dirkluijk , я часами ломал голову, и ваше описание помогло найти решение для моего случая.

Мне просто интересно, как правильно это сделать.
Нужен @angular/cdk? Почему ?

Я не думаю, что какой-либо обходной путь является жизнеспособным, если он требует, чтобы код, с которым вы интегрируетесь, знал об этом, потому что существует множество стороннего кода, который просто использует ComponentFactoryResolver , не зная о вашем специальном альтернативном реестре.

Имея это в виду, вот мое «решение»: CoalescingComponentFactoryResolver . Это сервис, который должен предоставляться модулем приложения и инициализироваться в его конструкторе, например:

@NgModule({
 providers: [CoalescingComponentFactoryResolver]
})
class AppModule {
  constructor(coalescingResolver: CoalescingComponentFactoryResolver) {
    coalescingResolver.init();
  }
}

Затем модули с ленивой загрузкой должны внедрить его и зарегистрировать в нем свои собственные экземпляры ComponentFactoryResolver . Вот так:

@NgModule({})
export class LazyModule {
  constructor(
    coalescingResolver: CoalescingComponentFactoryResolver,
    localResolver: ComponentFactoryResolver
  ) {
    coalescingResolver.registerResolver(localResolver);
  }
}

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

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

Так что да, довольно грубый хак. Но это работает прямо сейчас в Angular 7. Может быть, это кому-то пригодится.

Основываясь на решении @CWSpear , я заработал, просто добавив свой существующий PopupService в массив поставщиков компонента. Поэтому мне не нужно было создавать отдельный PopupService для конкретных функций.

Так вот так:

@Component({
  ...
  providers: [PopupService]  //<---- generic PopupService
})
export class MedicationPortletComponent implements OnInit, OnDestroy {
...

Кроме того, вы также можете добавить PopupService в массив поставщиков модуля NgModule с отложенной загрузкой. Например:

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  providers: [PopupService] //<--- here's the PopupService!
})
export class SharedFeaturePatientModule {}

@jonrimmer лучшее решение

@jonrimmer Это минимально навязчиво и идеально подходит для нашего варианта использования. Огромное спасибо от меня и моей команды :)

Мне просто любопытно, как он работает гладко в случае ленивого маршрутизатора, но выдает ошибку, когда я удаляю ленивый маршрут и загружаю модули вручную с помощью ngModuleFactoryLoader.

//Ignore the syntax
CompA {
  openDialog() {//invoked on button click of this comp
    matDialog.open(FilterComponent);//works fine in case of lazy route but throws error when manually loaded
  }
}

ModuleA {//lazy module
  imports: [MatDialog, FilterModule],
  declaration: [CompA]
}

FilterModule {
  declaration: [FilterComponent],
  entryComponent: [FilterComponent]
}

FilterComponent { ...
}

@jmcclanahan @MartinJHItInstituttet @splincode

async showBar() {
    const module = await import("../bar/bar.module");
    const compiled = this._compiler.compileModuleAndAllComponentsSync(
      module.BarModule
    );
    compiled.ngModuleFactory.create(this._injector);

    let factory: any = compiled.componentFactories.find(
      componentFactory => componentFactory.selector === "app-dialog"
    );
    this.modal.show(factory.componentType);
  }

@Jimmysh Это для режима JIT. Вам нужно загрузить import("../bar/bar.module.ngfactory") для режима AOT (где compileModuleAndAllComponentsSync недоступен).

@mlc-mlapis
сильно вдохновлен https://github.com/angular/angular/blob/master/aio/src/app/custom-elements/elements-loader.ts

https://github.com/Jimmysh/ng-lazy-component-load

Он может использовать AOT JIT. успех для меня.

import { InjectionToken, Type } from '@angular/core';
import { LoadChildrenCallback } from '@angular/router';

export const ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES = [
  {
    path: 'aio-foo',
    loadChildren: () => import('../foo/foo.module').then(mod => mod.FooModule)
  }
];

export interface WithCustomElementComponent {
  customElementComponent: Type<any>;
}

export const ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN = new InjectionToken<Map<string, LoadChildrenCallback>>(
  'aio/elements-map'
);

export const ELEMENT_MODULE_LOAD_CALLBACKS = new Map<string, LoadChildrenCallback>();
ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES.forEach(route => {
  ELEMENT_MODULE_LOAD_CALLBACKS.set(route.path, route.loadChildren);
});

```машинопись
import {Compiler, Inject, Injectable, NgModuleFactory, NgModuleRef, Type, ComponentFactory} из '@angular/core';
импортировать {LoadChildrenCallback} из '@angular/router';

импортировать {ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN} из './lazy-component-registry';

@Injectable({
при условии: «корень»
})
класс экспорта LazyComponentLoader {
приватные модулиToLoad: Карта;
приватные модулиЗагрузка = новая карта>();

конструктор(
частный модульСсылка: NgModuleRef,
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths: Карта,
частный компилятор: Компилятор
) {
this.modulesToLoad = новая карта (elementModulePaths);
}

загрузить (путь: строка, селектор?: строка): Обещание{
если (this.modulesLoading.has (путь)) {
вернуть this.modulesLoading.get (путь);
}

if (this.modulesToLoad.has(path)) {
  const modulePathLoader = this.modulesToLoad.get(path);
  const loadedAndRegistered = (modulePathLoader() as Promise<NgModuleFactory<any> | Type<any>>)
    .then(elementModuleOrFactory => {
      if (elementModuleOrFactory instanceof NgModuleFactory) {
        return elementModuleOrFactory;
      } else {
        return this.compiler.compileModuleAsync(elementModuleOrFactory);
      }
    })
    .then(elementModuleFactory => {
      const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
      const factories: Map<any, ComponentFactory<any>> = (elementModuleRef.componentFactoryResolver as any)
        ._factories;
      if (selector) {
        const find = Array.from(factories.keys()).find(type => {
          const factory = factories.get(type);
          return factory.selector === selector;
        });
        if (find) {
          return find;
        } else {
          return Promise.reject(new Error(`not found selector:${selector}`));
        }
      }
      this.modulesToLoad.delete(path);
      return;
    })
    .catch(err => {
      this.modulesLoading.delete(path);
      return Promise.reject(err);
    });
  this.modulesLoading.set(path, loadedAndRegistered);
  return loadedAndRegistered;
}
return Promise.resolve();

}
}

``` typescript
  async showFoo() {
    await this.lazyComponentLoader.load('aio-foo');
    this.modal.show(FooDialogComponent);
  }
  async showFoo2() {
      const aaa = await this.lazyComponentLoader.load('aio-foo', 'app-dialog');
    this.modal.show(aaa);
  }

@Jimmysh ... тогда исправьте. Он использует компилятор только в режиме JIT.

constructor(private compiler: Compiler) {}
...
if (elementModuleOrFactory instanceof NgModuleFactory) {
   return elementModuleOrFactory;
} else {
   return this.compiler.compileModuleAsync(elementModuleOrFactory);
}

@mlc-mlapis AOT будет NgModuleFactory. Вы можете протестировать https://github.com/Jimmysh/ng-lazy-component-load

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

Ссылка на репозиторий: https://github.com/Jonathan002/route-master-example
URL-адрес демонстрации: https://jonathan002.github.io/route-master-example/

FWIW, аналогично @CWSpear и @dirkluijk , я удалил providedIn: 'root' из своей службы (определен в лениво загруженном модуле и отвечает за открытие entryComponent ) и вместо этого указал службу в providers моего ленивого определения модуля. Я считаю, что самым большим/единственным недостатком этого является то, что сервис больше не может трястись деревом.

Использование providedIn: MyLazyModule приводило к предупреждениям о циклических зависимостях.

@jonrimmer Я не мог покинуть тему, не поблагодарив вас!! ты спас мой день.
Для всех, кто читает мой комментарий, это может помочь вам не тратить свое время, если вы посмотрите на этот замечательный ответ .

Подтверждено, что это еще не решено в Angular 8.2.0. С момента создания OP 2,5 года назад ландшафт Angular сильно изменился, и Angular Material 2 стал Angular Components.

Как указывали другие комментаторы, Angular Material использует DOM, поэтому разработчикам приложений не нужно использовать DOM при загрузке компонента приложения в MatDialog, даже если компонент приложения объявлен в entryComponents подмодуля с ленивой загрузкой.

Тем не менее, выполнение тех же действий с DOM в бизнес-приложении выглядит странно, несмотря на рекомендации Angular по программированию приложений.

Было бы неплохо, если бы запрос функции OP мог быть реализован, чтобы ComponentFactoryResolver мог «видеть» входной компонент, объявленный в лениво загруженном подмодуле, так или иначе. Объявление компонента входа ленивого модуля в app.module работает, но некрасиво.

В дополнение к грязным рабочим решениям, упомянутым выше: 1. DOM, 2. Объявление компонента в app.module, я бы предоставил другое решение, менее грязное, ниже.

3-й раствор, менее грязный.

У меня есть общие службы, используемые несколькими лениво загруженными модулями.

export interface DataComponent {
    data: any;
}

/**
 * Display an NG component in a dialog, and this dialog has not need to answer but close.
 */
@Injectable()
export class DataComponentDialogService {
    modalRef: MatDialogRef<DataComponentDialog>;

    constructor(private dialog: MatDialog) { }

    open(title: string, externalComponentType: Type<DataComponent>, componentData: any, autofocus = true): Observable<any> {
        const isSmallScreen = window.innerWidth < 640 || window.innerHeight < 640;
        this.modalRef = this.dialog.open(DataComponentDialog,
            {
                disableClose: true,
                minWidth: isSmallScreen ? '98vw' : undefined,
                autoFocus: autofocus,
                data: {
                    title: title,
                    externalComponentType: externalComponentType,
                    componentData: componentData,
                    isSmallScreen: isSmallScreen
                }
            });

        return this.modalRef.afterClosed();

    }

}

Эта служба может видеть входные компоненты модулей, не загруженных лениво. У меня есть некоторые компоненты входа, реализованные в некоторых лениво загруженных модулях. Итак, я создал производный класс этой службы, который будет предоставляться в каждом ленивом модуле:

import { DataComponentDialogService } from '../_ui_services/dataComponentDialog.component';

@Injectable()
export class LazyComponentDialogService extends DataComponentDialogService {
    constructor(dialog: MatDialog) {
        super(dialog);
    }
}

Поскольку LazyComponentDialogService предоставляется в том же отложенном модуле, что и компонент отложенной записи, он может видеть этот компонент.

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

Похоже, что проблема больше не существует в Angular 9 из-за реализации ivy (протестировано с 9.0.0-next.7). Вы можете динамически создать компонент лениво загруженного модуля из-за пределов ленивого модуля. Похоже, что объявление компонента входа больше не требуется для динамических компонентов в Ivy. Ivy не нуждается в отдельной фабрике компонентов, которая раньше генерировалась компилятором. Информация о фабрике компонентов встроена в определение компонента с помощью Ivy(?). Когда у вас есть тип компонента, вы сможете получить его фабрику компонентов, а затем создать его экземпляр. Это только мое впечатление. Поверь мне на слово с недоверием

Все, что вам нужно сделать, это создать отдельный модуль виджетов, который объявляет все ваши динамические компоненты, а также включает их как entryComponents. Затем импортируйте этот модуль из AppModule. Это гарантирует, что ваши входные компоненты будут зарегистрированы в корневом инжекторе.

Это решение является чистым и не нарушает ленивую инкапсуляцию.

@NgModule({
  declarations: [
    ModalComponent,
    ... more dynamic components ...
  ],
  entryComponents: [
     ModalComponent,
    ... more dynamic components ...
  ]
})
export class DynamicModule {

}

Модуль приложения:

@NgModule({
   declarations: [
      AppComponent
   ],
   imports: [
      BrowserModule,
      AppRoutingModule,
      BrowserAnimationsModule,
      DynamicModule
   ],
   providers: [],
   bootstrap: [
      AppComponent
   ]
})
export class AppModule { }

@pixelbits-mk Это сделало бы все динамические компоненты (например, модальные и тому подобное) загруженными с нетерпением , но суть здесь в том, чтобы они могли загружаться отложенно , чтобы не замедлять начальную загрузку приложения (особенно по мере роста проекта). ), как и вызывающие их компоненты.

Предположим, что лениво загруженный ComponentA динамически вызывает лениво загруженный динамический ComponentB . Если ComponentA знает, что вызовет ComponentB , вы можете просто импортировать ComponentBModule в ComponentAModule . Это не идеальный случай, но я могу с этим жить.

Основная проблема возникает (на мой взгляд) , когда ComponentA не знает, что будет загружено ComponentB . Например, если ComponentB является модальным, а ComponentA вызывает метод showModal() в службе, а служба динамически загружает ComponentB за кулисами, ComponentA не знал бы этого.

Что я делаю сейчас, так это включаю ссылку ComponentB в качестве параметра в метод, например showModal(ComponentB) , таким образом, что ComponentA теперь знает, что ComponentB будет загружен, но это плохо, на мой взгляд (каждый вызов метода должен передавать ссылку на компонент).

Если бы Entry Components были доступны без необходимости явно импортировать свои модули (о чем, я думаю, эта проблема), это решило бы ее (возможно, у Angular мог бы быть способ определить, что ComponentA вызывает службу, которая импортирует ComponentB и заставить ComponentAModule импортировать неявно ComponentBModule , и хотя это может привести к нежелательному эффекту импорта модулей компонентов, ссылки на которые импортируются, но причина импорта не в том, чтобы создавать их динамически , я бы рассматривал это скорее как пограничный случай, и меня бы это устраивало).

Я еще не проверял (пока) предложение @jonrimmer , но оно кажется многообещающим.

Было бы здорово, если бы ComponentA автоматически лениво загружал модуль с ComponentB , только если отображается ComponentA :
ModuleA имеет компонент ComponentA1 , ComponentA2 .
Ссылка ComponentA1 в html ComponentB (в модуле ModuleB ), но ComponentA2 нет.
ModuleB будет автоматически ленивой загрузкой, только если отображается ComponentA1 , но не если отображается ComponentA2 .

Это идеально подходит для сценария с информационными панелями на главной странице. Панели мониторинга входят в состав множества различных модулей, но если панель инструментов не включена (скажем, через пользовательские настройки), нет необходимости загружать этот модуль.

@jonrimmer Отличная идея обернуть функцию resolveComponentFactory основного ComponentFactoryResolver и выполнить поиск зарегистрированных модулей отложенной загрузки ранее.

Он работает с входными компонентами, которые внедряют службы, предоставляемые в корневом каталоге. Но я не могу внедрить службу, которая предоставляется только в модуле отложенной загрузки. В этом случае я получил StaticInjectorError. Не могли бы вы проверить это?

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

Во всяком случае, я только что включил ivy в своем существующем проекте angular 8.2, и все работает, как и ожидалось.

Мое решение для использования Overlay в модулях с ленивой загрузкой:
Вы должны передать фабричный преобразователь конструктору ComponentPortal , как это

@Injectable()
export class OverlayService {

  constructor(
    private overlay: Overlay,
    private componentFactoryResolver: ComponentFactoryResolver
  ) {
  }

  open<T>(component: ComponentType<T>) {
    const overlayRef = this.overlay.create();

    const filePreviewPortal = new ComponentPortal(component, null, null, this.componentFactoryResolver);

    overlayRef.attach(filePreviewPortal);
  }
}

но вы также должны указать OverlayService в модуле с ленивой загрузкой. OverlayService необходимо внедрить ComponentFactoryResolver модуля, в котором предоставляется OverlayService.

@NgModule({
  declarations: [
     SomeComponent,
  ],
  entryComponents: [
    SomeComponent,  // <-------- will be opened via this.overlayService.open(SomeComponent)
  ],
  providers: [
    OverlayService, // <--------
  ]
})
export class LazyLoadedModule {}

@mixrich Этот подход будет работать [в моем приложении я делаю так, и он работает нормально, пока мне не нужно закрыть все модальные диалоги в каком-то событии, таком как выход из системы], но имейте в виду, что это будет создавать экземпляр OverlayService каждый раз, когда другой модуль импортирует этот модуль, т. е. приложение не будет иметь ни одного экземпляра, как раньше.

Поскольку не будет сигнала службы наложения, НЕ будет легко узнать, сколько модальных диалогов открыто.

Конечно, это требования приложения, но нужно быть осторожным с таким подходом.

@mixrich Этот подход будет работать [в моем приложении я делаю так, и он работает нормально, пока мне не нужно закрыть все модальные диалоги в каком-то событии, таком как выход из системы], но имейте в виду, что это будет создавать экземпляр OverlayService каждый раз, когда другой модуль импортирует этот модуль, т. е. приложение не будет иметь ни одного экземпляра, как раньше.

Поскольку не будет сигнала службы наложения, НЕ будет легко узнать, сколько модальных диалогов открыто.

Конечно, это требования приложения, но нужно быть осторожным с таким подходом.

Например, в моем случае у меня есть ModalDialogModule с предоставленным OverlayService , и когда я вызываю OverlayService.open(SomeComponent) , он также создает для меня шаблон модального окна, вставляет SomeComponent внутри это и возвращает некоторую структуру данных с полезными наблюдаемыми (для событий закрытия и успеха), экземпляром компонента и ручным методом close .
Итак, когда мне нужно использовать модальные окна в моем LazyModule, мне просто нужно импортировать в него ModalDialogModule , чтобы получить возможность использовать OverlayService . Я нашел этот подход удобным, потому что вы всегда знаете, что для использования модального окна вам нужно импортировать ModalDialogModule , как вы всегда знаете, что для использования реактивных форм вам нужно импортировать ReactiveFormModule

У меня это работает в Angular 8.3.6. У меня есть библиотечный модуль с входными компонентами (матовые диалоги), которые не будут загружаться, если я добавлю их в входные компоненты библиотечных модулей. Он говорит, что не может найти фабрику компонентов для MyCustomDialog в журнале консоли, когда я пытался его открыть.

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

@NgModule({
    declarations: [
        MyCustomDialogComponent
    ],
    imports: [
        CommonModule
    ],
    exports: [
        MyCustomDialogComponent
    ]
    // no need to add the dialog component to entryComponents here.
})
export class MyCustomModule {
    static getEntryComponents() {
        const entryComponents = [];
        entryComponents.push(MyCustomDialogComponent);
        return entryComponents;
    }
}

import {MyCustomModule} from 'my-custom-library'; // import library from node_modules

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        BrowserAnimationsModule,
        MyCustomModule
    ],
    providers: [],
    bootstrap: [AppComponent],
    entryComponents: [].concat(MyCustomModule.getEntryComponents())
})
export class AppModule {
}

Я знаю, что это по-прежнему не исправляет неработающие входные компоненты в библиотеке, но, по крайней мере, со статическим методом модуль приложения не должен знать о входных компонентах в пользовательской библиотеке. Он просто вызывает этот метод и получает все, что указано в библиотеке. Приложение не обязано знать, какие компоненты в библиотеке являются компонентами входа.

@ asmith2306 , почему вы не используете оператор распространения? это выглядит некрасиво

@ asmith2306 , почему вы не используете оператор распространения? это выглядит некрасиво

Потому что с ним не работает. Где-то есть ошибка по этому поводу.

похоже, это не работает с режимом --prod

Даже если это сработало, это не решает НАСТОЯЩУЮ проблему. проблема в том, что если я добавляю компонент в entryComponents NgModule, и этот модуль импортируется в лениво загружаемый модуль в моем приложении, то entryComponents должны автоматически регистрироваться в компонентах входа приложений. Не должно быть никакой дополнительной работы. Я бы также сказал, что любой компонент в entryComponents любого NgModule должен автоматически попасть в entryComponents модуля приложения. В настоящее время это не так. Если бы это было так, нас бы здесь не было.

@broweratcognitecdotcom Как насчет конфликтов и приоритетов на разных уровнях инъекций?

По моему мнению, заказ @mlc-mlapis должен быть в порядке очереди.

@mlc-mlapis Вы имеете в виду, имеют ли разные компоненты одинаковое имя? Первое совпадение или ошибка, если совпадений несколько. Я мог бы жить с требованием, чтобы имена компонентов входа были уникальными.

@broweratcognitecdotcom Привет, да. Потому что все варианты должны быть охвачены, если мы говорим об общих принципах.

@mlc-mlapis Я думаю, что ограничение angular, которое многие хотели бы преодолеть, заключается в том, что компоненты входа могут быть созданы и помещены в dom только приложением. Приложению не нужно знать о диалоговом окне, которое использует модуль, который я импортирую в модуль с отложенной загрузкой. Нынешняя концепция начальных компонентов ломает модульный шаблон angular.

Насколько я знаю, с входными компонентами Ivy больше не будет необходимости, верно? Поскольку компоненты будут иметь локальность, поэтому их динамический импорт всегда будет работать?

@Airblader Ты прав, мы знаем. Это было просто небольшое напоминание об истории. 😄

Для тех, кто все еще борется с этой проблемой, вот действительно работающее решение: https://github.com/angular/angular/issues/14324#issuecomment -481898762.

Я публикую пакет для этой проблемы. некоторая копия кода из jonrimmer.
Похоже, что этой проблемы нет в Angular 9. Если вам нужно исправить это сейчас. вы можете использовать @aiao/lazy-component
пример кода здесь
https://github.com/aiao-io/aiao/tree/master/integration/lazy-компонент

Я считаю, что это исправлено в Angular v9 под управлением ivy. Если кто-то все еще сталкивается с подобными проблемами, пожалуйста, откройте новую проблему с минимальным сценарием воспроизведения. Спасибо!

Эта проблема была автоматически заблокирована из-за бездействия.
Пожалуйста, создайте новую проблему, если вы столкнулись с похожей или связанной проблемой.

Узнайте больше о нашей политике автоматической блокировки разговоров .

_Это действие было выполнено автоматически ботом._

Была ли эта страница полезной?
0 / 5 - 0 рейтинги