How do we properly handle undefined and null when generating strict typed forms with custom...

Discussão em 'Angular' iniciado por Optiq, Março 7, 2025.

  1. Optiq

    Optiq Guest

    I'm creating functions for generating a form that not only has optional fields but will serve as both a "new item" form and an "edit item" form meaning in one instance it will be populated with data and in the other it will generate a blank form.

    I'm making the form strictly typed and am a bit tripped up with properly handling undefined and null instances. The form is pretty intricate so I'll just outline the way I'm going about things rather than trying to figure out how much code is "too little code" or "too much unnecessary code" with adding actual code from my project. However if you would like to see that leave a comment and I will most definitely update my question with the requested code.

    To start out, I handle individual form controls as follows

    FormControl Method

    export type SomeControlType01 = 'someOption01' | 'someOption02' | 'someOption03';



    export function createSomeControl01( defaultValue? : SomeControlType01 ) : FormControl< SomeControlType01 | null > {


    return new FormControl< SomeControlType01 | null>( defaultValue ?? null );


    }


    Then from there I'll call my FormControl methods in my FormGroup methods like this

    FormGroup Method

    export interface SomeGroup01Data {

    prop01 : string;
    prop02 : someControlType01;

    }




    export interface SomeGroup01FormGroup {

    prop01 : FormControl< string | null >;
    prop02 : FormControl< someControlType01 | null >;

    }



    export function createSomeGroup01( defaultData? : SomeGroup01Data ) : FormGroup< SomeGroup01FormGroup >{



    return new FormGroup< SomeGroup01FormGroup >({

    prop01 : new FormControl< string | null >( defaultData?.prop01 ?? null ),
    prop02 : createSomeControl01( defaultData ? defaultData.prop02 : undefined )

    });



    }


    I have groups where some fields are optional which I handle like this

    FormGroups With Optional Props

    export interface SomeGroup02Data {

    propB01? : string;
    propB02 : string;

    }



    export interface SomeGroup02FormGroup {

    propB01? : FormControl< string | null >;
    propB02 : FormControl< string | null >;

    }



    export function createSomeGroup02( defaultData? : SomeGroup02Data ) : FormGroup< SomeGroup02FormGroup > {



    const group : FormGroup< SomeGroup02FormGroup >({

    propB02 : new FormControl< string | null >( defaultData?.propB02 ?? null )

    });



    if(defaultData?.propB01){

    group.addControl( 'propB01', new FormControl< string | null >( defaultData.propB01 ) );

    }



    return group;



    }


    I also have optional fields with a variety of potential types which will be determined by the value of another property which I'm handling in this fashion

    Optional Props That Depend on The Value Of Another Prop

    export type FlexiblePropDataType = SomeInterface01 | SomeInterface02 | SomeInterface03 | SomeInterface04;

    export type FlexiblePropFormGroupType = FormGroup< SomeInterface01FormGroup > | FormGroup< SomeInterface02FormGroup > | FormGroup< SomeInterface03FormGroup > | FormGroup< SomeInterface04FormGroup >;

    export type FlexiblePropOptionsType = 'interface01' | 'interface02' | 'interface03' | 'interface04';


    export interface SomeGroup03Data {

    propC01 : string;
    propC02? : FlexiblePropOptionsType;
    propC03? : FlexiblePropDataType;

    }


    export interface SomeGroup03FormGroup {

    propC01 : FormControl< string | null >;
    propC02? : FormControl< FlexiblePropOptionsType | null >;
    propC03? : FlexiblePropFormGroupType;

    }





    export function createPropC03Group( propType : FlexiblePropOptionsType, data? : FlexiblePropDataType ) : FlexiblePropFormGroupType {

    switch( propType ) {

    case 'interface01' : return createInterface01( data ? data : undefined ); break;
    case 'interface02' : return createInterface02( data ? data : undefined ); break;
    case 'interface03' : return createInterface03( data ? data : undefined ); break;
    case 'interface04' : return createInterface04( data ? data : undefined ); break;

    }

    }



    export function createSomeGroup03( defaultData? : SomeGroup03Data ) : FormGroup< SomeGroup03FormGroup > {

    const group : FormGroup< SomeGroup03FormGroup > = new FormGroup< SomeGroup03FormGroup >({

    propC01 : new FormControl< string | null >( defaultData?.propC01 ?? null )

    });


    if( defaultData?.propC02 ) {

    group.addControl( 'propC02', new FormControl< FlexiblePropOptionsType | null >( defaultData.propC02 );

    group.addControl( 'propC03', createPropC03Group( defaultData.propC02, defaultData.propC03 ?? undefined );

    }

    }



    My entire form is defined using these approaches to the different instances where I want to accomplish those things. Right now I'm trying to get it to work in the item editor I'm building. The way it works is when they user clicks on an item, that component sends the item data to a service file where its stored in a ItemToEdit variable while also triggering a EditorToggle variable which is a WriteableSignal< boolean > exposed by another computed() signal.

    The parent component of the ItemEditorComponent reads the EditorToggle signal and when it changes to true renders a ModalComponent that displays the ItemEditorComponent via content projection. The ItemEditorComponent then fetches the ItemToEdit variable from the service file and stores it into a local variable.

    When I render the Item in the ItemEditorComponent's template with {{Item | json}} I can see the data is present. However when I go to create my form I get this error


    ERROR TypeError: Cannot read properties of undefined (reading 'enabled')
    ​

    It then shows a trace from where it happens back to the line in the component where I generate the form. The way that component is setup looks like this

    MyParentComponent

    import { createMyForm } from "./some/folder/another-folder/create-my-form"


    export class MyParentComponent implements OnInit {

    Item! : ItemInterface;
    MyForm! : FormGroup< MyFormInterface >;



    constructor( private dataService : ItemDataService ) {}



    ngOnInit() : void {

    this.Item = this.dataService.getItemToEdit();

    this.MyForm = createMyForm( this.dataService.getItemToEdit() );

    }


    }


    The only things I've tried to do is try different ways of handling where things could be undefined or null. In fact prior to the way I'm doing things now with if conditions I was using ternaries like this

    Initial Ternary Attempt

    export function createBlahBlah( data? : BlahDataBlah ) : FormGroup< BlahBlah > {

    return new FormGroup< BlahBlah >({

    someOptionalProp : ( data?.someOptionalProp ? createTheProp( data.someOptionalProp ) : undefined )

    });
    }


    That yielded the same exact error in the same exact file which is a function that creates a required property on an optional object in my form which is the first instance of an optional property in my form. Seeing that it's pointing to an enabled property somewhere I'm guessing that's something within the ControlState or FormGroup not being set as a result of undefined and or null not being handled properly. Does anyone see what I'm doing wrong here? Again if you want to see any particular code just let me know and I'll update the question.

    Continue reading...

Compartilhe esta Página