Angular: 遅延ロードされたNgModuleのエントリコンポーネントは、モジュールの外部では使用できません

作成日 2017年02月06日  ·  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

@DzmitryShylovichによって要求された#12275のフォローアップの問題

現在の動作
遅延ロードされたNgModuleのentryComponentsは、 ComponentFactoryResolverを使用してレンダリングできません。 エラーメッセージ: No component factory found for {{entryComponent}}

予想される行動
entryComponentsは、モジュールがインポートされた場合と同じように使用可能である必要があります

指示による問題の最小限の再現
http://plnkr.co/edit/9euisYeSNbEFrfzOhhzf?p=preview

内部で使用するものと同様の簡単なセットアップを作成しました。 Mainコンポーネントは、 Typeをレンダリングするメソッドを提供します。 EntryComponentは、 Page1ModuleentryComponentとして宣言されています。 ただし、 Page1Moduleをロードした後、 ComponentFactoryResolver $を介して$ EntryComponentをレンダリングしようとすると、 No component factory found for EntryComponentがスローされます。

行動を変える動機/ユースケースは何ですか?
ルートレベルで子モジュールのentryComponentsをレンダリングします。 このアプローチのユースケースは次のとおりです。

  • すべての上にレンダリングされるモーダル
  • 通知

あなたの環境について教えてください:
現在Angular2.1.1を使用していますが、これは最新バージョンのAngular(2.4.6)にも影響します(plnkrを参照)。

  • 言語: TypeScript ^ 2.0.0
core feature

最も参考になるコメント

この場合、この問題を機能リクエストに変更します。

IMOのこれらのユースケースは、DOMに沿って手動で移動するのではなく、Angular自体で処理する必要があります。

全てのコメント122件

いくつかの調査の後、私はあなたの作品の中で設計通りにそれを見つけました。
エントリコンポーネントは、遅延ロードされたモジュールのプロバイダーと同じルールに従います。 https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#! #q-lazy-loaded-module-provider-visibility
それらは、遅延ロードされたモジュール内でのみ表示されます。

さて、これが意図された動作である場合、上記のユースケースのような特定のタスクをどのように達成できますか?

メインモジュールを爆破することは適切な解決策ではないと思います。

MainComponentでエントリコンポーネントを使用する場合は、 AppModuleで提供する必要があります

それで、あなたの解決策は確かにすべてをメインモジュールに移動することですか?

IMOこれは、機能ブロックを1つのモジュールにグループ化できるAngularのモジュール概念を完全に破ります。

これは基本的に、モーダルのようなものを遅延ロードアーキテクチャで使用することは不可能であることを意味します。

マテリアルのhttps://material.angular.io/components/component/dialogに類似したAPIを使用する必要があります
その後、それは動作するはずです

うわー、あなたの意見は、Angular Materialのように、DOMコンテンツを移動する必要があるということです。 ここを参照してください: https ://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts#L30 -L55

これがAngularでこれを処理するための本当にベストプラクティスである場合、基本的にAngularをダンプして、JQueryの使用を再開できます。

申し訳ありませんが、私はこのアプローチを真剣に受け止めることはできません。

モーダルと遅延読み込みの最善のアプローチはわかりませんが、とにかくプランカーでは意図したとおりに機能し、ここに角度のあるバグはありません。
モーダルについての議論には、gitterやstackoverflowなどのサポートチャネルを使用することをお勧めします

この場合、この問題を機能リクエストに変更します。

IMOのこれらのユースケースは、DOMに沿って手動で移動するのではなく、Angular自体で処理する必要があります。

今日同じ問題に直面しました-遅延ロー​​ドされたモジュールで定義されたモーダルのコンポーネントは、アプリのモーダルサービスによって処理されません( entryComponentsの使用にもかかわらずそれを見つけることができません)。 レイジーモジュールの構造と使用ロジックを壊すメインバンドルに移動することを余儀なくされました:confused:

そこに同じ問題があります...レイジーモジュールロジックを壊さずにlazyLoadedモジュールでentryComponentsを使用することは不可能です:私のLazyLoadedモジュールは私のAppModuleで宣言されたentryComponentに依存しています:(

この同じ問題に遭遇しましたが、ng-bootstrapモーダルソースコードを調べた後、解決に至りました。 基本的に問題は(私の理解では、間違っている場合は訂正してください)、アプリがブートストラップされたときにレイジーモジュールが初期インジェクターに含まれず、一度設定すると初期インジェクターを変更できないことです。 これが、OPでエラーが発生する理由です。 レイジーモジュールの1つがロードされると、レイジーモジュールで宣言または提供するものへの参照を含む独自のインジェクターを取得します。 そうは言っても、で動的コンポーネントを作成する場所が正しいinjectorcomponentFactoryResolverを参照している限り、遅延ロードされたモジュールの外部で実際にentryComponentを参照できます。

そこで、私が行ったのは、作成したい動的コンポーネントのinjectorcomponentFactoryResolverを格納するシングルトンサービスを作成することでした。 このサービスは、レイジーモジュールの外部にある必要があります。 次に、コンポーネントを動的に作成するときはいつでも、このサービスを呼び出して必要なものを取得できます。

以下のコードは、レイジーモジュールの外部で動的コンポーネントを作成する場所になります

@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) {}

これが意味をなさない場合はお知らせください。さらに詳しく説明します。 編集Angular2.4.9を使用していることを忘れました。

@jmcclanahanに感謝します。 そのサービスがどのように見えるか、そしてそれがどのように設定されているかについてもう少し教えていただけますか? 私は同様の問題に苦しんでいます。ある遅延ロードされたモジュールのコンポーネントを別の遅延ロードされたモジュールのコンポーネントに動的にロードしようとしていますが、(私は)エントリコンポーネントを適切な場所に追加したにもかかわらず、していません。 ありがとう

@ gcorgnet-もちろんです。 つまり、私のユースケースでは、アプリのすべてのページに共通のツールバーがありますが、ツールバー内に、表示しているページに基づいて追加機能用のボタンとツールを追加したいと思いました。 これを実現するために、これらのボタン/ツールとそのロジックを保持するコンポーネントを作成し、これらのコンポーネントを関連付けられているモジュール内に保持したいと考えました。 したがって、基本的に、モジュールの外部では、これらの特定のツールバー機能について何も知りませんが、共通のツールバーに表示することはできます。 以下は、私があなたを助けることを願っている私の実用的な解決策です:

ツールバーサービスで行っているのは、オブザーバブルを作成することだけです。そのため、レイジーコンポーネントは、 componentFactoryResolverinjectorの参照を、 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 });
    }
}

遅延ロードされたコンポーネントで、 componentFactoryResolverinjectorを挿入し、ツールバーサービスでイベントを発生させます。

遅延ロードされたツールバーコンポーネント

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

最後に、コアツールバーコンポーネントで、ツールバーサービスのobservableをサブスクライブしているので、イベントが発生するたびに、必要なレイジーコンポーネントを挿入して作成しようとします。 作成した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コードについて1つの質問(おそらくばかげた質問)があります。「遅延ロードされたツールバーコンポーネント」について言及するとき、サービス呼び出しコードをngOnInitセクションに配置しますが、どのようにしてそれ(コンポーネント)を初期化できますかHTMLコードに追加して、後でDOMで非表示にする必要はありませんか? そのコンポーネントはトップバーでのみ使用されるため、以前は他の場所にロードしませんでした。

@ Dunos-上記の例からその部分を完全に除外していることに気づきました。申し訳ありません。 動的コンポーネントを、関連付けられている遅延ロードされたモジュールにentryComponentとして追加する必要があります。

次に、ツールバーコンポーネント自体で、動的コンポーネントを表示するhtmlテンプレート内の場所を参照するViewContainerRefを定義します。 次に、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 (私は思う)が、ngOnInitが呼び出されない(または私がしない)ため、動的コンポーネント( this.toolbarService.receiveContext呼び出し部分)からサービスへの呼び出しがどのように行われるかをまだ理解していませんtその方法を参照してください)。 entryComponentsに追加しましたが、onInitを機能させるには、いくらか初期化する必要があります。

@ Dunos-たぶん例が一番でしょう。 アプリのインデックスページからページAに移動しているとします。ページAに対応するルートがあり、ページAのモジュール/コンポーネントを定義します。 ページAには、グローバルツールバーに表示する別のコンポーネントも定義されています。 したがって、ページAのメインコンポーネント内で、 ComponentFactoryResolverInjectorを挿入してから、 receiveContextイベントを発生させます。 このコードのビット:

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

申し訳ありませんが、それがどのように混乱する可能性があるかがわかりました。 ツールバーに表示するコンポーネントではなく、現在のページに関連付けられているコンポーネントにそのコードを配置します。

@jmcclanahanああ完璧、どうもありがとう!!! 今それが動作するようになりました:)

@jmcclanahanこの投稿を見つける前に、同じ問題を解決しました。 私が気づいたことの1つは、関数宣言に数文字を保存したい場合は、渡したインジェクターを使用してComponentFactoryResolverへの参照を取得できることです。 あなたはただすることができます:
injector.get(ComponentFactoryResolver);

そして、それはあなたが必要とする参照を返すはずです。 ただし、最終的には、ComponentFactoryResolverを関数のパラメーターとして渡すのと同じ結果が得られます。

これで私の問題も解決します。

つまり、レイジーモジュールで宣言されたコンポーネントを動的に作成する場合は、そのレイジーモジュールで宣言されたコンポーネントから実際にComponentFactoryResolverを使用する必要があるということだけを知っておく必要があります。 ルートインジェクター(ルートComponentFactoryResolverを含む)は、ロードされたlazyModuleで宣言されたentryComponentsを認識しません。

ちょっと奇妙に見えますが、これがAngularの設計方法です(これは理にかなっています)が、フレームワークを操作するときに実際にはさらに複雑になります。

Angularチームがこれをどのように活用できるか:ルートがアクティブ化されるたびに、ルートインジェクターはそのlazyModuleにリストされているentryComponentsを自動的に追加し、ルートが変更されるたびに、以前に追加されたentryComponentsを自動的に破棄する必要があります。これにより、複雑さが軽減され、過剰なフラッディングが回避されます。追加のコンポーネントファクトリを備えたルートインジェクター。

このための機能リクエストを送信します。

ありがとう!

どうもありがとう@jmcclanahan

@jmcclanahanどうもありがとう! @renatoaraujocこの機能をAngularチームに提出しましたか?

@rrayaこの「問題」に関する機能リクエストを送信しました(考えてみれば実際には問題ではありません)。#17168を参照してください。

これに追加するだけです。 私のユースケースは、AppModuleにModalManagerServiceを作成しました。このメソッドには、アプリの他のすべての部分がモーダルを開くために呼び出すopenメソッドがあります。 コンポーネントが渡され、モーダルとして開かれます。 また、ユーザーがモーダルを閉じる以外のアクション(セッションタイムアウトによるルート変更など)によってモーダルを閉じる必要がある場合に使用されるcloseAllメソッドもあります。

@jmcclanahanしかし、私はまだあなたのことを理解していません、いくつかの提案があればどうもありがとう。'this.componentFactoryResolver.resolveComponentFactory 'でエラーが発生したようです

@Dunosは私に手を差し伸べることができますか?私は何週間も同じ問題に苦しみました、そしてそれは本当に頭痛の種です。

@werts

理解する必要があるのは、遅延ロードされたルートに移動するたびに、アクティブ化されたルートが、そのlazyModuleのentryComponentsを含む子インジェクターを生成することです。 AppModule(アプリケーションのルートインジェクター)でグローバルモーダルサービスが定義されている場合、componentFactoryResolverはlazyModuleからの新しいentryComponentsを認識しません。 したがって、この問題を解決する方法の1つは、lazyModuleのコンポーネントからcomplementFactoryResolverのインスタンスを取得し、どういうわけかmodalServiceに「このインジェクター/ componentFactoryResolverを使用して必要なentryComponentをレンダリングする」ように指示することです。

理解するのは少し難しいようですが、そうではありません。

@renatoaraujoc説明してくれてありがとう、私のユースケースでは、リンクからコンポーネントをロードしたい、つまり、左側のサイドバーからメニューをクリックすると、レイジーロードされたモジュールからモジュールを取得し、次に作成します動的コンポーネント、タブのようなアプリを作成したいので、右側のメインコンテンツ領域にいくつかのコンポーネントコンテンツがあります。タブトリガーをクリックすると、コンテンツが表示されます。または、ソリューション作成タブがあります- angle2 / 4経由のアプリのように?

とにかく、lazyloadコンポーネントを初期化することはできません。どのようにしてサービスにそのcomponentFactoryResolverを伝えることができますか?

私のアプリにはAppModuleがあり、CoreModuleが含まれているため、ContentTabModule(タブセットの読み込みに使用)はCoreModuleに属し、ContentTabModuleによって処理されるすべてのコンポーネントを作成し、メニューをクリックするとサービス、コンポーネント、ディレクティブ、パイプがありますリンクをクリックすると、ContentTabServiceはメニューparamsから動的コンポーネントを作成します。ネットワークパネルにロードされたトランク(webpackを使用)を確認しました。次に、lazyloadモジュールでデモンストレーションされたコンポーネントを作成します。

@renatoaraujoc lazyModuleのコンポーネントからcomplementFactoryResolverのインスタンスを取得するにはどうすればよいですか?lazymoduleのコンポーネントが初期化されることはないため、はるかに難しいと思います(または私は間違っています)

@wertsは、上記の@jmcclanahanとの会話を確認します。ツールバーと同様のことについて話しました。

@Dunos @jmcclanahan例を理解できません、私は同じように苦しんでいると思います。lazyloadコンポーネントは決して初期化されません。
this.toolbarService.receiveContext()がlazyloadモジュールから呼び出されましたか?

@wertsは、レイジーモジュールのメインコンポーネントにルートをロードする必要があります。そのコンポーネントは、インジェクターとComponentFactoryResolverを使用してauxサービスを呼び出すコンポーネントです。

@Dunosありがとうございます。他のユースケースで便利な場合は、リンククリックからモジュールをレイジーロードしてから動的コンポーネントを作成したい場合は、私に連絡してください。可能ですか?

@werts ...はい、可能です。 開始点については、 https://github.com/Toxicable/module-loadingを参照してください。

OK、今すぐ試してみてください、thx。 @ mlc-mlapis

@werts et al:このサポートスレッドを別のチャネルに移動することをお勧めしますか? StackOverflowまたはGitterの方が適している場合があります。 現状では、私はこの問題を購読しています(他にもたくさんあると思いますが)。現在、多くのノイズが発生しており、少し話題から外れています。

プラス面:このような役立つコミュニティを見るのは素晴らしいことです!

この問題が、遅延ロードされたモジュールで直面しなければならないもう1つの障害でもあるとは信じられません。 私は、レイジーモジュールの子をその親(app.module)から匿名にして、必要に応じてレイジーモジュールを簡単に削除/エクスポートできるようにするための解決策を必死に探していました。 app.component.htmlのアプリ全体の<router-outlet name="app-popup">のような単純なもの(ComponentFactoryがその仕事を置き換えることができることを期待してあきらめました)では、遅延子コンポーネントをロードするだけで非常に困難です。 今だけ、lazy-modulesentry-childrenのentry-componentsの問題に遭遇します。 怠惰な子コンポーネントを登録するためにルートモジュールが必要な場合、モジュールを適切に分離しておくことは不可能です。 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独自のルーターの複数のアウトレット機能でさえありますか? 彼らは舞台裏でこの論理を実行しますか? これはAngular1.xでの私にとって非常に一般的なケースであり、Angular2 +に移行しようとしています。

ツールバーの例では、ui-view(おそらく名前付きビュー、または角度ルーターの場合はルーターアウトレット)は、より高いレベルのモジュールでセットアップされます。 遅延ロードされたモジュールの状態がツールバーのui-viewを独自のコンテンツに置き換える場合、それは単に「機能」する必要があります。そうしないと、現在のモジュールの外部のより高いレベルのビューを置き換える必要がある遅延ロードされた環境でui-routerが機能しません。 。 これは、Angular1.xと比較して非常に制限されます。

上記の例は、ui-router / angularrouterが解決するように設計されているのと同じことを解決しているようです。 私は何かが足りないのですか?

...私たちはおそらく何が問題なのかをまったく失っています。 私たちが知っていて毎日使用しているように、遅延ロードされたモジュールのコンポーネントを使用するのに問題はありません...ルーター経由またはプログラムで独自のコードを使用する場合でも。

上記の@jmcclanahanの例を使用すると、私のユースケースではうまく機能するようです。

this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);. $の後に$ this.componentRef.changeDetectorRef.markForCheck();を追加する必要がありました

これにより、HostBindingプロパティがトリガーされないという問題が修正されました。

私は自分のために働いた潜在的な解決策に挑戦するだろうと思いました。 これは元のプランカーのフォークです。
http://plnkr.co/edit/x3fdwqrFDQr2og0p6gwr?p=preview

これを回避するには、entryComponent(Page1Module)を持つモジュールで提供されるサービス(ResolverService)からコンポーネントファクトリを解決する必要があります。 次に、これは、解決されたファクトリを使用してコンポーネントをMainComponentのビューに追加する2番目のサービス(ChangerService)に渡されます。

私が真剣に理解していないのは、このなんてこったルーターです。 このプランカーを確認してください( @patkoellerの例から分岐)
http://plnkr.co/edit/keMuCdU9BwBDNcaMUAEU?p=preview @ src/page1.ts

基本的に私がやりたいのは、トップレベルアプリにコンポーネントを挿入し、 [routerLink]="['./', 'sub', 'component']のような遅延読み込みモジュールを定義するコマンドで[routerLink]ディレクティブを使用して/page-1/sub/component $を生成することです。 インジェクターを$# ActivatedRoutecreateComponentに渡すと、私の場合は他のすべてのrouterLinkが台無しになりますが、 router.routerState.snapshotstateに再割り当てすると

バグを報告する必要がありますか?

ダイアログシングルトンサービスのエントリコンポーネントで同じ問題に直面しています。 機能関連のモーダルコンポーネントは、 @ jmcclanahanの回避策を必要とせずに、それぞれのモジュールに残しておく必要があります。

これに関する新しいETAはありますか? または、少なくともAngularチームからの応答ですか? モジュールのカプセル化がウィンドウの外に投げ出されたばかりなので、この機能がないことは本当の欠点です。 @ angle / 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でこれを処理するためのより良い方法はありますか?
ありがとう

こんにちは、みなさん、

同じ問題に直面しているだけで、この回避策をソリューションのブートストラップとして使用しています。

これをさらに進めてグローバルサービスの必要性を減らす方法は、RouteConfigLoadEndイベントのloadedConfigプライベート変数を公開することです。 LoadedRouterConfig型のこのプライベート変数には、ロードされたモジュール、そのインジェクター、およびそのcomponentFactoryResolverが含まれます。

これを機能リクエストとして提案するには、新しい問題を開く必要がありますか?
---------編集後

私の質問を気にしないでください、私はこの機能リクエストを見つけましたhttps://github.com/angular/angular/issues/24069

同じ問題があると思いますが、追加のモジュールはlazy loadedではありません。 entryModulesSharedFeatureModule (必要な場合にのみロードされます)からCoreModuleに移動する必要があります。
今では動作します。 しかし、私は機能関連のコンポーネントをコアに移動しましたが、これは私の意見では問題ありません。

この問題はまだ解決されていませんか?

@jmcclanahanのソリューション

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

最後に余分な空白のルートを追加します。 動的なブレッドクラムを作成しようとすると、これが問題になります。

この空白のルートのデフォルトをコンポーネントに追加せずに、レイジーな子モジュールのブートストラップに似たものを使用することを好みます。

これに関する更新はありますか?

カスタムソリューションには、コンストラクターが含まれます。グローバルサイドバーを開く可能性のある各コンポーネントにComponentFactoryResolverInjectorを挿入します。 次に、呼び出し元のコンポーネントは、それらの参照(それ自体)を目的のComponentとともにサービスに渡します。 サービスはこれらのインスタンスを使用してコンポーネントを作成します。 過度に複雑で冗長です。

エントリコンポーネントを独自のサブモジュールに配置し、それをアプリにインポートするだけです。

@broweratcognitecdotcomこの問題のポイントは、遅延ロードされたモジュールentryComponentsにあると思います。 エントリコンポーネントとして使用するすべてのコンポーネントのすべてのサブモジュールをAppModuleに含めると、そのようなすべてのコンポーネントが強制的にロードされ、起動時にパフォーマンスの問題が発生する可能性があります。

これらのコンポーネントは、それらを使用するコンポーネント(コンシューマー)に依存しない必要があることは言うまでもありません。そのため、モジュールは、エントリーコンポーネントとして使用されているのか、通常のコンポーネント(htmlで宣言されている)として使用されているのかを認識できません。そのコンシューマーは知っておく必要があります(したがって、それらをAppModuleに配置すると、なぜそこにあるのかを知るのが難しくなります。他の場所で検索して知る必要があります)。

@lucasbasquerotto ...このような怠惰なモジュールをプログラムでロードします...その場合、完全に制御でき、それだけで機能します。

文字列キーと対応するコンポーネントのマップを使用してルートリゾルバーからコンポーネントを解決できないという奇妙な問題が発生した後、このスレッドに遭遇しました。 _factoriesマップで文字通りコンポーネントを見ることができますが、どういうわけか正しく比較されず、未定義を返します。

@jmcclanahan @Dunos機能モジュールがインジェクター参照を渡す(メインアプリモジュールにコンテキストを登録する)代わりに、正しく解決されたファクトリを実際に返すFactoryResolverなどのサービスを公開するとよいと思いませんか?独自のモジュールのインジェクターへの正しい参照があるため、コンポーネント。 このように、コンポーネントのパブリックAPIを公開するようなものになります。

同様のことをしようとしていますが、モジュールが遅延ロードされておらず、上記の問題にぶつかっています。 理論的には、ここで明らかな何かが欠けていない限り、それは機能するはずですか?

@ h-aroraコンプを見て、同じ問題に気づきました。 _factoriesにありますが、問題は、モジュールに実際にビルドエラーがあり、表示されなかったことです。

#17168からの対処(これの複製としてクローズ)

現在の動作
アプリのRootInjectorによって提供されるComponentFactoryResolverを使用してコンポーネントを動的に作成するシングルトンサービスがあり、レイジーモジュールで宣言されたentryComponentを作成する場合、ルートインジェクターがそれを認識しないため、これは失敗します。

予想される行動
これは、ルートインジェクターが子インジェクターによって認識されているentryComponentsを認識しないことを考慮すると、実際には予期される動作です。

行動を変える動機/ユースケースは何ですか?
Angularのモジュラー設計は正しいものの、複雑さを利用して動的コンポーネントを作成することが動機です。

Angularバージョン:2.4.9

提案された解決策:これはAngular自体が採用したモジュラー設計に反しますが、動的コンポーネントを作成するために提供される複雑さは、LazyModuleのentryComponentsをRootInjectorのentryComponentsに追加するだけで緩和でき、ナビゲートするたびにRootInjectorがフラッディングしないようにすることができると思います(LazyModuleを破壊します)、以前に挿入されたentryComponentsは配列から消去されます。

単に話す:

ページが読み込まれます-> RootInjectorが作成されます。
LazyModuleをアクティブにするナビゲーションを発行します-> ChildInjectorが作成されます。
RootInjector.entryComponents.append(childInjector.entryComponents)
別の場所へのナビゲーションを発行します-> LazyModuleは不要になり、破棄されます。
RootInjector.entryComponents.destroyLastAppendedEntryComponents()
私が何か良いことを提案できることを願っています。

会社のプロジェクトを分解し、遅延読み込みを実装するのに数日を費やしました。 それぞれのモジュールのエントリであるためにモーダルが機能していないことに気付いたのは、最後まででした。 元に戻り、修正を待っています...

これは予定されているものですか、それとも回避策を検討する必要がありますか。 人々が上でやっているように。

@ravtakharこれはバックログにあり、現時点では優先度が割り当てられていないため、回避策を確認することをお勧めします。

会社のプロジェクトを分解し、遅延読み込みを実装するのに数日を費やしました。 それぞれのモジュールのエントリであるためにモーダルが機能していないことに気付いたのは、最後まででした。 元に戻り、修正を待っています...

これは予定されているものですか、それとも回避策を検討する必要がありますか。 人々が上でやっているように。

ここに同じ問題

@tmirun ...プログラムでそのような怠惰なモジュールをロードする場合...あなたは完全な制御を持っています...それはあなたが最終的にモジュール参照を取得してmoduleRef.componentFactoryResolver.resolveComponentFactory(entryComponentType)を呼び出すことができることを意味します

上記の多くと同じ問題があり、appModule内のentryComponentsを含むすべてのentryComponentsを宣言してエクスポートするスタンドアロンモジュールを作成し、それをエクスポートすることで解決しました。これにより、すべてのlazyLoadedモジュールがこれらのコンポーネントをロードします。

私が見つけた他の唯一の実行可能な解決策は、 entryComponentをホストしているモジュールに従ってカスタムComponentFactoryResolverを提供することによってダイアログを生成することでもありました。

注意:rootで提​​供されるシングルトンモジュールはメインモジュールエントリコンポーネントのみを表示することを忘れないでください。そのため、最初の解決策を考え出すことにしました。そうでない場合は、アプリケーションのサブモジュールごとにダイアログスポーナーを作成することを検討してください。そのサブモジュールは、モーダルを生成するサービスに前述のモジュールのComponentFactoryResolverを提供することにより、最終的には他のentryComponentsを持つ共有モジュールを含みます。

...一般的にサポートされているソリューションが利用可能である必要があります(いくつかの新しいAPI + Ivyでのサポート)... Angular7.2でのJasonのプレゼンテーションhttp://www.youtube.com/watch?v=2wMQTxtpvoY&t=131m24sのため。 ..しかし、バージョン7.2が正しく言及されているかどうかはわかりません。

CDKオーバーレイでも同じ問題があります。CDKオーバーレイの上に構築されたギャラリープラグインがありますが、この問題のため、ルートモジュールにインポートする必要があります。

ところで、私の場合の回避策はありますか?

@MurhafSousli ...笑、これは仕様によるものだともう一度言わなければなりません。 物事は100回説明されました...ルートインジェクターが1つあり、遅延ロードされたモジュールは1レベルの深さであるため、ルートレベルからそのコンポーネントに触れることができるとは考えられません。 しかし、何度も言われているように、プログラムでそのモジュールをロードすることができます...そのため、そのモジュール参照を取得します...そしてそのコンポーネントに到達できます。

私もリストに追加してください😄。 コンポーネントを動的にロードするモーダルコンポーネントがありますが、アプリコンポーネントの一部ではないため、すべてのコンポーネントをアプリにロードしたくないので、デザインが悪くなります。 しかし、これをすべて読んだ後、私はそれをしなければならないと思います。

@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 {}

Angular7でテスト済み。

注:私たちのコードはAngular CDKのOverlayを利用していますが、概念は同じです(このスレッドの他の部分によると、問題となるのはInjectorです)。

...一般的にサポートされているソリューションが利用可能である必要があります(いくつかの新しいAPI + Ivyでのサポート)... Angular7.2で

... @ mlc-mlapisは、一般的にサポートされているソリューションを拡張できますか?

@cedvdb ...プレゼンテーションの中で、実際にはソリューションのように見える重要な瞬間について説明しました...これは、遅延ロードされたモジュールをプログラムでロードし、コンポーネント、ディレクティブなど、その一部を使用するためのサポートされた機能を意味します。 ..。。

その方法は今日でも利用可能です...しかしAngularCLIを使用すると、ルーター経由で使用したことがない場合でも、常に必要なルートを定義する必要があります。

おお。 これは非常に予想外でした。

@ angle / cdkポータルとオーバーレイに基づいて独自のダイアログシステムを実装しました: https://stackblitz.com/edit/cdk-dialog-example-p1。 この問題も発生しました。

@CWSpearで言及されている回避策は機能します。機能モジュールでサービスを拡張する必要があり、それでうまくいきました。 なんて変だ!

これに対するよりエレガントな解決策はありますか?

これに対するよりエレガントな解決策はありますか?

私が実験してきたことは、レイジーモジュールレベルでランチャーラッパーを再提供することです。 このラッパーにコンストラクターでインジェクターを取得させ、それを実際のランチャー(ルートから注入される)に渡します。 その1行の修正。 それがどれほど安定しているかはわかりませんが、

@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に感謝します。私は何時間も頭を悩ませていました。あなたの説明は、私の場合の解決策を見つけるのに役立ちました。

私はそれを行う正しい方法は何であるか疑問に思っています。
@ angle / 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メソッドにパッチを適用して独自の実装を呼び出します。 この実装は、最初に登録されたすべてのレイジーモジュールリゾルバーでコンポーネントファクトリを解決しようとし、次にルートアプリリゾルバーにフォールバックします(循環呼び出しを回避するための追加のロジックが少しありますが、それが要点です)。

だから、ええ、かなりひどいハック。 しかし、現在、Angular7で機能します。誰かに役立つかもしれません。

@CWSpearのソリューションに基づいて、既存のPopupServiceをコンポーネントのプロバイダー配列に追加するだけでこれを機能させることができました。 したがって、個別の「機能固有の」PopupServiceを作成する必要はありませんでした。

だからそう:

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

または、PopupServiceを遅延読み込みモジュールのNgModuleのproviders配列に追加することもできます。 例えば:

@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モード用です。 AOTモード( compileModuleAndAllComponentsSyncが使用できない場合)にはimport("../bar/bar.module.ngfactory")をロードする必要があります。

@ 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

AOTJITを使用できます。 私にとっての成功。

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);
});

`` `typescript
import {Compiler、Inject、Injectable、NgModuleFactory、NgModuleRef、Type、ComponentFactory} from '@ angle / core';
import {LoadChildrenCallback} from '@ angle / router';

import {ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN} from './ lazy-component-registry';

@Injectable({
提供: 'ルート'
})
エクスポートクラスLazyComponentLoader {
プライベートmodulesToLoad:マップ;
プライベートmodulesLoading = new Map>();

コンストラクタ(
プライベートmoduleRef:NgModuleRef
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN)elementModulePaths:マップ
プライベートコンパイラ:コンパイラ
){
this.modulesToLoad = new Map(elementModulePaths);
}

load(path:string、selector?:string):Promise{{
if(this.modulesLoading.has(path)){
this.modulesLoading.get(path);を返します。
}

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-mlapisAOTは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ありがとうございましたが、スレッドを離れることはできませんでした! あなたは私の日を救った。
私のコメントを読んでいるすべての人にとって、この素晴らしい答えを見ればあなたの時間を無駄にしないためにあなたを助けるかもしれません。

これはAngular8.2.0ではまだ解決されていないことを確認しました。 OPが2。5年前に作成されて以来、Angularの風景は大きく変化し、Angular Material2はAngularコンポーネントになりました。

他のコメンテーターが示しているように、Angular MaterialはDOMを使用するため、アプリケーション開発者は、アプリケーションコンポーネントが遅延ロードされたサブモジュールのentryComponentsで宣言されている場合でも、MatDialogでアプリケーションコンポーネントをロードするときにDOMを使用する必要はありません。

ただし、ビジネスアプリケーションで同じ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などを提供するだけです。

ivyの実装(9.0.0-next.7でテスト済み)が原因で、この問題はAngular9には存在しないようです。 遅延モジュールの外部から、遅延ロードされたモジュールのコンポーネントを動的に作成できます。 Ivyの動的コンポーネントでは、エントリコンポーネント宣言は不要になったようです。 Ivyは、コンパイラによって生成されていた分離されたコンポーネントファクトリを必要としません。 コンポーネントファクトリ情報は、Ivy(?)を使用してコンポーネント定義に埋め込まれます。 コンポーネントタイプを取得すると、そのコンポーネントファクトリを取得して、そのインスタンスを作成できるようになります。 それが私の印象です。 一粒の塩で私の言葉を取りなさい

あなたがする必要があるのは、すべての動的コンポーネントを宣言し、それらをentryComponentsとしても含む別個のウィジェットモジュールを作成することです。 次に、このモジュールをAppModuleからインポートします。 これにより、エントリコンポーネントがルートインジェクターに確実に登録されます。

このソリューションはクリーンで、レイジーカプセル化を壊しません。

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

}

AppModule:

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

@ pixelbits-mkこれにより、すべての動的コンポーネント(モーダルなど)が積極的に読み込まれますが、ここでのポイントは、アプリの初期読み込みを遅くしないように遅延読み込みできるようにすることです(特にプロジェクトが成長するにつれて) )、それらを呼び出すコンポーネントと同じように。

遅延ロードされたComponentAが動的に遅延ロードされた動的ComponentB $を呼び出すとしましょう。 ComponentAComponentBを呼び出すことを知っている場合は、 ComponentAModule ComponentBModuleをインポートできます。 これは理想的なケースではありませんが、私はそれと一緒に暮らすことができます。

主な問題は(私の意見では) ComponentAComponentBがロードされることを知らないときに発生します。 たとえば、 ComponentBがモーダルで、 ComponentAがサービスのメソッドshowModal() $を呼び出し、サービスがバックグラウンドでComponentBを動的にロードする場合、 ComponentAはそれを知りません。

私が今していることは、 ComponentAComponentBを認識できるように、 showModal(ComponentB)のように、メソッドのパラメーターとしてComponentB参照を含めることです。ロードされますが、それは私の意見では悪いです(メソッドへの各呼び出しはコンポーネント参照を渡す必要があります)。

モジュールを明示的にインポートせずにエントリコンポーネントが利用可能である場合(この問題について私が思うに)、それはそれを解決します(おそらく、AngularはComponentAComponentBをインポートするサービスを呼び出すことを検出する方法を持っている可能性がありますComponentAModuleを暗黙的にインポートしますComponentBModule 、参照がインポートされるコンポーネントのモジュールもインポートするという望ましくない影響を与える可能性がありますが、インポートの理由はそれらを動的に作成することではありません、私はこれをエッジケースとしてもっと見るでしょう、そしてそれで大丈夫でしょう)。

私は(まだ) @jonrimmerの提案をテストしていませんが、それは有望なようです。

ComponentAが表示されている場合にのみ、$ ComponentAComponentBでモジュールを自動的に遅延ロードするのは素晴らしいことです。
ModuleAにはコンポーネントComponentA1ComponentA2があります。
ComponentA1はhtml ComponentB (モジュールModuleB )で参照されますが、 ComponentA2はそうではありません。
ModuleBは、 ComponentA1が表示されている場合にのみ自動的に遅延読み込みされますが、 ComponentA2が表示されている場合は自動的に読み込まれません。

これは、ホームページのダッシュボードのシナリオに完全に適合します。 Dahsboardsはさまざまなモジュールから提供されますが、ダッシュボードが含まれていない場合(たとえば、ユーザー設定を介して)、そのモジュールをダウンロードする必要はありません。

@jonrimmerメインのComponentFactoryResolverのresolveComponentFactory関数をラップし、登録済みの遅延ロードモジュールを前に検索することをお勧めします。

ルートで提供されるサービスを注入するエントリコンポーネントで動作します。 しかし、遅延ロードモジュールでのみ提供されるサービスを注入することはできません。 この場合、StaticInjectorErrorが発生しました。 確認してもらえますか?

私はツタがこの問題を処理しているように見えることを確認したかった。 今日、この問題に遭遇したのは、エントリコンポーネントと共有サービスを含む遅延読み込みモジュールを書き直して、コンポーネントファクトリが見つからなかったことを示す新しいエラーを見つけるためだけにインスタンス化したときです。 そもそもこれが問題だったと読んでショックを受けました。

とにかく、私は既存の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を注入する必要があります

@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をインポートする必要があることを常に知っているからです。

私はこれをAngular8.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 {
}

これでもライブラリ内のentryComponentsが機能しない問題は修正されませんが、少なくとも静的メソッドを使用すると、アプリモジュールはカスタムライブラリ内のエントリコンポーネントについて知る必要がありません。 そのメソッドを呼び出すだけで、ライブラリにリストされているものをすべて取得します。 ライブラリ内のどのコンポーネントがエントリコンポーネントであるかを知ることは、アプリに責任がありません。

@ asmith2306なぜスプレッド演算子を使わないのですか? 醜く見えます

@ asmith2306なぜスプレッド演算子を使わないのですか? 醜く見えます

それはそれで動作しないため。 どこかにバグがあります。

--prodモードでは動作しないようです

それが機能したとしても、それはREALの問題に対処しません。 問題は、NgModuleのentryComponentsにコンポーネントを追加し、そのモジュールがアプリの遅延ロードされたモジュールにインポートされた場合、entryComponentsがアプリのエントリコンポーネントに自動的に登録されることです。 これ以上の作業は必要ありません。 さらに、NgModuleのentryComponents内のコンポーネントは、appモジュールのentryComponentsに自動的に組み込まれる必要があります。 現在、これは当てはまりません。 もしそうなら、私たちはここにいないでしょう。

@broweratcognitecdotcomさまざまな注入レベルからの競合と優先順位についてはどうですか?

@ mlc-mlapisの注文は、私の意見では先着順である必要があります。

@ mlc-mlapis異なるコンポーネントが同じ名前を持っているかどうかということですか? 最初の一致または複数の一致がある場合はエラー。 エントリコンポーネント名は一意である必要があるという要件に対応できます。

@broweratcognitecdotcomこんにちは、はい。 一般原則について話している場合は、すべてのバリアントをカバーする必要があるためです。

@ mlc-mlapis多くの人が克服したい角度の制限は、エントリコンポーネントをインスタンス化して、アプリによってdomに配置することしかできないということだと思います。 アプリは、遅延ロードされたモジュールにインポートしたモジュールが使用するダイアログボックスについて知る必要はありません。 エントリーコンポーネントの現在の概念は、角度のモジュラーパターンを破ります。

Ivy entryComponentsを使用したAFAIKは不要になりますよね? コンポーネントには局所性があるので、それらを動的にインポートするだけで常に機能しますか?

@Airbladerそうですね。 それは歴史へのほんの小さな回想でした。 😄

まだこの問題に苦しんでいる人のために、ここに実際に機能する解決策があります: https ://github.com/angular/angular/issues/14324#issuecomment -481898762

この問題のパッケージを公開しています。 jonrimmerからのコードコピー。
この問題はAngular9には存在しないようです。今すぐ修正する必要がある場合。 @aiao/lazy-componentを使用できます
ここにサンプルコード
https://github.com/aiao-io/aiao/tree/master/integration/lazy-component

これは、ツタを実行しているAngularv9で修正されていると思います。 それでも同様の問題が発生する場合は、最小限の再現シナリオで新しい問題を開いてください。 Thnx!

この問題は、非アクティブのために自動的にロックされています。
同様の問題または関連する問題が発生した場合は、新しい問題を提出してください。

自動会話ロックポリシーの詳細をご覧ください。

_このアクションはボットによって自動的に実行されました。_

このページは役に立ちましたか?
0 / 5 - 0 評価