Saturday, May 26, 2018

Typescript properties concatenation

TypeScript has a unique functionality, it can concatenate properties inline.

Given these following classes from C#
public class PagedDto<T>
{
    public IList<T> Data { get; set; }
    public int Pages { get; set; }
}

public class PagedCityDto
{
    public int CityId { get; set; }
    public string CityName { get; set; }        

    public int StateId { get; set; }
    public string StateName { get; set; }
}   


The above are translated to these:
export interface IPagedDto<T>
{
    data: T[];
    pages: number;
}

export interface IPagedCityDto
{
    cityId: number;
    cityName: string;

    stateId: number;
    stateName: string;
}


Then the following properties concatenation..
interface IComponentState
{
    grid: IPagedDto<IPagedCityDto> & {
        loading: boolean;
    };
}

..is expanded to:
interface IComponentState
{
    grid: {
        data: IPagedCityDto[];
        pages: number;
        loading: boolean;
    };
}


Why would we do concatenation? Why not just put the loading property out-of-band from grid property like this?
interface IComponentState
{
    grid: IPagedDto<IPagedCityDto>;
    isLoading: boolean;   
}

Well we need those three properties to be grouped in one property (i.e., grid) as they are related to each other. Besides, putting the isLoading outside of grid object is a cognitive load for other devs reading your code, the code would give them impression that isLoading property is related to the whole component and not specific to grid. One might say, that it can be renamed to isGridLoading to signify it pertains to the grid only.

interface IComponentState
{
    grid: IPagedDto<IPagedCityDto>;
    isGridLoading: boolean;   
}

Still the best way to write readable code is to group related things together, not just by naming convention, but through whatever the best mechanism the programming language can provide to the developer. The following is better than the code above.

interface IComponentState
{
    grid: {
        data: IPagedCityDto[];
        pages: number;
        loading: boolean;
    };
}

Better yet, use TypeScript's built-in concatenation mechanism:
interface IComponentState
{
    grid: IPagedDto<IPagedCityDto> & {
        loading: boolean;
    };
}

There's also a symmetry when grouped properties are used:

<ReactTable
    manual={true}
    columns={[
        {
            Header  : <Typography>City</Typography>,
            accessor: 'cityName',
            id      : 'City',
            Cell    : row => (
                <Typography>
                    <a href={'/city/' + row.original.cityId}>{row.value}</a>
                </Typography>
            )
        },
        {
            Header  : <Typography>State</Typography>,
            accessor: 'stateName',
            id      : 'State'
        }
    ]}
    onFetchData={this.fetchData}
    data={this.state.grid.data}
    pages={this.state.grid.pages}
    loading={this.state.grid.loading}
    defaultPageSize={10}
    className='-striped -highlight'
/>


Another nice thing when related properties are grouped together, it makes the code shorter and idiomatic when used with spread operator:

<ReactTable
    manual={true}
    columns={[
        {
            Header  : <Typography>City</Typography>,
            accessor: 'cityName',
            id      : 'City',
            Cell    : row => (
                <Typography>
                    <a href={'/city/' + row.original.cityId}>{row.value}</a>
                </Typography>
            )
        },
        {
            Header  : <Typography>State</Typography>,
            accessor: 'stateName',
            id      : 'State'
        }
    ]}
    onFetchData={this.fetchData}
    {...this.state.grid}
    defaultPageSize={10}
    className='-striped -highlight'
/>


Happy Coding!

No comments:

Post a Comment