Skip to content

Cannot create component without inserting into DOM without the deprecated ComponentFactory #45263

Closed

Description

@PowerKiKi

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

No

Description

Since Angular 13 ComponentFactory.create() is deprecated. It is advertised that we should instead use ViewContainerRef.createComponent(). However the behavior between those two methods are different and cannot necessarily replace each other for all use-cases.

My use-case is to create a component dynamically, not insert it into DOM, call a few custom methods on it, and then destroy the component. But ViewContainerRef.createComponent() will always insert the newly created component into DOM, wreaking havoc in my layout.

Basically I'd like to do something like that:

private validate(component: Type<DropdownComponent>): boolean {
    const injector = Injector.create({providers: this.getMyCustomProvider(), parent: this.getMyParentInjector()});
    const factory = this.componentFactoryResolver.resolveComponentFactory<DropdownComponent>(component);

    const dropdownComponentRef = factory.create(injector);
    const result = dropdownComponentRef.instance.isValid();
    dropdownComponentRef.destroy();

    return result;
}

Was it intentional that a component cannot be instantiated without being inserted into DOM anymore ? Or should it be considered a regression in the feature set of the framework ?

Or is there an alternative API that would allow me to instantiate and destroy components without ever inserting them into DOM ?

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 13.2.5
Node: 14.19.0
Package Manager: yarn 1.22.17
OS: linux x64

Angular: 13.2.5
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, language-service, localize, material
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1302.5
@angular-devkit/build-angular   13.2.5
@angular-devkit/core            13.2.5
@angular-devkit/schematics      13.2.5
@angular/flex-layout            13.0.0-beta.38
@schematics/angular             13.2.5
ng-packagr                      13.2.1
rxjs                            7.5.4
typescript                      4.5.5

Anything else?

No response

Activity

  1. self-assigned this
    on Mar 4, 2022
  2. added this to the needsTriage milestone on Mar 4, 2022
  3. GeorgianStan commented on Mar 19, 2022

    @GeorgianStan

    I also face this issue and a workaround is to inject ViewContainerRef inside the service.
    Like in this example

  4. patryk-zielinski93 commented on Mar 22, 2022

    @patryk-zielinski93

    And previously, components can be created outside components and directives. And, now that ComponentFactoryResolver is deprecated couldn't find any alternatives for the same.

    const componentFactoryResolver = injector.get(ComponentFactoryResolver);
    this.applicationRef = injector.get(ApplicationRef);
    
    const factory = componentFactoryResolver.resolveComponentFactory(MyComponent);
    
    this.componentRef = factory.create(injector, []);
    this.applicationRef.attachView(this.componentRef.hostView);

    Mostly when rendering outside angular app.

    I have the same problem

  5. JGrancha commented on Mar 28, 2022

    @JGrancha

    I have a similar problem that I mentioned in this issue #44926 but nobody has been able to answer yet. I also think that both forms should be maintained. They are complementary.

  6. AndrewKushnir commented on Mar 30, 2022

    @AndrewKushnir
    Contributor

    Hi, just a quick update: we plan to look into replacement APIs for the described use-case. The new API should provide an ability to create a ComponentRef instance, i.e. you should be able to do something like this:

    const appRef = injector.get(ApplicationRef);
    const compRef = createComponentRef(MyComponent, injector);
    appRef.attachView(compRef.hostView);
    

    We don't have an ETA yet, but we'd make sure that it's available before we remove ComponentFactory from the public API.

  7. fhljys commented on Apr 22, 2022

    @fhljys

    Following

  8. Ghostbird commented on May 12, 2022

    @Ghostbird

    We've got the same problem. We need to pop a modal that shows a TemplateRef constructed by the component initialisation.

    In this case the component is never added anywhere in the page, only that specific sub-view is shown.
    In this case the viewRef where the template is rendered is ultimately supplied through Angular Material's MatDialog service, but this is abstracted away (as it should be).

  9. id1945 commented on May 29, 2022

    @id1945

    //====================================================//
    👉 https://stackblitz.com/edit/angular-lazy-loading-compiler-module-async-v13 👈
    //====================================================//

        /**
       * Angular 13
       * Lazy module
       * Load component
       */
      public async loadComponent() {
        await import('./lazy/lazy.module').then((m) => {
          // Reset
          this.container.clear();
          // Register
          const module = (<any>m)[Object.keys(m)[0]];
          const moduleRef: NgModuleRef<any> = createNgModuleRef(module, this.injector);
          const componentA = m.LazyModule.components.LazyAComponent;
          const componentB = m.LazyModule.components.LazyBComponent;
          // Create component
          const componentRefA = this.container.createComponent(componentA, { ngModuleRef: moduleRef });
          const componentRefB = this.container.createComponent(componentB, { ngModuleRef: moduleRef });
          // @Input data
          componentRefA.instance.test = "Update input string A";
          componentRefB.instance.test = "Update input string B";
        });
      }
  10. LubosD commented on May 29, 2022

    @LubosD

    @id1945 How is this relevant? You're obviously placing these components into the DOM.

  11. Ghostbird commented on May 29, 2022

    @Ghostbird

    @id1945 This issue is about the case where you cannot have a this.container to call createComponent on, for whatever reason.

  12. pitAlex commented on Jun 14, 2022

    @pitAlex

    I have another use case where I am struggling with what to replace componentFactoryResolver with. In my situation, I have a page that is fully done in angular, but the result is a mostly static page. So it is rendered with node and on the client-side, I use compileModuleAsync along with componentFactoryResolver to target the 1 or 2 sections that are the already rendered in DOM to make them controllable with angular on the client-side so that they become functional.
    Imagine a page where at the bottom it has a QnA section and that section would be controllable by angular. Node renders the initial view and then in comes angular on the client-side and replaces it with the interactable version where it has things like loading more answers via ajax, having a reply form, voting, etc. This helps us a lot with web core vitals where the js size is smaller and by having only parts of the page be in angular on the client-side we avoid triggering a high LCP as the entire DOM would get replaced in case of a full-page bootstrap.
    In my case, I benefited from "factory.create(injector,[],selector)" where "selector" would be the target section.

  13. 2 remaining items

  14. tonysamperi commented on Jul 7, 2022

    @tonysamperi

    And previously, components can be created outside components and directives. And, now that ComponentFactoryResolver is deprecated couldn't find any alternatives for the same.

    const componentFactoryResolver = injector.get(ComponentFactoryResolver);
    this.applicationRef = injector.get(ApplicationRef);
    
    const factory = componentFactoryResolver.resolveComponentFactory(MyComponent);
    
    this.componentRef = factory.create(injector, []);
    this.applicationRef.attachView(this.componentRef.hostView);

    Mostly when rendering outside angular app.

    I have a piece of code which looks exactly like this.
    I use it to append dynamic components anywhere in the dom...But now I don't know what alternative I can use...
    I guess the solution could be instantiating a standalone component (bootstrapApplication, use some InjectionToken to provide the dynamic component class) where, using the viewContainerRef we could instantiate the dynamic component...
    Then retrieve the HTML and move it anywhere in the DOM...
    It seems a bit convoluted and I'm not sure how dependencies would be handled...

  15. pkozlowski-opensource commented on Jul 8, 2022

    @pkozlowski-opensource
    Member

    @tonysamperi the alternative is being worked on in #46685

  16. tonysamperi commented on Jul 9, 2022

    @tonysamperi

    @pkozlowski-opensource I saw the commits, cool! Thanks for letting me know!

  17. angular-automatic-lock-bot commented on Aug 14, 2022

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  18. locked and limited conversation to collaborators on Aug 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions