dschnelldavis / angular2-json-schema-form

Angular 2 JSON Schema Form builder
MIT License
285 stars 177 forks source link

Widget not found in library #155

Open jscharett opened 6 years ago

jscharett commented 6 years ago

Issue type

I'm submitting a (check one): [X ] Bug report

Prerequisites

Using version 0.5.0-alpha.16. Newer version(s) seem to require Angular 5, which I do not currently support. Was not able to get app to compile with newer version(s)

Current behavior

Custom Widget is not not found. I register the widget according to the documentation, but it is not found when the form tries to render. Believe this may be due to the Services be set as providers on multiple modules? Seems like a service should only be provided once, and if needed elsewhere, that module imported. Or maybe its because I'm using lazy Loading and registering it in the parent module?

Expected behavior

Widget should be found after registration.

IMPORTANT: How can we reproduce your problem?

OK, so here is a general idea of how I am implementing. Will try to make plunker when I have time

core.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { JsonSchemaFormModule } from 'angular2-json-schema-form';

import { LookupComponent } from './json-schema-widgets/lookup/lookup.component';

@NgModule({
    entryComponents: [
        LookupComponent
    ],
    declarations: [
        LookupComponent
    ],
    imports: [
        CommonModule,
        FormsModule, ReactiveFormsModule,
        JsonSchemaFormModule
    ],
    exports: [
        LookupComponent,
        JsonSchemaFormModule
    ]
})
export class CoreModule {}

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { CoreModule } from './core/core.module';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRoutingModule,
        HttpClientModule,
        CoreModule
    ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';

import { WidgetLibraryService } from 'angular2-json-schema-form';

import { LookupComponent } from './core/json-schema-widgets/lookup/lookup.component';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    constructor(private widgetLibrary: WidgetLibraryService) {
        widgetLibrary.registerWidget('lookup', LookupComponent);
        // Verified that widget is registered in library, at least THIS instance
    }
}

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

export const routes: Routes = [{
    path: 'rule',
    loadChildren: 'app/rule/rule.module#RuleModule'
}];

@NgModule({
    imports: [ RouterModule.forRoot(routes) ],
    exports: [ RouterModule ]
})
export class AppRoutingModule {}

rule.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { CoreModule } from '../core/core.module';

import { RuleRoutingModule } from './rule-routing.module';

import { AttributesComponent } from './attributes/attributes.component';

@NgModule({
    declarations: [
        AttributesComponent
    ],
    imports: [
        CommonModule,
        RuleRoutingModule,
        CoreModule,
        FormsModule,
        ReactiveFormsModule
    ]
})
export class RuleModule { }

attributes.component.ts

import { Component } from '@angular/core';

@Component({
    selector: 'app-attributes',
    templateUrl: './attributes.component.html',
    styleUrls: ['./attributes.component.css']
})
export class AttributesComponent  {
    formLayout = [
        "name",
        {
            "type": "lookup",
            "key": "type",
            "options": {
                "source": "TYPE"
            }
        }
    ];
    formSchema = {
        "additionalProperties": false,
        "type": "object",
        "properties": {
            "name": {
                "title": "Name",
                "type": "string"
            },
            "type": {
                "items": {
                    "type": "string"
                },
                "minItems": 1,
                "title": "Type",
                "type": "array",
                "uniqueItems": true
            }
        },
        "required": ["name", "type"],
    };
    formOptions = {
        addSubmit: false
    };
    formData = {};

    constructor() { }
}

attributes.component.html

<json-schema-form
    [schema]="formSchema"
    [data]="formData"
    [form]="formLayout"
    [options]="formOptions"
    framework="bootstrap-3">
</json-schema-form>

Environment

@angular/cli: 1.4.9 node: 6.9.3 os: win32 x64 @angular/animations: 4.4.6 @angular/cdk: 2.0.0-beta.12 @angular/common: 4.4.6 @angular/compiler: 4.4.6 @angular/core: 4.4.6 @angular/forms: 4.4.6 @angular/flex-layout: 2.0.0-beta.10-4905443 @angular/http: 4.4.6 @angular/material: 2.0.0-beta.12 @angular/platform-browser: 4.4.6 @angular/platform-browser-dynamic: 4.4.6 @angular/router: 4.4.6 @angular/cli: 1.4.9 @angular/compiler-cli: 4.4.6 @angular/language-service: 4.4.6 typescript: 2.5.3 angular2-json-schema-form: 0.5.0-alpha.16

jscharett commented 6 years ago

So, after some digging, it appears that there is an issue with lazyloading and the ngModules defined in this library. As per the Angular NgModule spec, shared modules need to define their providers in a static forRoot method. This would explain why I'm seeing different instances between my lazy loaded modules.

Thorski commented 6 years ago

I don't know if this is related to your problem, but <json-schema-form [schema]="formSchema" [data]="formData" [form]="formLayout" [options]="formOptions" framework="bootstrap-3">

[form]="formLayout" appears incorrect. [form] input is expecting object of entire form (schema, layout, data, etc) you may want [layout]="formLayout"

jscharett commented 6 years ago

Thanks, I corrected, but that did not solve my issue. I ended up recreating the modules so that they use forRoot to expose the providers. That resolved the issue, which points to a bug with lazy loading. Here is my temporay solution. Would love to see a fix put in; even for 0.5 if possible

import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';

import {
    MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule,
    MatDatepickerModule, MatFormFieldModule, MatGridListModule, MatIconModule,
    MatInputModule, MatNativeDateModule, MatRadioModule, MatSelectModule,
    MatSliderModule, MatSlideToggleModule, MatTabsModule, MatTooltipModule,
  } from '@angular/material';

import {
    JsonSchemaFormComponent,
    JsonSchemaFormService,
    FrameworkLibraryService,
    WidgetLibraryService,
    NoFrameworkComponent,
    OrderableDirective,
    Bootstrap3FrameworkComponent,
    AddReferenceComponent, AnyOfComponent, ButtonComponent, CheckboxComponent,
    CheckboxesComponent, FileComponent, HiddenComponent, InputComponent,
    MessageComponent, NoneComponent, NumberComponent, RadiosComponent,
    RootComponent, SectionComponent, SelectComponent, SelectFrameworkComponent,
    SelectWidgetComponent, SubmitComponent, TabComponent, TabsComponent,
    TemplateComponent, TextareaComponent,
    FlexLayoutRootComponent, FlexLayoutSectionComponent,
    MaterialAddReferenceComponent, MaterialAnyOfComponent,
    MaterialButtonComponent, MaterialButtonGroupComponent,
    MaterialCheckboxComponent, MaterialCheckboxesComponent,
    MaterialChipListComponent, MaterialDatepickerComponent,
    MaterialFileComponent, MaterialInputComponent, MaterialNumberComponent,
    MaterialRadiosComponent, MaterialSelectComponent, MaterialSliderComponent,
    MaterialStepperComponent, MaterialTabsComponent, MaterialTextareaComponent,
    MaterialDesignFrameworkComponent
} from 'angular2-json-schema-form';

export const BASIC_WIDGETS = [
    AddReferenceComponent, AnyOfComponent, ButtonComponent, CheckboxComponent,
    CheckboxesComponent, FileComponent, HiddenComponent, InputComponent,
    MessageComponent, NoneComponent, NumberComponent, RadiosComponent,
    RootComponent, SectionComponent, SelectComponent, SelectFrameworkComponent,
    SelectWidgetComponent, SubmitComponent, TabComponent, TabsComponent,
    TemplateComponent, TextareaComponent
];

export const MATERIAL_DESIGN_COMPONENTS = [
    FlexLayoutRootComponent, FlexLayoutSectionComponent,
    MaterialAddReferenceComponent, MaterialAnyOfComponent,
    MaterialButtonComponent, MaterialButtonGroupComponent,
    MaterialCheckboxComponent, MaterialCheckboxesComponent,
    MaterialChipListComponent, MaterialDatepickerComponent,
    MaterialFileComponent, MaterialInputComponent, MaterialNumberComponent,
    MaterialRadiosComponent, MaterialSelectComponent, MaterialSliderComponent,
    MaterialStepperComponent, MaterialTabsComponent, MaterialTextareaComponent,
    MaterialDesignFrameworkComponent
  ];

@NgModule({
    declarations: [
        ...BASIC_WIDGETS,
        OrderableDirective
    ],
    imports: [
        CommonModule,
        FormsModule, ReactiveFormsModule
    ],
    exports: [
        ...BASIC_WIDGETS,
        OrderableDirective
    ],
    entryComponents: [
        ...BASIC_WIDGETS
    ],
})
export class WidgetLibraryModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: WidgetLibraryModule,
            providers: [
                JsonSchemaFormService,
                WidgetLibraryService
            ]
        };
    }
}

@NgModule({
    declarations: [
        Bootstrap3FrameworkComponent
    ],
    imports: [
        CommonModule,
        WidgetLibraryModule
    ],
    exports: [
        Bootstrap3FrameworkComponent
    ],
    entryComponents: [
        Bootstrap3FrameworkComponent
    ],
})
export class Bootstrap3FrameworkModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: Bootstrap3FrameworkModule,
            providers: [
                FrameworkLibraryService,
                WidgetLibraryService
            ]
        };
    }
}

@NgModule({
    declarations: [
       ...MATERIAL_DESIGN_COMPONENTS
    ],
    imports: [
        CommonModule,
        FlexLayoutModule, FormsModule, ReactiveFormsModule,
        WidgetLibraryModule,
        MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule,
        MatDatepickerModule, MatFormFieldModule, MatGridListModule, MatIconModule,
        MatInputModule, MatNativeDateModule, MatRadioModule, MatSelectModule,
        MatSliderModule, MatSlideToggleModule, MatTabsModule, MatTooltipModule
    ],
    exports: [
        ...MATERIAL_DESIGN_COMPONENTS
    ],
    entryComponents: [
        ...MATERIAL_DESIGN_COMPONENTS
    ],
})
export class MaterialDesignFrameworkModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: MaterialDesignFrameworkModule,
            providers: [
                JsonSchemaFormService
            ]
        };
    }
}

@NgModule({
    declarations: [
        NoFrameworkComponent
    ],
    imports: [
        CommonModule,
        WidgetLibraryModule,
        Bootstrap3FrameworkModule, MaterialDesignFrameworkModule
    ],
    exports: [
        NoFrameworkComponent,
        Bootstrap3FrameworkModule, MaterialDesignFrameworkModule
    ],
    entryComponents: [
        NoFrameworkComponent
    ]
})
export class FrameworkLibraryModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: FrameworkLibraryModule,
            providers: [
                FrameworkLibraryService,
                WidgetLibraryService
            ]
        };
    }
}

@NgModule({
    declarations: [
        JsonSchemaFormComponent
    ],
    imports: [
        CommonModule, FormsModule, ReactiveFormsModule,
        FrameworkLibraryModule, WidgetLibraryModule
    ],
    exports: [
        JsonSchemaFormComponent,
        FrameworkLibraryModule, WidgetLibraryModule
    ]
})
export class JsonSchemaFormModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: JsonSchemaFormModule,
            providers: [
                JsonSchemaFormService,
                FrameworkLibraryService,
                WidgetLibraryService
            ]
        };
    }
}

it allows me to import JsonSchemaFormModule in my core module like so

import { NgModule, ModuleWithProviders } from '@angular/core';

import { JsonSchemaFormModule } from '../../json-schema-form.module';

@NgModule({
    imports: [
        JsonSchemaFormModule
    ],
    exports: [
        JsonSchemaFormModule
    ]
})
export class CoreModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: CoreModule,
            providers: JsonSchemaFormModule.forRoot().providers
        };
    }
}

The CoreModule is then imported into all my lazy loaded modules and in my app module I import Core.forRoot(), so it gets the providers. Shared providers should only be set in the root module.

jscharett commented 6 years ago

Looks like I am still blocked by this issue. while declaring my own module resolved the issue for dev, prod still fails due to components being declared in multiple modules. Would love to see a fix for this.

person2713 commented 6 years ago

Can you help me? How you set layout?