formio / angular

JSON powered forms for Angular
https://formio.github.io/angular-demo
MIT License
644 stars 472 forks source link

[Custom Component] - Is it possible to access the current instance of the Formio Component within the custom component? #1023

Closed dinbtechit closed 1 month ago

dinbtechit commented 1 year ago

Is it possible to access the current instance of the Formio Component within the custom component?

export class My CustomComponent implements FormioCustomComponent<string> {
  @Input() value: string;
  @Output() valueChange: EventEmitter<string> = new EventEmitter<string>();
  @Output() formioEvent: EventEmitter<FormioEvent> = new EventEmitter<FormioEvent>();

  customFunction() {

      //  I need to access the instance of Formio Component here.
   }  

}

I would like to access the component because the customOptions are not accessible during initialization. Furthermore, dragging the component does not preserve any of the customOptions.

adhonay commented 11 months ago

I had this question too, and no one answered.

The solution I came up with was to create a global service, and within it there are methods for adding and viewing the main instance.

In the formiobasecomponent component within the createRenderer function, I called the add method of the service I created.

  createRenderer() {
    const Renderer = this.getRenderer();
    const form = (new Renderer(
      this.formioElement ? this.formioElement.nativeElement : null,
      this.form,
      this.getRendererOptions()
    ));

    // adicionado-inicio
    this.formFormioService.setEstruturaComponenteFormularioRender(form);
    // adicionado-fim

    return form.instance;
  }

In custom components, I injected this global service and viewed the instance that way.

It worked, I hope it helps you.

pradeepsonisoni commented 11 months ago

Can you explain more @adhonay how you are storing this and how you will get the same instance? It will be very helpful.

adhonay commented 11 months ago

Você pode explicar mais@adhonaycomo você está armazenando isso e como obterá a mesma instância? Será muito útil.

Ignore the main and secondary instance part, in your case it will probably only be 1 instance. I did it this way, because I am using modals from one instance within another.

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { ConstantesSistemaFormio } from '../constantes/constantes-sistema-formio';

@Injectable({
  providedIn: 'root'
})
export class FormFormioService {

  private estruturaComponenteFormularioRenderInstanciaPrincipal = new BehaviorSubject<object>(undefined);
  private estruturaComponenteFormularioRenderInstanciaSecundariaGrid = new BehaviorSubject<object>(undefined);

  constructor() { }

  private quantidadeInstaciasFormioRender(): number {
    return document.querySelectorAll(ConstantesSistemaFormio.tceFormio)?.length;
  }

  public existeInstanciaRenderFormio(): boolean {
    return this.quantidadeInstaciasFormioRender() > 0
  }

  public existeInstanciaSecundariaGridAberta(): boolean {
    return this.quantidadeInstaciasFormioRender() === 2
  }

  public setEstruturaComponenteFormularioRender(instancia) {
    if (instancia && this.quantidadeInstaciasFormioRender() <= 1) {
      this.estruturaComponenteFormularioRenderInstanciaPrincipal.next(instancia);
    } else if (instancia && this.quantidadeInstaciasFormioRender() === 2) {
      this.estruturaComponenteFormularioRenderInstanciaSecundariaGrid.next(instancia);
    }
  }
  public getEstruturaComponenteFormularioRenderInstanciaPrincipal(): object {
    return this.estruturaComponenteFormularioRenderInstanciaPrincipal.value;
  }

  public getEstruturaComponenteFormularioRenderInstanciaSecundariaGrid(): object {
    return this.estruturaComponenteFormularioRenderInstanciaSecundariaGrid.value;
  }
}
pradeepsonisoni commented 11 months ago

Can you show the createRenderer class aswell how you are extending the class.?

Sahil-Sayyed commented 11 months ago

Você pode explicar mais@adhonaycomo você está armazenando isso e como obterá a mesma instância? Será muito útil.

Ignore the main and secondary instance part, in your case it will probably only be 1 instance. I did it this way, because I am using modals from one instance within another.

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { ConstantesSistemaFormio } from '../constantes/constantes-sistema-formio';

@Injectable({
  providedIn: 'root'
})
export class FormFormioService {

  private estruturaComponenteFormularioRenderInstanciaPrincipal = new BehaviorSubject<object>(undefined);
  private estruturaComponenteFormularioRenderInstanciaSecundariaGrid = new BehaviorSubject<object>(undefined);

  constructor() { }

  private quantidadeInstaciasFormioRender(): number {
    return document.querySelectorAll(ConstantesSistemaFormio.tceFormio)?.length;
  }

  public existeInstanciaRenderFormio(): boolean {
    return this.quantidadeInstaciasFormioRender() > 0
  }

  public existeInstanciaSecundariaGridAberta(): boolean {
    return this.quantidadeInstaciasFormioRender() === 2
  }

  public setEstruturaComponenteFormularioRender(instancia) {
    if (instancia && this.quantidadeInstaciasFormioRender() <= 1) {
      this.estruturaComponenteFormularioRenderInstanciaPrincipal.next(instancia);
    } else if (instancia && this.quantidadeInstaciasFormioRender() === 2) {
      this.estruturaComponenteFormularioRenderInstanciaSecundariaGrid.next(instancia);
    }
  }
  public getEstruturaComponenteFormularioRenderInstanciaPrincipal(): object {
    return this.estruturaComponenteFormularioRenderInstanciaPrincipal.value;
  }

  public getEstruturaComponenteFormularioRenderInstanciaSecundariaGrid(): object {
    return this.estruturaComponenteFormularioRenderInstanciaSecundariaGrid.value;
  }
}

I am not able to get instance when i invoke setInstance(), or either calling createRenderer() from constructor in my custom component using reference from code snippet you provided , so could you please provide your custom component with the proper place where you are getting the instance ? or you can send across the files which have the running code. (Note: We have tried all possible ways to get instance and we have angular cli 16 as a mandatory platform.)

Sahil-Sayyed commented 11 months ago

By extending MaterialComponent class i am able to get the instance of the custom component with setInstance() in one project but in another project without using angular material and considering typescript and angular(angular material excluded ) setup, which class i can extend/implement to achieve similar behavior?

pradeepsonisoni commented 10 months ago

Hi @dinbtechit

Did you find any solution?

dinbtechit commented 10 months ago

@pradeepsonisoni - I have not. I think the custom component has some issues with the way how it implemented updating the component value. Also, starting from version Formio/angular version 6.0.0 the custom components are being deprecated all together. So it may be worth forking the custom component code and adding it to your own angular project. That way, you will have more control over the code or at least that's what I am going to be doing to fix this issue.

pradeepsonisoni commented 10 months ago

@dinbtechit Thank you for quick reply.

adhonay commented 10 months ago

Você pode mostrar a classe createRenderer também como está estendendo a classe?

createRenderer is a method already implemented by formio itself in the formiobasecomponent.ts component

pradeepsonisoni commented 10 months ago

@adhonay If you are able to get the instance. you can share the code how you are implementing the FormioCustomComponent class. let us know because We are not getting the instance.

adhonay commented 10 months ago

@adhonaySe você conseguir obter a instância. você pode compartilhar o código como está implementando a classe FormioCustomComponent . deixe-nos saber porque não estamos recebendo a instância.

FormioCustomComponent here is a simple interface, created in the Angular template.

export interface  FormioCustomComponent<T> {
  value: T; // Should be an @Input
  valueChange: EventEmitter<T>; // Should be an @Output
  disabled: boolean;
  formioEvent?: EventEmitter<FormioEvent>; // Should be an @Output
}

You don't need anything more than:

1- Create the service as I sent above.

2- Insert the line below into the createRenderer function in formiobasecomponent.ts to send the instance. this.formFormioService.setEstruturaComponenteFormularioRender(form);

3- In your CustomComponent, inject the service into the constructor and then wherever you want in the component, use the line: let instanciaRender = this.formFormioService.getEstruturaComponenteFormularioRenderInstanciaPrincipal();

pradeepsonisoni commented 10 months ago

@adhonay If we have two instance of the same custom component. Is it working with two instance? I don't think so.

dinbtechit commented 10 months ago

I had this question too, and no one answered.

The solution I came up with was to create a global service, and within it there are methods for adding and viewing the main instance.

In the formiobasecomponent component within the createRenderer function, I called the add method of the service I created.

  createRenderer() {
    const Renderer = this.getRenderer();
    const form = (new Renderer(
      this.formioElement ? this.formioElement.nativeElement : null,
      this.form,
      this.getRendererOptions()
    ));

    // adicionado-inicio
    this.formFormioService.setEstruturaComponenteFormularioRender(form);
    // adicionado-fim

    return form.instance;
  }

In custom components, I injected this global service and viewed the instance that way.

It worked, I hope it helps you.

@adhonay - isn't the formiobasecomponent class part the npm package, how did you manage to update the createRenderer() method to inject the code - > this.formFormioService.setEstruturaComponenteFormularioRender(form);?

adhonay commented 10 months ago

I had this question too, and no one answered. The solution I came up with was to create a global service, and within it there are methods for adding and viewing the main instance. In the formiobasecomponent component within the createRenderer function, I called the add method of the service I created.

  createRenderer() {
    const Renderer = this.getRenderer();
    const form = (new Renderer(
      this.formioElement ? this.formioElement.nativeElement : null,
      this.form,
      this.getRendererOptions()
    ));

    // adicionado-inicio
    this.formFormioService.setEstruturaComponenteFormularioRender(form);
    // adicionado-fim

    return form.instance;
  }

In custom components, I injected this global service and viewed the instance that way. It worked, I hope it helps you.

@adhonay - isn't the formiobasecomponent class part the npm package, how did you manage to update the createRenderer() method to inject the code - > this.formFormioService.setEstruturaComponenteFormularioRender(form);?

I cloned the git project and started implementing custom components from it.

You can modify the createRenderer method because you have direct access to it.

https://github.com/formio/angular/blob/master/projects/angular-formio/src/FormioBaseComponent.ts

Line 108

dinbtechit commented 10 months ago

Thanks @adhonay, that makes sense now. Actually, a better approach would be to fork the custom component and modify the logic there.

https://github.com/formio/angular/tree/5.5.x/projects/angular-formio/src/custom-component

lane-formio commented 1 month ago

It seems a resolution has been provided by the community so I'm going to go ahead and close it but can re-open if someone needs to contribute more here.

dinbtechit commented 1 month ago

For the folks who are still looking for a solution. I ended up creating an Angular Service and maintain the state there. Here is a pseudo code

@Injectable({for: 'root'})
export class MyCompService {
  string buttonTextState = ''; // default empty
}
@Component({...})
export class MyComponent implement FormIoCustomComponent, OnInit, AfterViewInit {

 @Input() buttonText: string;
 myCompService = inject<MyCompService>();

 ngOnInit() {
      if (!buttonText && !this.myCompService.buttonTextState) {
     // Read the buttonText from the state service variable before the component is loaded
      this.buttonText =  this.myCompService.buttonTextState;
     }
 }

ngAfterViewInit() {
    // store Button Text in the service.
  if (buttonText) {
    this.myCompService.buttonTextState = this.buttonText;
 }
}

}