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

Custom Form Control sample example Needs #762

Open
NurdinDev opened this issue May 28, 2018 · 16 comments
Open

Custom Form Control sample example Needs #762

NurdinDev opened this issue May 28, 2018 · 16 comments
Labels

Comments

@NurdinDev
Copy link

NurdinDev commented May 28, 2018

I'm submitting a


[ ] Bug / Regression
[ ] Feature Request / Proposal
[x] Question

I'm using


NG Dynamic Forms Version: `X.Y.Z`

[ ] Basic UI
[x] Bootstrap UI  
[ ] Foundation UI
[ ] Ionic UI
[ ] Kendo UI
[ ] Material  
[ ] NG Bootstrap
[ ] Prime NG

Description

Hello, I'm trying to make a custom control but at some point, I didn't understand as well, if there is example it's good because the documentation not explains enough.

for the hard part I didn't get it is: in corresponding DynamicFormControlModel what should we put ?

@Input() model: /* corresponding DynamicFormControlModel */;
providers: [
  {
    provide: DYNAMIC_FORM_CONTROL_MAP_FN,
    useValue: (model: DynamicFormControlModel): Type<DynamicFormControl> | null  => {

      switch (model.type) {

        case /* corresponding DynamicFormControlModel */:
          return MyDynamicCustomFormControlComponent;

        }
     }
  }
]
@udos86
Copy link
Owner

udos86 commented May 31, 2018

@NuruddinBadawi

You need to state which DynamicFormControlModel should map to your custom component.

For example if you'd like to replace the default select component of a certain UI package you would do this:

@Input() model: DynamicSelectModel;

providers: [
  {
    provide: DYNAMIC_FORM_CONTROL_MAP_FN,
    useValue: (model: DynamicFormControlModel): Type<DynamicFormControl> | null  => {

      switch (model.type) {

        case DynamicSelectModel:
          return MyDynamicCustomFormControlComponent;

        }
     }
  }
]

@NurdinDev
Copy link
Author

Thanks to your replay @udos86, but if I need to make new type not override, for example, I have ng-select component custom view to show colors in the list, it's possible to create a new type and use my component?

@udos86
Copy link
Owner

udos86 commented May 31, 2018

@NuruddinBadawi

Yes, it technically should be possible to introduce a custom DynamicFormControlModel.

But please beware that there's currently no support for recreating a custom dynamic form control model from JSON.

@maroy1986
Copy link
Contributor

maroy1986 commented Jun 7, 2018

@udos86 As for the Recreating a custom dynamic form control model from JSON, is this something you plan to implement one day for custom dynamic form model or the complexity of supporting it could lead to problems you think are not worth the effort?

I'm just asking a question here. We are having a similar need at the moment, I'm evaluating our options.

Thanks!

@ironbirdie
Copy link

I'm trying to build a custom component based on the autocomplete from primeng with a rest-backend to get a list of suggestions, do u have an example on how to build your own model?

@ntziolis
Copy link

ntziolis commented Aug 8, 2018

@udos86 Can you clarify the below

But please beware that there's currently no support for recreating a custom dynamic form control model from JSON.

Where does this limitation come from? I'm happy to put in some work to enable this as this is a key requirement for us.

Here is what we are trying to achieve:

  • Build custom custom controls (for example a lookup control that needs additional data parameters to figure out where to get its data from)
  • Create a form leveraging these custom controls from JSON (we generate this json inkl parameters for lookup fields from a datamodel with annotations hence we cannot use the coding route)

At first glance introducing custom modelType would be the sensible route but I'm open to other suggestions. Again also happy to put in some time to enable this scenario if its not possible as for now.

@ntziolis
Copy link

ntziolis commented Aug 8, 2018

Did some digging:

throw new Error(`unknown form control model type defined on JSON object with id "${model.id}"`);

Would be the right approach to implement DYNAMIC_FORM_CONTROL_MODEL_MAP_FN similar to the ones for form controls?

@darrenmothersele
Copy link

Yes, it technically should be possible to introduce a custom DynamicFormControlModel.

But please beware that there's currently no support for recreating a custom dynamic form control model from JSON.

I have a proof-of-concept working where I can create a dynamic form from JSON with a custom control.

I implemented a custom DynamicFormControlModel by looking at how the Input control was implemented. I extended DynamicInputControlModel<string> and also implemented a config class to extend DynamicInputControlModelConfig<string>.

In order to get loading from JSON working, I had to extend DynamicFormService and re-implement fromJSON(), adding in my custom type to the switch statement:

export class MyFormService extends DynamicFormService {
    ...

    fromJSON(json: string | object[]): DynamicFormModel | never {
        ...
        case CUSTOM_DYNAMIC_FORM_CONTROL_TYPE:
          formModel.push(new MyCustomModel(model, layout));
          break;
       ...
    }
}

Then I set my app to use my overriden DynamicFormService class via app.module.ts providers:


  providers: [
    {
      provide: DYNAMIC_FORM_CONTROL_MAP_FN,
      useValue: (model: DynamicFormControlModel): Type<DynamicFormControl> | null  => {
        switch (model.type) {
          case CUSTOM_DYNAMIC_FORM_CONTROL_TYPE:
            return MyDynamicCustomFormControlComponent;
        }
      }
    },
    {
      provide: DynamicFormService,
      useClass: MyFormService
    }
  ],

This works, but it doesn't feel like the most robust way of doing this.

@danielleiszen
Copy link

The documentation states that by using this approach we can override the default mapping however, it is not clear for the first sight that this library is NOT extendable. I choose this library because I saw the custom controls section in documentation and it seemed to do exactly what I wanted. Only when got familiar with the library I had to realize that I have to implement the actual behavior myself. I find the documentation to be misleading in this regard.

I read a few issues here and I saw that a lot of us would be happy to contribute in order to make this library better, but there was no answer from the maintainer side. I am a bit disappointed about this. I had to implement visibility relations myself and I am going to implement true customization as well, because we need this behavior in our project.

It would also be good if I was able to use my forked source in my project, but due to the fact that the library contains multiple packages I did not find a way to include this git in packages.json. I had to copy and paste the relevant parts of source code into my project - which sounds ridiculous.

So if anyone can share some solution about these problems, I would be happy to share my contribution to this library. Thx

@danielleiszen
Copy link

danielleiszen commented Mar 27, 2019

Now, I describe here what I basically did in order to implement this behavior, maybe it helps someone.

In order to implement a real custom control behavior I declared a new function signature and an injection token for the model selector in dynamic-form.service.ts

export type DynamicFormModelMapFn = (type: string, model: any, layout: any) => DynamicFormControlModel | null;
export const DYNAMIC_FORM_MODEL_MAP_FN = new InjectionToken<DynamicFormModelMapFn>("DYNAMIC_FORM_MODEL_MAP_FN");

this should be injected in constructor:

 constructor(
    @Inject(DYNAMIC_FORM_MODEL_MAP_FN) @Optional() private readonly DYNAMIC_FORM_MODEL_MAP_FN: any,
    private validationService: DynamicFormValidationService) {
    this.DYNAMIC_FORM_MODEL_MAP_FN = DYNAMIC_FORM_MODEL_MAP_FN as DynamicFormModelMapFn;
  }

then in DynamicFormService.fromJSON the default case has been extended as:

default:
  {
    if (this.DYNAMIC_FORM_MODEL_MAP_FN !== undefined) {
      var model = this.DYNAMIC_FORM_MODEL_MAP_FN(model.type, model, layout);

      if (model !== null) {
        formModel.push(model);
      } else {
        throw new Error(`custom form control model is not resolved by type "${model.type}"`);
      }
    } else {
      throw new Error(`unknown form control model defined on JSON with id "${model.id}"`);
    }
  }
  break;

then I use it via a provider in my module:

providers: [{
  provide: DYNAMIC_FORM_CONTROL_MAP_FN,
  useValue: (model: DynamicFormControlModel): Type<DynamicFormControl> | null => {

    switch (model.type) {

      case MY_CUSTOM_DYNAMIC_FORM_CONTROL_TYPE_CONSTANT:
        return MyDynamicCustomFormControlComponent;
    }
  }
}, {
    provide: DYNAMIC_FORM_MODEL_MAP_FN,
    useValue: (type: string, model: any, layout: any): DynamicFormControlModel | null => {
      switch (type) {
        case MY_CUSTOM_DYNAMIC_FORM_CONTROL_TYPE_CONSTANT:
          return new MyDynamicCustomControlModel(model, layout);
      }
    }
  }],

the custom model should define the inherited type field as serializable:

@serializable() readonly type: string = MY_CUSTOM_DYNAMIC_FORM_CONTROL_TYPE_CONSTANT;

everything else is done by the docs

@rockeshub
Copy link

rockeshub commented Mar 28, 2019 via email

@danielleiszen
Copy link

@rockeshub: I am trying to integrate these changes into my repo asap.

Thanks it's really helpful. Do u have this in any stackblitz or any github repo. I am new to this lib and find it difficult where all to make the changes

@rockeshub
Copy link

rockeshub commented Apr 1, 2019 via email

@darrenmothersele
Copy link

default:
  {
    if (this.DYNAMIC_FORM_MODEL_MAP_FN !== undefined) {
      var model = this.DYNAMIC_FORM_MODEL_MAP_FN(model.type, model, layout);

      if (model !== null) {
        formModel.push(model);
      } else {
        throw new Error(`custom form control model is not resolved by type "${model.type}"`);
      }
    } else {
      throw new Error(`unknown form control model defined on JSON with id "${model.id}"`);
    }
  }
  break;

I'm not sure if this would work in my use case, as I need to inject helper services into the form service to resolve data for the custom form control models.

Perhaps the custom logic added in this example could be implemented in an separate method on the DynamicFormService class, which could then be overridded in a subclass?

@ronnetzer
Copy link

ronnetzer commented Aug 17, 2019

Hi,
I've stumbled on this issue after opening a PR exactly for this case.

#1009

Now, I describe here what I basically did in order to implement this behavior, maybe it helps someone.

In order to implement a real custom control behavior I declared a new function signature and an injection token for the model selector in dynamic-form.service.ts

export type DynamicFormModelMapFn = (type: string, model: any, layout: any) => DynamicFormControlModel | null;
export const DYNAMIC_FORM_MODEL_MAP_FN = new InjectionToken<DynamicFormModelMapFn>("DYNAMIC_FORM_MODEL_MAP_FN");

this should be injected in constructor:

 constructor(
    @Inject(DYNAMIC_FORM_MODEL_MAP_FN) @Optional() private readonly DYNAMIC_FORM_MODEL_MAP_FN: any,
    private validationService: DynamicFormValidationService) {
    this.DYNAMIC_FORM_MODEL_MAP_FN = DYNAMIC_FORM_MODEL_MAP_FN as DynamicFormModelMapFn;
  }

then in DynamicFormService.fromJSON the default case has been extended as:

default:
  {
    if (this.DYNAMIC_FORM_MODEL_MAP_FN !== undefined) {
      var model = this.DYNAMIC_FORM_MODEL_MAP_FN(model.type, model, layout);

      if (model !== null) {
        formModel.push(model);
      } else {
        throw new Error(`custom form control model is not resolved by type "${model.type}"`);
      }
    } else {
      throw new Error(`unknown form control model defined on JSON with id "${model.id}"`);
    }
  }
  break;

then I use it via a provider in my module:

providers: [{
  provide: DYNAMIC_FORM_CONTROL_MAP_FN,
  useValue: (model: DynamicFormControlModel): Type<DynamicFormControl> | null => {

    switch (model.type) {

      case MY_CUSTOM_DYNAMIC_FORM_CONTROL_TYPE_CONSTANT:
        return MyDynamicCustomFormControlComponent;
    }
  }
}, {
    provide: DYNAMIC_FORM_MODEL_MAP_FN,
    useValue: (type: string, model: any, layout: any): DynamicFormControlModel | null => {
      switch (type) {
        case MY_CUSTOM_DYNAMIC_FORM_CONTROL_TYPE_CONSTANT:
          return new MyDynamicCustomControlModel(model, layout);
      }
    }
  }],

the custom model should define the inherited type field as serializable:

@serializable() readonly type: string = MY_CUSTOM_DYNAMIC_FORM_CONTROL_TYPE_CONSTANT;

everything else is done by the docs

That's exactly what I did except that my custom map fn runs before the default fromJSON's switch

Let me know what you think

@sarora2073
Copy link

@ronnetzer - thanks for this solution! hope it will make it into the package.. it's a life saver for anyone relying on custom controls and fromJSON model.

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

No branches or pull requests

10 participants