Jsonforms in Angular standalone without material

Hi everyone,

I’m working with a standalone Angular 18 application and I want to integrate JSONForms. However, I don’t want to use Material UI. Instead, I’ve created my own custom input component.

For example, here’s how my input component looks:

html

<our-input 
  [formControl]="form"
  [type]="'text'"
  [label]="'test'"
></our-input>

Now, I’d like to render a form using a JSON schema and UI schema, but have it use my custom component instead of Material inputs.

How can I configure JSONForms to use this component when rendering fields like this?
here is just as example my schema and UIschema

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },

    "birthDate": {
      "type": "string"
    },

    "occupation": {
      "type": "string"
    }
  }
}

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "HorizontalLayout",
      "elements": [
        {
          "type": "Control",
          "scope": "#/properties/name"
        },

        {
          "type": "Control",
          "scope": "#/properties/birthDate"
        },
        {
          "type": "Control",
          "scope": "#/properties/occupation"
        }
      ]
    }
  ]
}

and here is my tester

import { rankWith, schemaTypeIs } from '@jsonforms/core';

export const insureInputTester = rankWith(
  5, // must be > 1
  schemaTypeIs('string'),
);

here i get the static schema and uischema as example

import schemaAsset from './schema.json';
import * as test from './uischema.json';

export class FormsComponent implements OnInit {
  private readonly http = inject(HttpClient);

  data: any;

  customRenderers: any[] = [
    { tester: insureInputTester, renderer: FormTypesComponent },
  ];

  ngOnInit(): void {
    this.http.get('assets/data.json').subscribe((data: any) => {
      this.data = data;
      console.log('Data loaded:', this.data);
    });
  }
 onSubmit(): void {
    console.log('Form values:', this.data);
  }

  onDataChange(newData: FormData): void {
    console.log('Data changed:', newData);
    console.log('Debug: onDataChange triggered');
    this.data = newData;
  }
}

here is html of FormsComponent

@if (data && schema && uischema) {
  <jsonforms
    [data]="data"
    [schema]="schema"
    [uischema]="uischema"
    [renderers]="customRenderers"
    (dataChange)="onDataChange($event)"
  ></jsonforms>
}

<button (click)="onSubmit()">Submit</button>

here is rederer component

@Component({
  selector: 'form-management-form-types',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './form-types.component.html',
  styleUrl: './form-types.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FormTypesComponent extends JsonFormsControl {
  constructor(jsonFormsService: JsonFormsAngularService) {
    super(jsonFormsService);
    console.log('FormTypesComponent initialized');
  }
}

and here is html of FormTypesComponent

<our-input
  [formControl]="form"
  [type]="'text'"
  [label]="'test'"
></our-input>

I tried setting it up, but after running the app, I see the following message on the page:

No applicable renderer found.

I assume it’s not picking up my custom renderer correctly. How can I register or bind my custom component so JSONForms recognizes and uses it?

And another question
If I don’t use Angular Material, how can I implement Layouts like VerticalLayout in UI schema?

Okay, I figured out where the issue was. The problem was that since I hadn’t installed Angular Material, JSONForms couldn’t recognize and render the VerticalLayout and HorizontalLayout elements. That’s why I had to create and register a custom renderer for each of those layout types.

For example, for VerticalLayout, I created a custom renderer like this:

// vertical-layout.renderer.ts
import { Component } from '@angular/core';
import { InsureComponentsModule } from '@insure/ngx-components';
import { JsonFormsBaseRenderer, JsonFormsModule } from '@jsonforms/angular';
import { Layout, rankWith, uiTypeIs } from '@jsonforms/core';

@Component({
  selector: 'form-management-layout-renderer',
  standalone: true,
  imports: [JsonFormsModule, InsureComponentsModule],
  template: `
    <div style="display: flex; flex-direction: column; gap: 1rem;">
      @for (el of uischema?.elements; track el) {
        <jsonforms-outlet
          [schema]="schema"
          [uischema]="el"
          [path]="path"
        ></jsonforms-outlet>
      }
    </div>
  `,
})
export class VerticalLayoutRendererComponent extends JsonFormsBaseRenderer<Layout> {
  override uischema!: Layout;
}

export const verticalLayoutTester = rankWith(1, uiTypeIs('VerticalLayout'));

Now the issue I’m currently facing is that when the user changes the data in the form and clicks the submit button, the data doesn’t get updated. It seems like the onDataChange event is not triggered at all.

How can the submit button be disabled when a field is required but hasn’t been filled in yet?

Hi @vsam,

You are definitely on the right path. It looks like you are making good progress.

In your custom input renderer, are you calling onChange? This will update the internal state of JSON Forms and thereby trigger the onDataChange event. See here and here on how the Angular Material text renderer handles it.

Most adopters listen to the errors emitted by JSON Forms. If it’s not empty, then they disable the “submit”.

Thank you so much for your responses. @sdirix

However, there’s still an issue: when a field is marked as required in the schema, the error array is empty if that field is left empty.
For example, if a text input is defined as required in the JSON schema, and the user leaves it empty in the form, no error message appears — even though it should.

And the next question I have is about using rules in the schema.

I’m trying to use a rule in the schema like this:

{
  "properties": {
  
    "vegetables": {
      "type": "boolean"
    },
    "kindOfVegetables": {
      "type": "string"
    }
  },
  "required": ["kindOfVegetables"]
}

UIschema

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "label": "Eats vegetables?",
      "scope": "#/properties/vegetables"
    },
    {
      "type": "Control",
      "label": "Kind of vegetables",
      "scope": "#/properties/kindOfVegetables",
      "rule": {
        "effect": "HIDE",
        "condition": {
          "scope": "#/properties/vegetables",
          "schema": {
            "const": true
          }
        }
      }
    }
  ]
}

But the problem is:
When I click on the checkbox (vegetables), the updated value is not passed correctly to the component, and as a result, the new value doesn’t trigger the visibility change (e.g., hidden doesn’t update).

That should not happen. Can you post your schema and data?

Does the checkbox correctly update the JSON Forms internal state? Can you post the code of your custom renderer?

I uploaded the code related to the form here.

Currently, I’m using if/then/else in the schema, and I want a text input field to become required when a checkbox is ticked.

Since I’m using UI components that were developed by another team, I can’t directly modify them. So I need a way to detect at runtime whether the field is currently required or not, depending on the checkbox state.

My question is:
Is there a way to determine whether the field is required at a given moment, based on the current form state and the schema logic?

Hi @vsam,

No this is not possible. We use AJV for validation, so if a dynamic error is violated, then you will get an appropriate error. However if all required constraints are followed, there will be no error and you will not know whether this field was required or not.

Within JSON Forms itself we only do a very simple analysis whether a field is required or not, basically checking the parent object’s required.

You can find more information in this PR in which is was suggested to add more powerful requirements analysis to JSON Forms.

As a workaround, you can always add a custom required analysis for the patterns commonly encountered in your leveraged JSON Schemas and then render your controls correspondingly.

1 Like