Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(modal): support server rendering with lazy loading #2180

Closed
wants to merge 1 commit into from

Conversation

Scoup
Copy link
Contributor

@Scoup Scoup commented Feb 20, 2018

Closes #1968
Fixes #858

This PR fix the problem No component factory found for NgbModalBackdrop. Did you add it to @NgModule.entryComponents? at noComponentFactoryError when using ng-bootstrap on server side.

We had 2 main issues:

  • First, on lazy loading for some reason (I don't know how Angular Compiler really works) the service NgbModalStack is injected but the components NgbModalWindow and NgbModalBackdrop are not on the factories list yet. For that reason ComponentFactory<NgbModalBackdrop> and ComponentFactory<NgbModalWindow> will be initialized only when open is called.
  • A lot of document access directly. I changed the code so we can inject the document to run on server side.


/**
* Opens a new modal window with the specified content and using supplied options. Content can be provided
* as a TemplateRef or a component type. If you pass a component type as content than instances of those
* components can be injected with an instance of the NgbActiveModal class. You can use methods on the
* NgbActiveModal class to close / dismiss modals from "inside" of a component.
*/
open(content: any, options: NgbModalOptions = {}): NgbModalRef {
return this._modalStack.open(this._moduleCFR, this._injector, content, options);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not good, as it would change ComponentFactoryResolver instance and break lazy-loaded modules (I could not open a component from a lazy-loaded module).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tip!
I was looking for how ComponentFactoryResolver and now makes sense why the ComponentFactoryResolver was here!

// Cant build factories on constructor when using lazy loading
private _buildFactories() {
this._buildBackdropFactory();
this._buildWindowFactory();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that caching factories here, in the code, adds any performance benefits. Remove those methods and replace with a direct call to this._componentFactoryResolver.resolveComponentFactory(NgbModalBackdrop)


if (!containerEl) {
throw new Error(`The specified modal container "${containerSelector}" was not found in the DOM.`);
throw new Error(`The specified modal container "${options.container}" was not found in the DOM.`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have a broken error message if a user doesn't specify the container option

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user doesn't specify the container option then will use this._document.body and not a query selector.
this._document.body will be always defined, right?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this._document.body will be always defined, right?

In theory not always, as it can potentially be null: https://developer.mozilla.org/en-US/docs/Web/API/Document/body

In practice it should be always defined, but it is better to be safe than sorry...

private _getContentRef(
moduleCFR: ComponentFactoryResolver, contentInjector: Injector, content: any,
context: NgbActiveModal): ContentRef {
private _getContentRef(contentInjector: Injector, content: any, context: NgbActiveModal): ContentRef {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature should stay as-is, we need to use a proper ComponentFactoryResolver

@pkozlowski-opensource
Copy link
Member

@Scoup thnx for the PR. I did the first pass on it and left the comments in the code, here is the gist:

  • injecting document is a good change, thnx;
  • we need to use proper ComponentFactoryResolver

Please resolve those issues and I will have another look.

@Scoup
Copy link
Contributor Author

Scoup commented Mar 7, 2018

@pkozlowski-opensource I did the changes. Was very nice to learn how ComponentFactoryResolver works behind the scene. Thanks helping me.

@pkozlowski-opensource
Copy link
Member

Merged, thnx @Scoup

One note - I've removed Fixes #858 from the commit as I believe that there is more work needed on other components dynamically inserting views (tooltip, popover, typeahead, datepicker) to fully support universal for all components.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

modal service breaks universal server side rendering Server side rendering with universal
2 participants