ng-dynamic-forms icon indicating copy to clipboard operation
ng-dynamic-forms copied to clipboard

Custom Form Control sample example Needs

Open NurdinDev opened this issue 7 years ago • 16 comments
trafficstars

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;

        }
     }
  }
]

NurdinDev avatar May 28 '18 09:05 NurdinDev

@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;

        }
     }
  }
]

udos86 avatar May 31 '18 18:05 udos86

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?

NurdinDev avatar May 31 '18 20:05 NurdinDev

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

udos86 avatar May 31 '18 22:05 udos86

@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!

maroy1986 avatar Jun 07 '18 14:06 maroy1986

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?

ironbirdie avatar Aug 02 '18 10:08 ironbirdie

@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 avatar Aug 08 '18 17:08 ntziolis

Did some digging: https://github.com/udos86/ng-dynamic-forms/blob/edd11d9b808175ac0f69844d756956f935800f69/packages/core/src/service/dynamic-form.service.ts#L406

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

ntziolis avatar Aug 08 '18 17:08 ntziolis

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.

darrenmothersele avatar Feb 25 '19 11:02 darrenmothersele

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 avatar Mar 27 '19 19:03 danielleiszen

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

danielleiszen avatar Mar 27 '19 22:03 danielleiszen

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

On Thu, 28 Mar, 2019, 3:54 AM danielleiszen, [email protected] wrote:

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(`unknown form control model type defined on JSON object 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

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/udos86/ng-dynamic-forms/issues/762#issuecomment-477370340, or mute the thread https://github.com/notifications/unsubscribe-auth/AQi9nJaL8vGziDg8ZTFYVn0EpmbxnU_3ks5va--SgaJpZM4UP8de .

rockeshub avatar Mar 28 '19 10:03 rockeshub

@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

danielleiszen avatar Mar 29 '19 14:03 danielleiszen

Thank you

On Fri, 29 Mar, 2019, 7:34 PM danielleiszen, [email protected] wrote:

@rockeshub https://github.com/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

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/udos86/ng-dynamic-forms/issues/762#issuecomment-478009199, or mute the thread https://github.com/notifications/unsubscribe-auth/AQi9nFdAb2RvL1RTdLpbWP9UfCZ76xOrks5vbh1YgaJpZM4UP8de .

rockeshub avatar Apr 01 '19 06:04 rockeshub

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?

darrenmothersele avatar Apr 02 '19 10:04 darrenmothersele

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

https://github.com/udos86/ng-dynamic-forms/pull/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

ronnetzer avatar Aug 17 '19 16:08 ronnetzer

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

sarora2073 avatar Jan 02 '21 20:01 sarora2073