Angular: Komponen Entri dari NgModule Lazy Loaded tidak tersedia di luar Modul

Dibuat pada 6 Feb 2017  ·  122Komentar  ·  Sumber: angular/angular

Saya mengirimkan ... (centang satu dengan "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

Masalah tindak lanjut untuk #12275 seperti yang diminta oleh @DzmitryShylovich

Perilaku saat ini
entryComponents dari NgModule yang dimuat lambat tidak dapat dirender menggunakan ComponentFactoryResolver . Pesan kesalahan: No component factory found for {{entryComponent}}

Perilaku yang diharapkan
entryComponents harus tersedia seperti jika modul diimpor

Reproduksi minimal masalah dengan instruksi
http://plnkr.co/edit/9euisYeSNbEFrfzOhhzf?p=preview

Saya membuat pengaturan sederhana, mirip dengan yang kami gunakan secara internal. Komponen Main menyediakan metode untuk merender Type . EntryComponent dideklarasikan sebagai entryComponent di Page1Module . Namun setelah memuat Page1Module , ketika mencoba merender EntryComponent melalui ComponentFactoryResolver , No component factory found for EntryComponent dilemparkan.

Apa motivasi / kasus penggunaan untuk mengubah perilaku?
Render entryComponents dari modul anak di tingkat root. Kasus penggunaan untuk pendekatan ini adalah

  • Modal yang harus diberikan di atas segalanya
  • Pemberitahuan
  • dll.

Tolong beritahu kami tentang lingkungan Anda:
Saat ini kami menggunakan Angular 2.1.1 tetapi ini juga memengaruhi versi terbaru Angular (2.4.6) (lihat plnkr).

  • Bahasa: TypeScript ^2.0.0
core feature

Komentar yang paling membantu

Oke, dalam hal ini saya mengubah masalah ini menjadi permintaan fitur.

IMO kasus penggunaan ini harus ditangani oleh Angular sendiri daripada dengan memindahkan barang-barang di sepanjang DOM secara manual.

Semua 122 komentar

Setelah beberapa penyelidikan, saya menemukan bahwa dalam karya Anda seperti yang dirancang.
Komponen entri mengikuti aturan yang sama seperti penyedia dalam modul yang dimuat lambat. https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#! #q-lazy-loaded-module-provider-visibility
Mereka hanya terlihat di dalam modul yang dimuat lambat.

Oke, jika ini adalah perilaku yang dimaksudkan, bagaimana tugas tertentu seperti kasus penggunaan di atas dapat diselesaikan?

Saya tidak berpikir bahwa meledakkan modul utama adalah solusi yang tepat.

Jika Anda ingin menggunakan komponen entri di MainComponent maka Anda harus menyediakannya di AppModule

Jadi solusi Anda memang adalah memindahkan semuanya ke modul utama?

IMO ini benar-benar memecah konsep modul Angular yang seharusnya memungkinkan untuk mengelompokkan blok fungsionalitas menjadi satu modul.

Ini berarti pada dasarnya bahwa hal-hal seperti modals tidak mungkin digunakan dengan arsitektur beban malas.

Anda perlu menggunakan api yang mirip dengan materi https://material.angular.io/components/component/dialog
Maka itu harus berhasil

Wow, jadi menurut Anda sebaiknya kami memindahkan konten DOM, seperti yang dilakukan Angular Material. Lihat di sini: https://github.com/angular/material2/blob/master/src/lib/core/portal/dom-portal-host.ts#L30 -L55

Jika ini benar-benar praktik terbaik untuk menangani ini di Angular, pada dasarnya kita dapat membuang Angular dan mulai menggunakan JQuery lagi.

Maaf, tapi saya tidak bisa menganggap pendekatan ini serius.

Saya tidak tahu apa pendekatan terbaik untuk modals dan pemuatan malas, tetapi bagaimanapun juga di plunkr Anda berfungsi sebagaimana dimaksud dan tidak ada bug sudut di sini.
Untuk diskusi tentang modals lebih baik menggunakan saluran pendukung seperti gitter dan stackoverflow

Oke, dalam hal ini saya mengubah masalah ini menjadi permintaan fitur.

IMO kasus penggunaan ini harus ditangani oleh Angular sendiri daripada dengan memindahkan barang-barang di sepanjang DOM secara manual.

Menghadapi masalah yang sama hari ini - komponen untuk modal yang didefinisikan dalam modul yang dimuat lambat tidak ditangani oleh layanan modal aplikasi (tidak dapat menemukannya meskipun penggunaan entryComponents ). Dipaksa untuk memindahkannya ke bundel utama yang merusak struktur modul malas dan logika penggunaan :confused:

Masalah yang sama di sana ... Tidak mungkin menggunakan entryComponents dalam modul lazyLoaded tanpa merusak logika modul lazy : Modul LazyLoaded saya bergantung pada entryComponent yang dideklarasikan di AppModule saya :(

Mengalami masalah yang sama, saya mencapai resolusi setelah melihat melalui kode sumber modal ng-bootstrap. Pada dasarnya masalahnya (dari pemahaman saya, tolong perbaiki saya jika saya salah) adalah bahwa modul malas Anda tidak termasuk dalam injektor awal ketika aplikasi Anda di-bootstrap dan injektor awal tidak dapat dimodifikasi setelah disetel. Itulah sebabnya Anda mendapatkan kesalahan di OP. Ketika salah satu modul lazy Anda dimuat, ia mendapatkan injektornya sendiri dengan referensi ke apa pun yang Anda nyatakan atau berikan dalam modul lazy Anda. Karena itu, selama di mana pun Anda membuat komponen dinamis memiliki referensi ke injector dan componentFactoryResolver yang benar, Anda memang dapat mereferensikan entryComponent di luar modul yang dimuat lambat.

Jadi, yang saya lakukan adalah membuat layanan tunggal untuk menyimpan injector dan componentFactoryResolver dari komponen dinamis yang ingin saya buat. Layanan ini harus berada di luar modul malas Anda. Kemudian setiap kali saya membuat komponen secara dinamis, saya dapat memanggil layanan ini untuk mendapatkan apa yang saya butuhkan.

Kode di bawah ini akan berada di mana pun Anda membuat komponen dinamis di luar modul malas Anda

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

Beri tahu saya jika ini tidak masuk akal dan saya dapat menguraikan lebih lanjut. Sunting Saya lupa menyebutkan bahwa saya menggunakan Angular 2.4.9.

Terima kasih @jmcclanahan. Bisakah Anda memberi tahu kami lebih banyak tentang seperti apa layanan itu dan bagaimana pengaturannya? Saya berjuang dengan masalah yang sama, mencoba secara dinamis memuat komponen dari satu modul yang dimuat lambat ke dalam komponen dari modul lain yang dimuat lambat dan meskipun (saya pikir) saya telah menambahkan komponen entri di tempat yang tepat, saya diberitahu bahwa saya belum. Terima kasih

@gcorgnet - Tentu. Jadi kasus penggunaan saya adalah, saya memiliki bilah alat umum di setiap halaman aplikasi saya, tetapi di dalam bilah alat saya ingin menambahkan tombol dan alat untuk fungsionalitas tambahan berdasarkan halaman tempat saya berada. Untuk mencapai ini, saya membuat komponen untuk menahan tombol/alat ini dan logikanya dan saya ingin menyimpan komponen ini di dalam modul yang terkait dengannya. Jadi, pada dasarnya tidak ada di luar modul saya yang tahu apa pun tentang fungsi bilah alat khusus ini sambil tetap dapat menampilkannya di bilah alat umum. Di bawah ini adalah solusi kerja saya yang saya harap akan membantu Anda:

Semua yang saya lakukan dengan layanan bilah alat adalah membuat yang dapat diamati, sehingga komponen malas Anda dapat meneruskan referensi componentFactoryResolver dan injector ke komponen umum bilah alat yang mendengarkan receiveContext Acara.

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

Dalam komponen yang dimuat dengan lambat, Anda ingin menyuntikkan componentFactoryResolver dan injector dan menjalankan suatu peristiwa di layanan bilah alat.

komponen toolbar yang dimuat lambat

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

Akhirnya, di komponen toolbar inti saya, saya berlangganan layanan yang dapat diamati di toolbar sehingga kapan saja suatu peristiwa dipicu, ia akan mencoba menyuntikkan dan membuat komponen malas yang saya butuhkan. Penting juga untuk menghancurkan componentRef yang Anda buat jika tidak, Anda akan berakhir dengan kebocoran memori.

...
@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();
  }

Komponen Anda yang dibuat secara dinamis kemudian akan ditempatkan di mana pun Anda meletakkan #toolbarTarget yang sesuai

<div #toolbarTarget></div>

Sebagai tambahan, saya menggunakan baris ini this.component = data['toolbar']; di komponen toolbar umum untuk mendapatkan referensi komponen yang dimuat lambat dari rute. Jika Anda ingin melihat kode lengkap untuk ini, saya dapat memposting itu, tetapi itu di luar cakupan diskusi ini.

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

Beri tahu saya jika Anda memiliki pertanyaan lebih lanjut!

@jmcclanahan Saya punya satu pertanyaan (mungkin pertanyaan bodoh) tentang kode Anda, ketika Anda menyebutkan "komponen bilah alat yang dimuat malas", Anda meletakkan kode panggilan layanan di bagian ngOnInit, tetapi bagaimana Anda mengatur inisialisasi (komponen) tanpa harus menambahkannya ke kode HTML dan menyembunyikannya nanti di DOM? Karena komponen itu hanya akan digunakan di bilah atas, saya tidak memuatnya di tempat lain sebelumnya.

@Dunos - Saya menyadari bahwa saya benar-benar meninggalkan bagian itu dari contoh saya di atas, maaf tentang itu! Anda ingin menambahkan komponen dinamis sebagai entryComponent dalam modul yang dimuat lambat yang terkait dengannya.

Kemudian di komponen toolbar itu sendiri, Anda akan menentukan ViewContainerRef yang akan mereferensikan lokasi di template html tempat Anda ingin menampilkan komponen dinamis. Kemudian metode updateToolbar() akan menangani pembuatan komponen Anda, menempatkannya di lokasi yang ditentukan dan menampilkan komponen pada bilah alat.

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

Komponen Anda yang dibuat secara dinamis kemudian akan ditempatkan di mana pun Anda meletakkan #toolbarTarget yang sesuai

<div #toolbarTarget></div>

Metode clearComponent() dipanggil setiap kali ngOnDestroy() dijalankan, yang menangani penyembunyian komponen saat Anda meninggalkan halaman.

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

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

Saya juga telah memperbarui posting asli saya di atas. Saya mendapat banyak pertanyaan tentang ini, jadi saya akan menambahkan contoh kerja menggunakan bilah alat dinamis ini sesegera mungkin dan memperbarui Anda dengan tautan.

@jmcclanahan yang saya dapatkan (saya pikir), tetapi saya masih tidak mengerti bagaimana panggilan ke layanan dilakukan dari komponen dinamis (bagian panggilan this.toolbarService.receiveContext ) karena ngOnInit tidak pernah dipanggil (atau saya tidak' t lihat bagaimana melakukannya). Saya menambahkan ke entryComponents tetapi perlu menginisialisasinya agar onInit berfungsi.

@Dunos - Mungkin contoh yang terbaik. Katakanlah saya menavigasi dari halaman indeks aplikasi saya ke Halaman A. Anda akan memiliki rute yang sesuai untuk Halaman A, yang akan menentukan modul/komponen Halaman A. Halaman A juga akan memiliki komponen terpisah yang ingin saya tampilkan di toolbar global. Jadi, di dalam komponen utama Halaman A Anda akan menginjeksi ComponentFactoryResolver dan Injector dan kemudian menjalankan event receiveContext . Sedikit kode ini:

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

Maaf, saya bisa melihat bagaimana itu bisa membingungkan. Anda tidak memasukkan kode itu ke dalam komponen yang ingin Anda tampilkan di bilah alat, melainkan komponen yang terkait dengan halaman tempat Anda berada.

@jmcclanahan Ah sempurna, terima kasih banyak!!! Sudah bekerja sekarang :)

@jmcclanahan Saya baru saja memecahkan masalah yang sama sebelum menemukan posting ini. Satu hal yang saya perhatikan adalah jika Anda ingin menyimpan beberapa karakter dalam deklarasi fungsi Anda, Anda bisa mendapatkan referensi ke ComponentFactoryResolver menggunakan Injector yang Anda lewati. Anda hanya dapat melakukan:
injector.get(ComponentFactoryResolver);

Dan itu akan mengembalikan referensi yang Anda butuhkan. Tetapi pada akhirnya memberikan hasil yang sama dengan meneruskan ComponentFactoryResolver sebagai parameter dalam fungsi juga.

Ini memecahkan masalah saya juga.

Singkatnya, yang perlu Anda ketahui adalah ketika Anda ingin membuat komponen yang dideklarasikan dalam modul lazy secara dinamis, Anda harus benar-benar menggunakan ComponentFactoryResolver dari komponen yang dideklarasikan dalam modul lazy tersebut. Injektor root (yang berisi root ComponentFactoryResolver) tidak akan mengetahui tentang entriComponents yang dideklarasikan dalam lazyModule yang dimuat.

Terlihat agak aneh, tetapi ini adalah bagaimana Angular dirancang (yang masuk akal) tetapi ini sebenarnya menciptakan kompleksitas ekstra ketika bekerja dengan kerangka kerja, imho.

Bagaimana tim Angular dapat memanfaatkan ini: Setiap kali rute diaktifkan, injektor root harus menambahkan entriKomponen yang terdaftar di lazyModule itu secara otomatis dan setiap kali rute diubah, entriKomponen yang ditambahkan sebelumnya harus secara otomatis dihancurkan, ini akan memastikan lebih sedikit kerumitan dan menghindari banjir berlebihan injektor root dengan pabrik komponen tambahan.

Saya akan mengirimkan permintaan fitur untuk ini.

Terima kasih!

Terima kasih banyak @jmcclanahan

@jmcclanahan terima kasih banyak!! @renatoaraujoc sudahkah Anda mengirimkan fitur ini ke tim Angular?

@rraya Saya telah mengirimkan permintaan fitur mengenai "masalah" ini (ini sebenarnya bukan masalah jika Anda memikirkannya), silakan lihat #17168.

Hanya untuk menambahkan ini. Kasus penggunaan saya adalah saya telah membuat ModalManagerService di AppModule saya yang memiliki metode terbuka yang semua bagian lain dari panggilan aplikasi untuk membuka modal. Itu akan melewati komponen yang kemudian dibuka sebagai modal. Saya juga memiliki metode closeAll yang digunakan ketika modals perlu ditutup oleh beberapa tindakan selain pengguna menutupnya misalnya perubahan rute karena batas waktu sesi.

@jmcclanahan tapi saya masih tidak mengerti Anda, terima kasih banyak jika ada beberapa saran. Sepertinya beberapa kesalahan terjadi di 'this.componentFactoryResolver.resolveComponentFactory'

@Dunos dapatkah Anda membantu saya? Saya menderita masalah yang sama selama berminggu-minggu, dan itu benar-benar sakit kepala.

@werts ,

Yang perlu Anda pahami adalah setiap kali Anda menavigasi ke rute yang dimuat lambat, rute yang diaktifkan akan memunculkan injektor anak yang berisi entriComponents untuk lazyModule itu. Jika Anda memiliki modalService global yang ditentukan di AppModule (yang merupakan injektor root aplikasi), componentFactoryResolver tidak akan mengetahui entryComponents baru dari lazyModule mana pun. Jadi, salah satu cara untuk mengatasi masalah ini adalah dengan mendapatkan instance ComplementFactoryResolver dari komponen lazyModule dan entah bagaimana memberi tahu modalService "hei, gunakan injector/componentFactoryResolver ini untuk merender entryComponent yang diinginkan".

Tampaknya agak sulit dimengerti tetapi tidak.

@renatoaraujoc terima kasih atas penjelasan Anda, dalam kasus penggunaan saya, saya ingin memuat komponen dari tautan, artinya, saya mengklik menu dari bilah sisi kiri, dan saya akan mengambil modul dari modul yang malas, dan kemudian, saya akan membuat komponen dinamis, karena saya ingin membangun aplikasi seperti tab, jadi, di area konten utama kanan, ada beberapa konten komponen, jika saya mengklik pemicu tab, konten akan ditampilkan. suka aplikasi melalui angular2/4?

lagi pula, komponen lazyload tidak pernah bisa init, bagaimana saya bisa memberi tahu layanan componentFactoryResolver?

untuk aplikasi saya, ada AppModule, dan itu berisi CoreModule, jadi, ContentTabModule saya (digunakan untuk memuat tabset) milik CoreModule,, semua komponen pembuatan ditangani oleh ContentTabModule, ia memiliki layanan, komponen, arahan, pipa, ketika mengklik menu link, ContentTabService akan membuat komponen dinamis dari menu params. saya telah melihat beberapa trunk (saya menggunakan webpack) dimuat di panel jaringan. dan kemudian saya ingin membuat komponen yang didemonstrasikan di modul lazyload.

@renatoaraujoc bagaimana saya bisa mendapatkan instance dari ComplementFactoryResolver dari komponen lazyModule? Saya pikir ini jauh lebih sulit, karena komponen lazymodule tidak pernah init (atau saya salah)

@werts periksa percakapan saya dengan @jmcclanahan di atas, kami berbicara tentang sesuatu yang mirip dengan bilah alat.

@Dunos saya tidak dapat memahami contoh dari @jmcclanahan , saya pikir saya menderita hal yang sama. komponen lazyload tidak pernah init.
this.toolbarService.receiveContext() dipanggil dari modul lazyload?

@werts Anda harus memuat komponen utama modul lazy dengan rute, dan komponen itu adalah komponen yang memanggil layanan aux dengan Injector dan ComponentFactoryResolver-nya.

@Dunos terima kasih, dapatkah Anda membantu saya jika itu nyaman bagi Anda, beberapa kasus penggunaan lainnya, saya ingin memuat modul dari klik tautan, dan kemudian membuat komponen dinamis.

@werts ... ya, itu mungkin. Lihat https://github.com/Toxicable/module-loading untuk titik awal.

ok, coba sekarang, thx. @mlc-mlapis

@werts dkk: Bisakah saya menyarankan Anda membawa utas dukungan ini ke saluran lain? StackOverflow atau Gitter mungkin lebih cocok. Karena itu, saya berlangganan masalah ini (seperti yang saya yakin banyak yang lain) dan itu menghasilkan banyak kebisingan sekarang yang menjadi sedikit di luar topik.

Sisi positifnya: senang melihat komunitas yang sangat membantu!

Saya tidak percaya bahwa masalah ini juga merupakan hambatan lain yang harus saya hadapi dengan modul yang dimuat dengan malas. Saya telah mati-matian mencari solusi untuk menjaga agar anak-anak modul malas saya tetap anonim dari induknya (app.module) sehingga saya dapat dengan mudah menghapus/mengekspor modul malas jika diperlukan. Untuk sesuatu yang sederhana seperti aplikasi <router-outlet name="app-popup"> di app.component.html (yang saya sudah menyerah dengan harapan bahwa ComponentFactory dapat menggantikan tugasnya) hanya untuk memuat komponen anak-anak yang malas sudah sangat menakutkan. Hanya untuk sekarang menemukan masalah komponen entri dengan modul anak entri malas. Hanya saja tidak mungkin untuk menjaga modul saya tetap terisolasi dengan benar jika saya memerlukan modul root untuk mendaftarkan komponen anak-anak yang malas. Saya hanya berharap tim Angular segera menyadari ini sebagai masalah dan menyelesaikannya. Saya masih menunggu mereka untuk menanggapi masalah saya dengan outlet router seluruh aplikasi untuk modul malas. https://github.com/angular/angular/issues/19520

Pada awalnya, tidak ada apa-apa,
'Ayo buat tampilan! ', kata tim jQuery,
'Ayo daftar! ', kata tim Angular.

@jmcclanahan kami mendapat ngComponentOutlet sekarang, itu membuat segalanya lebih sederhana.

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

@crysislinux ngComponentOutlet sangat bagus tetapi belum memungkinkan kami untuk mengirimkan data ke komponen jadi itu bukan solusi.

Ada kabar tentang fitur ini? =/

Saya baru saja menemukan masalah ini. Mencoba beberapa saran, tanpa hasil. Aku membuatnya bekerja, meskipun. Ternyata modul saya yang dimuat dengan malas kehilangan impor modul dependen lain - dalam hal ini modul modal NGX - tetapi kesalahan menuduh komponen saya tidak dapat diselesaikan.

Saya ingin tahu: Apakah masalah ini diselesaikan dengan menggunakan ui-router? Atau mungkin bahkan kemampuan beberapa outlet router angular sendiri? Apakah mereka melakukan logika ini di belakang layar? Ini adalah kasus yang sangat umum bagi saya di sudut 1.x dan saya mencoba mentransisikannya ke sudut 2+

Dalam contoh bilah alat, tampilan ui (mungkin tampilan bernama, atau outlet router jika router sudut), akan diatur dalam modul tingkat yang lebih tinggi. Jika status modul yang dimuat lambat menggantikan tampilan ui bilah alat dengan kontennya sendiri, itu hanya akan "berfungsi" atau ui-router tidak akan berfungsi di lingkungan yang dimuat lambat di mana tampilan tingkat yang lebih tinggi di luar modul saat ini perlu diganti . Itu akan sangat membatasi dibandingkan dengan 1.x sudut.

Contoh di atas sepertinya menyelesaikan hal yang sama dengan yang dirancang untuk dipecahkan oleh ui-router/router sudut. Apakah saya melewatkan sesuatu?

... kita mungkin tersesat apa masalahnya sama sekali. Seperti yang kita ketahui dan gunakan setiap hari tidak ada masalah untuk menggunakan komponen dari modul yang dimuat lambat ... bahkan melalui router atau secara terprogram dengan kode kita sendiri.

Saat menggunakan contoh @jmcclanahan di atas, tampaknya berfungsi dengan baik untuk kasus penggunaan saya.

Saya harus menambahkan this.componentRef.changeDetectorRef.markForCheck(); setelah this.componentRef = this.toolbarTarget.createComponent(childComponent, 0, this.refInjector);.

Ini memperbaiki masalah yang saya miliki dengan properti HostBinding yang tidak terpicu.

Saya pikir saya akan mencoba solusi potensial yang berhasil untuk saya. Ini adalah garpu dari plunkr asli.
http://plnkr.co/edit/x3fdwqrFDQr2og0p6gwr?p=preview

Untuk mengatasinya, Anda perlu menyelesaikan pabrik komponen dari layanan (ResolverService) yang disediakan dalam modul yang memiliki entriComponent (Page1Module). Ini kemudian diteruskan ke layanan kedua (ChangerService) yang menggunakan pabrik yang diselesaikan dan menambahkan komponen ke tampilan di MainComponent.

Apa yang saya benar-benar tidak mengerti adalah router sialan ini. Periksa plunker ini (bercabang dari contoh @patkoeller )
http://plnkr.co/edit/keMuCdU9BwBDNcaMUAEU?p=preview @ src/page1.ts

Apa yang pada dasarnya ingin saya lakukan adalah menyuntikkan beberapa komponen ke dalam aplikasi tingkat atas dan menggunakan direktif [routerLink] dengan perintah relatif terhadap modul yang dimuat malas yang mendefinisikan seperti: [routerLink]="['./', 'sub', 'component'] untuk menghasilkan /page-1/sub/component . Jika saya meneruskan injektor ke createComponent dengan beberapa ActivatedRoute dan berfungsi untuk kasus saya, itu mengacaukan setiap routerLink lainnya, tetapi jika saya menetapkan ulang router.routerState.snapshot ke state dari penyelesaian dan berikan injektor dengan instance router ini tampaknya berfungsi sebagaimana dimaksud ... sangat membingungkan!

Haruskah saya mengajukan bug?

Saya menghadapi masalah yang sama dengan komponen entri untuk layanan tunggal dialog saya. Komponen modal terkait fitur harus tetap berada di modul masing-masing tanpa memerlukan solusi @jmcclanahan .

Ada ETA baru tentang ini? Atau setidaknya tanggapan dari tim Angular? Tidak memiliki fungsi ini adalah penurunan nyata karena enkapsulasi modul baru saja dibuang ke luar jendela. Semua dialog untuk @angular/material perlu diimpor ke modul root.

@ xban1x Saya tidak berpikir kami memiliki kemajuan dengan itu. Kita harus melakukan walkaround itu :(

@jmcclanahan apakah mungkin untuk mendapatkan referensi komponen yang disuntikkan menggunakan pendekatan Anda? Saya telah berhasil menyuntikkan komponen, itu adalah pohon btw, jadi sekarang saya perlu berlangganan acara klik pohon di komponen, yang menyuntikkan pohon. Apakah ada cara untuk menyelesaikan ini?

Dalam masalah #23819 saya bertanya tentang membuat komponen entri berperilaku dengan cara yang sama seperti layanan dengan providesIn: 'root' . Menutup masalah itu karena tampaknya kedua masalah dimaksudkan untuk menyelesaikan hal yang sama. Saya harap masalah ini akan segera diselesaikan karena ini adalah fitur penting :)

Memperbarui

Dari apa yang saya lihat di kode yang tidak diperkecil, ketika providesIn didefinisikan dalam layanan, itu mendaftarkan pabrik sehingga sudah dapat digunakan (tanpa perlu mengimpornya dalam modul), jika saya mengerti benar:

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

Saya berasumsi ada cara untuk menerapkan logika serupa dalam komponen dengan beberapa opsi seperti @Component({ ... entryComponent: true }) sehingga ketika potongan yang berisi itu dimuat, pabrik didefinisikan dalam resolver (harus ada modul yang menyertakan komponen di entryComponents dengan dependensi komponen, jika tidak maka akan menyebabkan kesalahan waktu kompilasi).

@lucasbasquerotto ... kami menggunakan logika berikut untuk memuat modul dan menggunakan jenis komponennya (dengan pemetaan kami sendiri antara kunci string <-> jenis komponen yang disimpan langsung di modul itu):

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, []);
    });

Halo teman-teman, untuk saat ini saya juga menggunakan solusi memiliki kelas tunggal yang mempertahankan referensi ke beberapa penyelesai modul yang dimuat dengan lambat.
Apakah ada cara yang lebih baik untuk menangani ini dengan versi baru Angular ?
Terima kasih

Hai semuanya,

Hanya menghadapi masalah yang sama dan saya menggunakan solusi sebagai bootstrap untuk solusi saya.

Cara untuk melangkah lebih jauh dalam hal ini dan mengurangi kebutuhan untuk memiliki layanan global, adalah dengan mengekspos variabel pribadi LoadConfig dari acara RouteConfigLoadEnd. Variabel pribadi tipe LoadedRouterConfig ini berisi modul yang dimuat, injektornya, dan componentFactoryResolver.

Haruskah saya membuka edisi baru untuk mengajukan ini sebagai permintaan fitur?
--------- Setelah diedit

Jangankan pertanyaan saya, saya menemukan permintaan fitur ini https://github.com/angular/angular/issues/24069

Saya pikir saya memiliki masalah yang sama, tetapi modul tambahan saya bukan lazy loaded . Saya harus memindahkan entryModules saya dari SharedFeatureModule (yang dimuat hanya jika diperlukan) ke CoreModule .
Sekarang bekerja. Tapi saya memindahkan komponen terkait fitur ke inti yang menurut saya tidak ok.

Masalah ini masih belum terselesaikan?

solusi @jmcclanahan

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

Menambahkan rute kosong ekstra ke akhir. Ketika saya mencoba membuat remah roti dinamis, ini menjadi masalah.

Tanpa menambahkan default rute kosong ini ke komponen, saya lebih suka memiliki sesuatu yang mirip dengan bootstrap di modul anak malas.

Ada pembaruan tentang ini?

Solusi kustom kami melibatkan penyuntikan konstruktor ComponentFactoryResolver dan Injector ke setiap komponen yang dapat membuka sidebar global kami. Komponen pemanggil kemudian meneruskan referensi tersebut (miliknya sendiri) ke layanan bersama dengan Component yang diinginkan. Layanan menggunakan instance tersebut untuk membuat komponen. Terlalu rumit dan terlalu bertele-tele.

Cukup masukkan komponen entri Anda ke dalam submodulnya sendiri dan impor ke dalam aplikasi dan tidak di tempat lain.

@broweratcognitecdotcom Saya pikir inti dari masalah ini adalah untuk entryComponents dalam modul yang dimuat malas . Menyertakan semua submodul dari setiap komponen yang ingin Anda gunakan sebagai komponen entri di AppModule akan memaksa semua komponen tersebut dimuat dengan penuh semangat, yang dapat menyebabkan masalah kinerja saat startup .

Bukan untuk mengatakan bahwa komponen tersebut harus agnostik terhadap komponen yang menggunakannya (konsumen), sedemikian rupa sehingga modul mereka tidak boleh tahu apakah mereka digunakan sebagai komponen entri atau komponen normal (dideklarasikan dalam html), hanya konsumennya harus tahu (jadi, menempatkannya di AppModule akan menyulitkan untuk mengetahui mengapa mereka ada di sana, Anda perlu mencari di tempat lain untuk mengetahuinya).

@lucasbasquerotto ... memuat modul malas seperti itu secara terprogram ... Anda memiliki kontrol penuh saat itu, dan itu hanya berfungsi.

Saya menemukan utas ini setelah menghadapi masalah aneh di mana saya tidak bisa menyelesaikan komponen dari root resolver dengan menggunakan peta kunci string dan komponen yang sesuai. Saya benar-benar dapat melihat komponen di Peta _factories tetapi entah bagaimana itu tidak membandingkan dengan benar dan mengembalikan tidak terdefinisi.

@jmcclanahan @Dunos Tidakkah menurut Anda alih-alih modul fitur menyerahkan referensi injektornya (mendaftarkan konteksnya dengan modul aplikasi utama) akan lebih baik jika itu memperlihatkan layanan katakan FactoryResolver yang benar-benar akan mengembalikan pabrik yang diselesaikan dengan benar untuknya komponen karena akan memiliki referensi yang benar ke injektor modulnya sendiri. Dengan cara ini akan seperti mengekspos api publik untuk komponen Anda.

Saya mencoba melakukan sesuatu yang serupa, tetapi modul saya tidak dimuat dengan malas dan saya mengalami masalah yang saya jelaskan di atas. Secara teori, itu akan berhasil, kecuali saya melewatkan sesuatu yang jelas di sini?

@h-arora Saya melihat masalah yang sama, melihat comp. di _factories, tetapi masalahnya adalah modul sebenarnya memiliki kesalahan build, yang tidak muncul

Mengatasi dari #17168 (ditutup sebagai duplikat dari yang ini)

Perilaku saat ini
Jika Anda memiliki layanan tunggal yang menggunakan ComponentFactoryResolver yang disediakan oleh RootInjector Aplikasi untuk membuat komponen secara dinamis dan Anda ingin membuat entriComponent yang dideklarasikan dalam modul lazy, ini akan gagal karena injektor root tidak mengetahuinya.

Perilaku yang diharapkan
Ini sebenarnya adalah perilaku yang diharapkan mengingat injektor root tidak akan tahu tentang entryComponents yang dikenal oleh injektor anak.

Apa motivasi / kasus penggunaan untuk mengubah perilaku?
Motivasinya adalah untuk memanfaatkan kompleksitas untuk membuat komponen dinamis meskipun desain modular Angular benar.

Versi sudut: 2.4.9

Solusi yang diusulkan: Meskipun ini bertentangan dengan desain modular yang diadopsi oleh Angular itu sendiri, saya pikir kompleksitas yang disediakan untuk membuat komponen dinamis dapat dilunakkan hanya dengan menambahkan komponen entri LazyModule ke komponen entri RootInjector dan untuk menghindari membanjiri RootInjector, setiap kali Anda menavigasi keluar (yang menghancurkan LazyModule), entryComponents yang sebelumnya disuntikkan akan dihapus dari array.

Cukup berbicara:

Halaman dimuat -> RootInjector dibuat.
Mengeluarkan navigasi yang mengaktifkan LazyModule -> ChildInjector dibuat.
RootInjector.entryComponents.append(childInjector.entryComponents)
Mengeluarkan navigasi ke tempat lain -> LazyModule tidak lagi diperlukan, sehingga dihancurkan.
RootInjector.entryComponents.destroyLastAppendedEntryComponents()
Semoga saya bisa menyarankan sesuatu yang baik.

Saya menghabiskan beberapa hari memecah proyek perusahaan saya dan menerapkan pemuatan malas. Baru pada akhirnya saya menyadari bahwa modals kami tidak berfungsi karena menjadi entri di modul masing-masing. Kami telah kembali dan menunggu perbaikan...

Apakah ini sesuatu yang dijadwalkan atau haruskah kita mencari solusi. Seperti yang dilakukan orang-orang di atas.

@ravtakhar Anda mungkin ingin melihat solusinya karena ini ada di Backlog dan tidak memiliki prioritas yang ditetapkan saat ini.

Saya menghabiskan beberapa hari memecah proyek perusahaan saya dan menerapkan pemuatan malas. Baru pada akhirnya saya menyadari bahwa modals kami tidak berfungsi karena menjadi entri di modul masing-masing. Kami telah kembali dan menunggu perbaikan...

Apakah ini sesuatu yang dijadwalkan atau haruskah kita mencari solusi. Seperti yang dilakukan orang-orang di atas.

Masalah yang sama disini

@tmirun ... jika Anda memuat modul malas seperti itu secara terprogram ... Anda memiliki kendali penuh, ... itu berarti Anda akhirnya bisa mendapatkan referensi modul dan memanggil moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponentType)

Punya masalah yang sama seperti banyak di atas, dan diselesaikan dengan membuat modul mandiri yang mendeklarasikan dan mengekspor semua entryComponents, termasuk yang ada di appModule dan mengekspornya juga, sehingga setiap modul lazyLoaded akan memuat komponen ini.

Satu-satunya solusi layak lainnya yang saya temukan juga memunculkan dialog dengan menyediakan ComponentFactoryResolver kustom sesuai dengan modul yang menghosting entryComponent .

HATI -HATI: ingat bahwa modul tunggal yang disediakan di root hanya akan melihat komponen entri modul utama , itu sebabnya saya memutuskan untuk memberikan solusi pertama, jika tidak, Anda mungkin berpikir untuk membuat spawner dialog untuk setiap submodul aplikasi Anda dan, di submodule itu, akhirnya menyertakan modul bersama yang memiliki entryComponents lain, dengan menyediakan layanan yang memunculkan modal ComponentFactoryResolver dari modul tersebut.

... solusi umum yang didukung harus tersedia (beberapa API baru + dukungan di Ivy) ... karena presentasi Jason http://www.youtube.com/watch?v=2wMQTxtpvoY&t=131m24s ... di Angular 7.2 . .. tapi saya tidak yakin apakah versi 7.2 disebutkan dengan benar.

Saya memiliki masalah yang sama dengan CDK Overlay , saya memiliki plugin galeri yang dibangun di atas overlay CDK, tetapi karena masalah ini saya harus mengimpornya di modul root.

BTW, apakah ada solusi untuk kasus saya?

@MurhafSousli ... lol, saya harus mengatakan lagi bahwa ini adalah desain. Hal ini dijelaskan ratusan kali ... ada satu root injector dan setiap modul yang dimuat lambat memiliki kedalaman satu level sehingga Anda tidak dapat mengira bahwa Anda dapat menyentuh komponennya dari level root. Tetapi seperti yang dikatakan berkali-kali Anda dapat memuat modul itu secara terprogram ... sehingga Anda akan mendapatkan referensi modul itu ... dan Anda dapat mencapai komponennya kemudian.

Tambahkan saya ke daftar juga . Saya memiliki komponen modal di mana saya memuat komponen saya secara dinamis dan saya tidak ingin memuat semuanya dalam aplikasi yang akan menjadi desain yang buruk karena mereka bukan bagian dari komponen aplikasi. Tapi itulah yang harus saya lakukan setelah membaca semua ini.

@msarica menunjukkan kepada saya hari ini bahwa jika Anda membuat layanan yang dimuat dengan malas dan memperpanjang layanan global Anda yang membuka sembulan, lalu gunakan layanan yang dimuat dengan malas untuk membuka sembulan, itu akan berfungsi seperti yang diharapkan:

Global PopupService

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

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

Layanan Tertentu hanya dimuat dengan malas di MyFeatureModule

@Injectable()
export class MyFeaturePopupService extends PopupService {}

Penggunaan dalam komponen yang merupakan bagian dari MyFeatureModule yang dimuat dengan malas

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

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

Contoh definisi modul yang dimuat dengan malas

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

Diuji di Angular 7.

Catatan: Kode kami memanfaatkan Angular CDK Overlay , tetapi konsepnya sama (menurut bagian lain dari utas ini, Injector yang menjadi perhatian).

... solusi umum yang didukung harus tersedia (beberapa API baru + dukungan di Ivy) ... di Angular 7.2

... @mlc-mlapis dapatkah Anda memperluas solusi yang didukung secara umum?

@cedvdb ... Saya baru saja menyebutkan momen penting dalam presentasi itu yang sebenarnya terlihat seperti solusi ... itu berarti kemampuan yang didukung untuk memuat modul yang dimuat lambat secara terprogram dan kemudian menggunakan bagian mana pun darinya, seperti komponen, arahan, ...

Cara itu tersedia bahkan hari ini ... tetapi dengan Angular CLI Anda selalu perlu menentukan rute yang diperlukan ... bahkan jika Anda tidak pernah menggunakannya melalui router.

Wow. Ini sangat tidak terduga.

Saya menerapkan sistem dialog saya sendiri berdasarkan @angular/cdk portal dan overlay: https://stackblitz.com/edit/cdk-dialog-example-p1. Punya masalah ini juga.

Solusi yang disebutkan oleh @CWSpear berfungsi: Saya harus memperluas layanan di modul fitur saya dan itu berhasil untuk saya. Betapa anehnya!

Apakah ada solusi yang lebih elegan untuk ini?

Apakah ada solusi yang lebih elegan untuk ini?

Sesuatu yang telah saya coba adalah menyediakan kembali pembungkus peluncur di tingkat modul malas. Minta pembungkus ini mengambil injektor di konstruktor, dan meneruskannya ke peluncur asli (yang disuntikkan dari root). Ini adalah perbaikan satu baris. Saya tidak yakin seberapa stabil itu, meskipun,

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

Masalah saya disebabkan karena saya menggunakan @Injectable({providedIn: 'root'}) . Mengubahnya menjadi @Injectable({providedIn: MyModule}) menyelesaikannya. 😄.

Ini masuk akal, karena layanan memerlukan akses ke komponen entri modul yang dimuat lambat. Ketika disediakan di root, itu tidak dapat mengaksesnya karena modul terputus sementara dari injektor root.

Terima kasih banyak kepada @jmcclanahan dan @dirkluijk , saya menggaruk-garuk kepala selama berjam-jam dan deskripsi Anda membantu menemukan solusi untuk kasus saya.

Saya hanya bertanya-tanya apa cara yang tepat untuk melakukannya.
Diperlukan @angular/cdk ? Mengapa ?

Saya tidak berpikir solusi apa pun yang layak jika memerlukan kode yang Anda integrasikan untuk mengetahuinya, karena ada banyak kode pihak ke-3 yang hanya menggunakan ComponentFactoryResolver tanpa mengetahui tentang registri alternatif khusus Anda.

Dengan mengingat hal itu, inilah "solusi" saya: CoalescingComponentFactoryResolver . Ini adalah layanan yang harus disediakan oleh modul aplikasi dan diinisialisasi dalam konstruktornya, seperti:

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

Kemudian, modul yang dimuat lambat harus menyuntikkannya dan mendaftarkan instance ComponentFactoryResolver mereka sendiri dengannya. Seperti:

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

Ketika ini selesai, komponen entri dalam modul yang dimuat lambat harus tersedia dari layanan yang tidak dimuat.

Cara kerjanya: Saat diinisialisasi, ia menyuntikkan ComponentFactoryResolver aplikasi root dan monkey menambal metode resolveComponentFactory untuk memanggil implementasinya sendiri. Implementasi ini pertama-tama mencoba menyelesaikan pabrik komponen pada semua resolver modul-malas yang terdaftar, lalu kembali ke resolver aplikasi root (ada sedikit logika tambahan untuk menghindari panggilan siklik, tapi itulah intinya).

Jadi, ya, peretasan yang cukup menjijikkan. Tapi itu berhasil, sekarang, di Angular 7. Mungkin itu akan berguna bagi seseorang.

Membangun solusi @CWSpear , saya membuatnya berfungsi hanya dengan menambahkan PopupService saya yang ada ke array penyedia komponen. Jadi saya tidak perlu membuat PopupService "khusus fitur" yang terpisah.

Jadi seperti:

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

Atau, Anda juga dapat menambahkan PopupService ke array penyedia dari modul yang dimuat lambat NgModule. Misalnya:

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

@jonrimmer solusi terbaik

@jonrimmer Ini sangat mengganggu dan berfungsi sempurna untuk kasus penggunaan kami. Terima kasih banyak dari saya dan tim saya :)

Saya hanya ingin tahu bagaimana cara kerjanya dengan lancar dalam kasus router yang malas tetapi itu menimbulkan kesalahan ketika saya menghapus rute malas dan memuat modul secara manual menggunakan 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 Ini untuk mode JIT. Anda perlu memuat import("../bar/bar.module.ngfactory") untuk mode AOT (di mana compileModuleAndAllComponentsSync tidak tersedia).

@mlc-mlapis
sangat terinspirasi oleh https://github.com/angular/angular/blob/master/aio/src/app/custom-elements/elements-loader.ts

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

Itu dapat menggunakan AOT JIT. sukses untuk saya.

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

``` naskah
import { Compiler, Inject, Injectable, NgModuleFactory, NgModuleRef, Type, ComponentFactory } dari '@angular/core';
impor { LoadChildrenCallback } dari '@angular/router';

impor { ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN } dari './lazy-component-registry';

@Injeksi({
disediakanIn: 'root'
})
kelas ekspor LazyComponentLoader {
modul pribadiUntukDimuat: Peta;
modul pribadi Memuat = Peta baru>();

konstruktor (
modul pribadiRef: NgModuleRef,
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths: Peta,
kompiler pribadi: Kompiler
) {
this.modulesToLoad = Peta baru(elementModulePaths);
}

memuat (jalur: string, pemilih?: string): Janji{
if (this.modulesLoading.has(path)) {
kembalikan 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 ... lalu benar. Ini menggunakan kompiler hanya dalam mode JIT.

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

@mlc-mlapis AOT akan menjadi NgModuleFactory. Anda dapat menguji https://github.com/Jimmysh/ng-lazy-component-load

Hai teman-teman, dengan bantuan formulir komentar utas ini, saya telah membuat paket yang dimaksudkan sebagai solusi untuk mencoba memasukkan komponen entri yang dimuat di luar modul melalui perutean. Silahkan disimak dan semoga membantu.

Tautan Repo: https://github.com/Jonathan002/route-master-example
Demo Url: https://jonathan002.github.io/route-master-example/

FWIW, dalam nada yang mirip dengan @CWSpear dan @dirkluijk , saya menghapus providedIn: 'root' dari layanan saya (didefinisikan dalam modul yang dimuat lambat dan bertanggung jawab untuk membuka entryComponent ) dan sebagai gantinya menentukan layanan dalam providers dari definisi modul malas saya. Saya percaya kelemahan terbesar/satu-satunya dari ini adalah layanan ini tidak lagi dapat digoyahkan.

Menggunakan providedIn: MyLazyModule menyebabkan peringatan dependensi melingkar.

@jonrimmer Saya tidak bisa meninggalkan utas tanpa berterima kasih kepada Anda!! Anda menyelamatkan hari saya.
Untuk semua orang yang membaca komentar saya, ini dapat membantu Anda agar tidak membuang waktu jika Anda melihat jawaban yang luar biasa ini.

Dikonfirmasi ini belum diselesaikan di Angular 8.2.0. Sejak OP dibuat 2,5 tahun yang lalu, lanskap Angular telah banyak berubah, dan Angular Material 2 telah menjadi Angular Components.

Seperti yang ditunjukkan oleh komentator lain, Angular Material menggunakan DOM sehingga pengembang aplikasi tidak harus menggunakan DOM saat memuat komponen aplikasi di MatDialog, bahkan jika komponen aplikasi dideklarasikan dalam entriKomponen sub modul yang dimuat dengan lambat.

Namun, melakukan hal DOM yang sama dalam aplikasi bisnis terlihat aneh, bertentangan dengan pedoman pemrograman aplikasi Angular.

Akan menyenangkan bahwa permintaan fitur OP dapat diimplementasikan sehingga ComponentFactoryResolver dapat "melihat" komponen entri yang dideklarasikan dalam sub modul yang dimuat dengan malas, dengan satu atau lain cara. Mendeklarasikan komponen entri modul malas di app.module berfungsi, tetapi jelek.

Selain solusi kerja kotor seperti yang disebutkan di atas: 1. DOM, 2. Mendeklarasikan komponen di app.module, saya akan memberikan solusi lain, kurang kotor, di bawah ini.

solusi ke-3, kurang kotor.

Saya memiliki layanan bersama yang digunakan oleh beberapa modul yang dimuat dengan malas.

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

    }

}

Layanan ini dapat melihat komponen entri modul yang tidak dimuat dengan malas. Saya memiliki beberapa komponen entri yang diterapkan di beberapa modul yang dimuat dengan malas. Jadi saya telah menurunkan kelas layanan ini untuk disediakan di setiap modul malas:

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

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

Karena LazyComponentDialogService disediakan dalam modul lazy yang sama dengan komponen lazy entry, sehingga ia dapat melihat komponen tersebut.

Bergantung pada arsitektur aplikasi Anda, Anda mungkin tidak memerlukan kelas turunan, tetapi cukup sediakan DataComponentDialogService atau yang serupa di setiap modul.

Sepertinya masalah tidak ada lagi di Angular 9 karena implementasi ivy (diuji dengan 9.0.0-next.7). Anda dapat secara dinamis membuat komponen modul yang dimuat lambat dari luar modul malas. Tampaknya deklarasi komponen entri tidak diperlukan lagi untuk komponen dinamis di Ivy. Ivy tidak memerlukan pabrik komponen terpisah yang dulu dibuat oleh kompiler. Info pabrik komponen disematkan dalam definisi komponen dengan Ivy(?). Setelah Anda memiliki tipe komponen, Anda seharusnya bisa mendapatkan pabrik komponennya dan kemudian membuat turunannya. Itu hanya kesan saya. Ambil kata-kata saya dengan sebutir garam

Yang perlu Anda lakukan adalah membuat modul widget terpisah yang mendeklarasikan semua komponen dinamis Anda dan juga menyertakannya sebagai entryComponents. Kemudian impor modul ini dari AppModule. Ini akan memastikan bahwa komponen entri Anda terdaftar dengan injektor root.

Solusi ini bersih dan tidak merusak enkapsulasi Malas.

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

}

Modul Aplikasi:

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

@pixelbits-mk Itu akan membuat semua komponen dinamis (seperti modal dan sejenisnya) bersemangat dimuat , tetapi intinya di sini adalah membuat mereka dapat menjadi malas dimuat untuk tidak memperlambat pemuatan awal aplikasi (terutama ketika proyek tumbuh ), seperti komponen yang memanggilnya.

Katakanlah ComponentA yang dimuat dengan lambat secara dinamis memanggil dinamis yang dimuat dengan malas ComponentB . Jika ComponentA mengetahui bahwa ia akan memanggil ComponentB Anda dapat mengimpor ComponentBModule di ComponentAModule . Ini bukan kasus yang ideal, tapi saya bisa menjalaninya.

Masalah utama muncul (menurut saya) ketika ComponentA tidak tahu bahwa ComponentB akan dimuat . Misalnya, jika ComponentB adalah modal dan ComponentA memanggil metode showModal() dalam layanan, dan layanan memuat ComponentB secara dinamis di belakang layar, ComponentA tidak akan tahu itu.

Apa yang saya lakukan sekarang adalah memasukkan referensi ComponentB sebagai parameter dalam metode, seperti showModal(ComponentB) , sedemikian rupa sehingga ComponentA sekarang tahu bahwa ComponentB akan dimuat, tapi itu buruk menurut saya (setiap panggilan ke metode harus melewati referensi komponen).

Jika Komponen Entri tersedia tanpa harus mengimpor modul mereka secara eksplisit (apa yang saya pikir tentang masalah ini), itu akan menyelesaikannya (mungkin Angular dapat memiliki cara untuk mendeteksi bahwa ComponentA memanggil layanan yang mengimpor ComponentB dan buat ComponentAModule import secara implisit ComponentBModule , dan meskipun itu dapat memiliki efek yang tidak diinginkan dari juga mengimpor modul komponen yang referensinya diimpor tetapi alasan impor bukan untuk membuatnya secara dinamis , saya akan melihat ini lebih sebagai kasus tepi, dan akan baik-baik saja dengan itu).

Saya belum menguji (belum) saran @jonrimmer , tapi itu tampaknya menjanjikan.

Akan luar biasa jika ComponentA akan secara otomatis memuat modul dengan ComponentB hanya jika ComponentA ditampilkan:
ModuleA memiliki komponen ComponentA1 , ComponentA2 .
ComponentA1 referensi dalam html ComponentB (dalam modul ModuleB ), tetapi ComponentA2 tidak.
ModuleB akan secara otomatis menjadi lazy load hanya jika ComponentA1 ditampilkan, tetapi tidak jika ComponentA2 ditampilkan.

Ini akan sangat cocok dengan skenario dasbor di halaman beranda. Dahsboards berasal dari banyak modul yang berbeda, tetapi jika dasbor tidak disertakan (misalnya melalui pengaturan pengguna), tidak perlu mengunduh modul itu.

@jonrimmer Ide bagus untuk membungkus fungsi resolveComponentFactory dari ComponentFactoryResolver utama dan mencari modul lazy load yang terdaftar sebelumnya.

Ia bekerja dengan komponen entri yang menyuntikkan layanan yang disediakan di root. Tapi saya tidak bisa menyuntikkan layanan yang hanya disediakan di modul lazy load. Dalam hal ini saya mendapat StaticInjectorError. Bisakah Anda memeriksanya?

Saya ingin mengonfirmasi bahwa ivy tampaknya menangani masalah ini. Saya baru saja menemukan masalah ini hari ini ketika saya menulis ulang modul saya menjadi malas dimuat yang berisi komponen entri dan layanan bersama untuk membuat instance mereka hanya untuk menemukan kesalahan baru yang menyatakan pabrik komponen tidak dapat ditemukan. Aku benar-benar terkejut membaca ini adalah masalah di tempat pertama.

Bagaimanapun, saya baru saja mengaktifkan ivy di proyek sudut 8.2 saya yang ada dan semua tampaknya berfungsi seperti yang diharapkan.

Solusi saya untuk menggunakan Overlay dalam modul yang dimuat lambat:
Anda harus meneruskan resolver pabrik ke konstruktor ComponentPortal seperti ini

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

tetapi Anda juga harus menyediakan OverlayService dalam modul yang dimuat lambat. OverlayService perlu menyuntikkan ComponentFactoryResolver modul di mana OverlayService Disediakan

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

@mixrich Pendekatan ini akan berfungsi [di aplikasi saya, saya melakukan seperti itu dan berfungsi dengan baik sampai saya harus menutup semua dialog modal pada beberapa acara seperti logout] tetapi perlu diingat bahwa ini akan membuat Instansi OverlayService setiap kali ketika modul lain mengimpor modul ini yaitu aplikasi tidak akan memiliki satu pun instance seperti layanan sebelumnya.

Karena tidak akan ada signalton dari layanan overlay, maka TIDAK akan mudah untuk mengetahui berapa banyak dialog modal yang dibuka.

Tentu saja, ini adalah persyaratan aplikasi tetapi hanya untuk berhati-hati dengan pendekatan ini.

@mixrich Pendekatan ini akan berfungsi [di aplikasi saya, saya melakukan seperti itu dan berfungsi dengan baik sampai saya harus menutup semua dialog modal pada beberapa acara seperti logout] tetapi perlu diingat bahwa ini akan membuat Instansi OverlayService setiap kali ketika modul lain mengimpor modul ini yaitu aplikasi tidak akan memiliki satu pun instance seperti layanan sebelumnya.

Karena tidak akan ada signalton dari layanan overlay, maka TIDAK akan mudah untuk mengetahui berapa banyak dialog modal yang dibuka.

Tentu saja, ini adalah persyaratan aplikasi tetapi hanya untuk berhati-hati dengan pendekatan ini.

Misalnya, dalam kasus saya, saya memiliki ModalDialogModule dengan OverlayService yang disediakan dan ketika saya memanggil OverlayService.open(SomeComponent) itu juga membuat untuk saya tempate jendela modal, memasukkan SomeComponent di dalamnya itu dan mengembalikan beberapa struktur data dengan observable yang berguna (untuk acara yang dekat dan sukses), instance komponen dan metode manual close .
Jadi, ketika saya perlu menggunakan modals di LazyModule saya, saya hanya perlu mengimpor ModalDialogModule ke sana untuk mendapatkan kemampuan menggunakan OverlayService . Saya menemukan pendekatan ini nyaman, karena Anda selalu tahu bahwa untuk menggunakan modal Anda harus mengimpor ModalDialogModule , seperti Anda selalu tahu bahwa untuk menggunakan formulir reaktif Anda harus mengimpor ReactiveFormModule

Saya mendapatkan ini bekerja di Angular 8.3.6. Saya memiliki modul perpustakaan dengan komponen entri (dialog mat) yang tidak akan dimuat jika saya menambahkannya ke komponen entri modul perpustakaan. Dikatakan tidak dapat menemukan pabrik komponen untuk MyCustomDialog di log konsol ketika saya mencoba membukanya.

Solusinya adalah membuat metode statis di kelas NgModule modul perpustakaan yang mengembalikan larik semua komponen entri modul. Kemudian panggil metode ini dari modul aplikasi 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 {
}

Saya tahu ini masih tidak memperbaiki entriComponents di perpustakaan tidak berfungsi tetapi setidaknya dengan metode statis modul aplikasi tidak harus tahu tentang komponen entri di perpustakaan khusus. Itu hanya memanggil metode itu dan mendapatkan apa pun yang terdaftar di perpustakaan. Tidak ada tanggung jawab pada aplikasi untuk mengetahui komponen apa di perpustakaan yang merupakan komponen entri.

@asmith2306 mengapa Anda tidak menggunakan operator spread? terlihat jelek

@asmith2306 mengapa Anda tidak menggunakan operator spread? terlihat jelek

Karena itu tidak bekerja dengan itu. Ada bug tentang itu di suatu tempat.

sepertinya tidak berfungsi dengan mode --prod

Bahkan jika itu berhasil, itu tidak mengatasi masalah NYATA. masalahnya adalah jika saya menambahkan komponen ke entryComponents dari NgModule, dan modul itu diimpor ke modul yang dimuat dengan malas di aplikasi saya, maka entryComponents akan secara otomatis terdaftar dengan komponen entri aplikasi. Seharusnya tidak ada pekerjaan lebih lanjut yang diperlukan. Lebih lanjut saya akan mengatakan bahwa komponen apa pun di entryComponents dari NgModule mana pun akan secara otomatis berakhir di entryComponents dari modul aplikasi. Saat ini tidak demikian. Jika demikian maka kita tidak akan berada di sini.

@broweratcognitecdotcom Bagaimana dengan konflik dan prioritas dari tingkat injeksi yang berbeda?

Menurut saya, pesanan @mlc-mlapis harus berdasarkan siapa datang pertama, dilayani pertama.

@mlc-mlapis Apakah maksud Anda jika komponen yang berbeda memiliki nama yang sama? Kecocokan pertama atau kesalahan jika ada beberapa kecocokan. Saya dapat hidup dengan persyaratan bahwa nama komponen entri harus unik.

@broweratcognitecdotcom Hai, ya. Karena semua varian harus tercakup jika kita berbicara tentang prinsip-prinsip umum.

@mlc-mlapis Saya pikir batasan dalam sudut yang ingin diatasi banyak orang adalah bahwa komponen entri hanya dapat dipakai dan ditempatkan di dom oleh aplikasi. Aplikasi tidak perlu tahu tentang kotak dialog yang digunakan oleh modul yang saya impor ke dalam modul yang dimuat lambat. Konsep komponen entri saat ini memecah pola modular sudut.

AFAIK dengan Ivy entryComponents tidak lagi diperlukan, bukan? Karena komponen akan memiliki lokalitas, jadi mengimpornya secara dinamis akan selalu berfungsi?

@Airblader Anda benar, kami tahu. Itu hanya kenangan kecil kembali ke sejarah. 😄.

Bagi mereka yang masih berjuang dengan masalah ini, inilah solusi yang benar-benar berfungsi: https://github.com/angular/angular/issues/14324#issuecomment -481898762

Saya menerbitkan paket untuk masalah ini. beberapa salinan kode dari jonrimmer.
Sepertinya masalahnya tidak ada di Angular 9. Jika Anda perlu memperbaikinya sekarang. anda dapat menggunakan @aiao/lazy-component
contoh kode disini
https://github.com/aiao-io/aiao/tree/master/integration/lazy-component

Saya percaya bahwa ini diperbaiki di Angular v9 yang menjalankan ivy. Jika ada yang masih menghadapi masalah serupa, silakan buka masalah baru dengan skenario reproduksi minimal. Terima kasih!

Masalah ini telah dikunci secara otomatis karena tidak ada aktivitas.
Silakan ajukan masalah baru jika Anda mengalami masalah serupa atau terkait.

Baca lebih lanjut tentang kebijakan penguncian percakapan otomatis kami.

_Tindakan ini telah dilakukan secara otomatis oleh bot._

Apakah halaman ini membantu?
0 / 5 - 0 peringkat