Skip to content

Alternative way for depreciation of ComponentFactory in order to get componentFactory inputs #44926

Closed

Description

@ofirrifo

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

core

Is this a regression?

Yes

Description

Since Angular 13 the ComponentFactory is deprecated.

Since it depreciated I need an alternative way to get the inputs property that I used to get from the instance of ComponentFactory this.componentFactory.inputs

Here is example of code where I'm using the inputs

@Directive({
  selector: '[dynamicComponentDirective]',
})
export class SingDynamicComponentDirective implements OnInit {
  @Input() dynamicComponentClass!: Type<any>;
  @Input() inputs: any;

  componentFactory: ComponentFactory<any> | undefined = undefined;
  component!: ComponentRef<any> | null;

  constructor(private resolver: ComponentFactoryResolver, private container: ViewContainerRef) {}

  ngOnInit(): void {
    this.container.clear();
    this.componentFactory = void 0;
    this.component = null;
    this.componentFactory = this.resolver.resolveComponentFactory<any>(this.dynamicComponentClass);
    this.component = this.container.createComponent(this.componentFactory);

    this.updateComponentInputs();
  }

  /**
   * update component inputs
   */
  updateComponentInputs(): void {
    const validInputs = this.inputs && isObject(this.inputs) && Object.keys(this.inputs).length;
    if (this.component && validInputs && this.componentFactory && this.componentFactory.inputs) {
      this.componentFactory.inputs.forEach((input: any) => {
        if (this.component && this.inputs.hasOwnProperty(input.propName)) {
          this.component.instance[input.propName] = this.inputs[input.propName];
        }
      });
    }
  }
}

Now that ComponentFactory is deprecated what is the alternative to get the inputs property?

Please provide a link to a minimal reproduction of the bug

above there is a code with the example of my problem

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

Angular CLI: 13.2.0
Node: 16.10.0
Package Manager: npm 7.6.0
OS: darwin x64

Angular: 13.2.0
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, material, platform-browser, platform-browser-dynamic
... router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1302.0
@angular-devkit/build-angular   13.2.0
@angular-devkit/core            13.2.0
@angular-devkit/schematics      13.2.0
@schematics/angular             13.2.0
ng-packagr                      13.2.0
rxjs                            7.5.2
typescript                      4.5.5

Anything else?

No response

Activity

  1. self-assigned this
    on Feb 1, 2022
  2. modified the milestone: needsTriage on Feb 1, 2022
  3. JGrancha commented on Mar 1, 2022

    @JGrancha

    I am going to comment on a similar example in which I have tried to find an alternative solution but have not found it. I have defined this variable that exports some components that I need to create with the @angular/elements library

    export const customElementComponents = [
    TextBoxCustomElementComponent,
      CheckBoxCustomElementComponent,
      NumberBoxCustomElementComponent,
      DateBoxCustomElementComponent,
      TextAreaCustomElementComponent,
      WebcamCustomElementComponent,
      LabelCustomElementComponent,
      ColorBoxCustomElementComponent,
      PdfCustomElementComponent,
      PdfFakeCustomElementComponent,
      ImageDrawerCustomElementComponent,
      ImageDrawerFakeCustomElementComponent,
      SelectBoxFixedCustomElementComponent,
      SwitchBoxCustomElementComponent,
      RadioGroupCustomElementComponent,
    ];
    

    And then in the app.module I have the following to define the Web Components. For that I need to get the selector and I get it with the ComponentFactoryResolver:

    import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
    import { ComponentFactoryResolver, Injector, NgModule, Type } from '@angular/core';
    import { createCustomElement } from '@angular/elements';
    import { BrowserModule } from '@angular/platform-browser';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { RouterModule } from '@angular/router';
    import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
    import { TranslateHttpLoader } from '@ngx-translate/http-loader';
    import { SharedModule } from '@shared';
    
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { ErrorInterceptor, JwtInterceptor } from './core/interceptors';
    import { customElementComponents } from './shared/components/custom-elements';
    
    export function createTranslateLoader(http: HttpClient) {
      return new TranslateHttpLoader(http, './assets/i18n/', '.json')
    }
    
    function createCustomElements(injector: Injector, factory: ComponentFactoryResolver){
      customElementComponents.forEach((cmp: Type<any>) => {
        const element = createCustomElement(cmp, { injector: injector })
        const { selector } = factory.resolveComponentFactory(cmp);
        customElements.define(selector, element);
      })
    }
    
    @NgModule({
      declarations: [AppComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        RouterModule,
        AppRoutingModule,
        HttpClientModule,
        TranslateModule.forRoot({
          loader: {
            provide: TranslateLoader,
            useFactory: createTranslateLoader,
            deps: [HttpClient]
          }
        }),
        SharedModule
      ],
      providers: [
        { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
        { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule{
      constructor(private injector: Injector, private factory: ComponentFactoryResolver) {
        createCustomElements(injector, factory);    
      }
    }
    

    At the internal core level, I can't comment because I don't control it, but it would still be a good idea to leave the new way of creating components and this way of working with components. At the moment it is only deprecated but I read that in version 16 it was going to be removed...

  4. winterer commented on Apr 15, 2022

    @winterer

    Same problem here. Our entire application framework relies on dynamic views that are created on behalf of a (JSON) view model that can be modified by the user at runtime. This model also contains values for input properties that are set on the view component after creation. Currently, we use ComponentFactory.inputs for two reasons:

    1. to limit the setting of values to real input properties (those decorated with @Input). (Other properties of the model are ignored.)
    2. (less important) to be able to use the template name of an input property (instead of the javascript property name) in the json model.
  5. DanielIcc commented on May 25, 2022

    @DanielIcc

    I have the same problem i need the selector any alternative?

  6. sabribv commented on Jun 2, 2022

    @sabribv

    Same problem here. I'm creating dynamic dashboards where all of their components are created dynamically depending on the dashboard instance type. I need to get the components' inputs and outputs to validate that I'm setting them all correctly and to bind their values.

  7. ofirrifo commented on Jun 12, 2022

    @ofirrifo
    Author

    @alxhub , @AndrewKushnir is this block me from upgrade to angular 14 ?

  8. AndrewKushnir commented on Jun 13, 2022

    @AndrewKushnir
    Contributor

    is this block me from upgrade to angular 14 ?

    @ofirrifo it should not block you from upgrading to Angular v14, the ComponentFactory symbol is still present there. We plan to perform further research on the APIs needed to support the described use-cases and we'll provide an update once we have additional information.

  9. 2 remaining items

  10. demike commented on Jul 4, 2022

    @demike
    Contributor

    @pkozlowski-opensource since one of your latest features (#46641) it is possible to set an input value (including marking for change ...).
    Wouldn't it be possible to implement some methods on ComponentRef like
    public hasInput(name: string): boolean
    and / or public getInputs(): string[].

    I think this could/would solve this issue.

    And as a bonus one would not have to rely on try / catch if the input is not available.
    It would be possible to check the availability upfront

  11. pkozlowski-opensource commented on Jul 4, 2022

    @pkozlowski-opensource
    Member

    @demike ComponentMirror API from #46685 gets us almost there but I agree that a boolean check on the ComponentRef would be probably more convenient. @AndrewKushnir WDYT?

  12. AndrewKushnir commented on Jul 6, 2022

    @AndrewKushnir
    Contributor

    @pkozlowski-opensource I agree, the ComponentMirror API should provide an ability to check if an input is present. Adding an extra hasInput function to the ComponentRef feels redundant in this case and would add some non-tree-shakable code.

  13. demike commented on Jul 7, 2022

    @demike
    Contributor

    On ComponentRef the non-tree-shakable code would be like:

      hasInput(name: string) { 
        return this._tNode.inputs?.[name] !== undefined;
      }
    
  14. 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.

  15. 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