Luis Valencia
Luis Valencia

Reputation: 34028

Typescript refactor switch statement

I have a switch statement but the content on each case its very similar, basically the only difference is the URL with the select properties in the REST call, and the .then when the json result is converted to a specific collection of specific types.

import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http";
import { IWebPartContext } from "@microsoft/sp-webpart-base";
import { IListItem} from "./models/IListItem";
import { IFactory } from "./IFactory";
import { INewsListItem } from "./models/INewsListItem";
import { IDirectoryListItem } from "./models/IDirectoryListItem";
import { IAnnouncementListItem } from "./models/IAnnouncementListItem";

export class ListItemFactory implements IFactory {
    // private _listItems: IListItem[];
    public getItems(requester: SPHttpClient, siteUrl: string, listName: string): Promise<any[]> {
        switch(listName) {
            case "GenericList":
                let items: IListItem[];
                // tslint:disable-next-line:max-line-length
                return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`,
                SPHttpClient.configurations.v1,
                {
                    headers: {
                        "Accept": "application/json;odata=nometadata",
                        "odata-version": ""
                    }
                })
                .then((response: SPHttpClientResponse): Promise<{ value: IListItem[] }> => {
                    return response.json();
                })
                .then((json: { value: IListItem[] }) => {
                    console.log(JSON.stringify(json.value));
                    return items=json.value.map((v,i)=>(
                        {
                            // key: v.id,
                            id: v.Id,
                            title: v.Title,
                            created: v.Created,
                            createdby: v.Author.Title,
                            modified: v.Modified,
                            modifiedby: v.Editor.Title
                        }
                    ));
                });
            case "News":
                let newsitems: INewsListItem[];
                // tslint:disable-next-line:max-line-length
                return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Created By,Modified By,newsheader,newsbody,expiryDate`,
                SPHttpClient.configurations.v1,
                {
                    headers: {
                        "Accept": "application/json;odata=nometadata",
                        "odata-version": ""
                    }
                })
                .then((response: SPHttpClientResponse): Promise<{ value: INewsListItem[] }> => {
                    return response.json();
                })
                .then((json: { value: INewsListItem[] }) => {
                    return newsitems=json.value.map((v,i)=>(
                        { 
                            id: v.Id,
                            title: v.Title,
                            created: v.Created,
                            createdby: v.Author.Title,
                            modified: v.Modified,
                            modifiedby: v.Editor.Title,
                            newsheader: v.newsheader,
                            newsbody: v.newsbody,
                            expiryDate: v.expiryDate
                        }
                    ));
                });
            case "Announcements":
                let announcementitems: IAnnouncementListItem[];
                return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Created,Author/Title,Modified,Editor/Title,announcementBody,expiryDate&$expand=Author,Editor`,
                SPHttpClient.configurations.v1,
                {
                    headers: {
                        "Accept": "application/json;odata=nometadata",
                        "odata-version": ""
                    }
                })
                .then((response: SPHttpClientResponse): Promise<{ value: IAnnouncementListItem[] }> => {
                    return response.json();
                })
                .then((json: { value: IAnnouncementListItem[] }) => {
                    return announcementitems=json.value.map((v,i)=>(
                        { 
                            id: v.Id,
                            title: v.Title,
                            created: v.Created,
                            createdby: v.Author.Title,
                            modified: v.Modified,
                            modifiedby: v.Editor.Title,
                            announcementBody: v.announcementBody,
                            expiryDate: v.expiryDate
                        }
                    ));
                });
            case "Directory":
                let directoryitems: IDirectoryListItem[];
                return requester.get(`${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
                SPHttpClient.configurations.v1,
                {
                    headers: {
                        "Accept": "application/json;odata=nometadata",
                        "odata-version": ""
                    }
                })
                .then((response: SPHttpClientResponse): Promise<{ value: IDirectoryListItem[] }> => {
                    return response.json();
                })
                .then((json: { value: IDirectoryListItem[] }) => {
                    return directoryitems=json.value.map((v,i)=>(
                        {
                            id: v.Id,
                            title: v.Title,
                            created: v.Created,
                            createdby: v.Author.Title,
                            modified: v.Modified,
                            modifiedby: v.Editor.Title,
                            firstName: v.firstName,
                            lastName: v.lastName,
                            mobileNumber: v.mobileNumber,
                            internalNumber: v.internalNumber
                        }
                    ));
                });
            default:
                break;
            }
      }
}

Update 1

Based on the answer I changed the IFactory.ts

import { IListItem } from "./models/IListItem";
import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http";
import { ListItemFactory } from "./ListItemFactory";
export  interface IFactory {

    getItems<K extends keyof ListItemFactory['handlers']>(requester: SPHttpClient, siteUrl: string, listName: K): Promise<ReturnType<ListItemFactory['handlers'][K]['process']>> ;


    //getItems(requester: SPHttpClient, siteUrl: string, listName: string): Promise<any[]>;
}

And the ListItemFactory.ts

import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http";
import { IWebPartContext } from "@microsoft/sp-webpart-base";
import { IListItem} from "./models/IListItem";
import { IFactory } from "./IFactory";
import { INewsListItem } from "./models/INewsListItem";
import { IDirectoryListItem } from "./models/IDirectoryListItem";
import { IAnnouncementListItem } from "./models/IAnnouncementListItem";

export class ListItemFactory implements IFactory {
    private handlers = {
        GenericList : {
            url: (siteUrl: string, listName: string) => `${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`,
            async process(result: any)  {
                const json: { value: IListItem[] } = result;
                return json.value.map((v,i)=>({
                    // key: v.id,
                    id: v.Id,
                    title: v.Title,
                    created: v.Created,
                    createdby: v.Author.Title,
                    modified: v.Modified,
                    modifiedby: v.Editor.Title
                }));
            }
        },
        News: {
            url: (siteUrl: string, listName: string) => `${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Created By,Modified By,newsheader,newsbody,expiryDate`,
            async process(result: any) {
                const json: { value: INewsListItem[] } = result;
                return json.value.map((v,i)=>(
                    { 
                        id: v.Id,
                        title: v.Title,
                        created: v.Created,
                        createdby: v.Author.Title,
                        modified: v.Modified,
                        modifiedby: v.Editor.Title,
                        newsheader: v.newsheader,
                        newsbody: v.newsbody,
                        expiryDate: v.expiryDate
                    }
                ));
            }
        },
        Announcements: {
            url: (siteUrl: string, listName: string) => `${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Created,Author/Title,Modified,Editor/Title,announcementBody,expiryDate&$expand=Author,Editor`,
            async process(result: any) {
                const json: { value: IAnnouncementListItem[] } = result;
                return json.value.map((v,i)=>({ 
                    id: v.Id,
                    title: v.Title,
                    created: v.Created,
                    createdby: v.Author.Title,
                    modified: v.Modified,
                    modifiedby: v.Editor.Title,
                    announcementBody: v.announcementBody,
                    expiryDate: v.expiryDate
                }));
            }
        },
        Directory: {
            url: (siteUrl: string, listName: string) => `${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
            process(result: any) {
                const json: { value: IDirectoryListItem[] } = result;
                return json.value.map((v,i)=>({
                    id: v.Id,
                    title: v.Title,
                    created: v.Created,
                    createdby: v.Author.Title,
                    modified: v.Modified,
                    modifiedby: v.Editor.Title,
                    firstName: v.firstName,
                    lastName: v.lastName,
                    mobileNumber: v.mobileNumber,
                    internalNumber: v.internalNumber
                }));
            }
        }
    };

    public async getItems<K extends keyof ListItemFactory['handlers']>(requester: SPHttpClient, siteUrl: string, listName: K): Promise<ReturnType<ListItemFactory['handlers'][K]['process']>> {
        var h = this.handlers[listName];
        const response = await requester.get(
            h.url(siteUrl, listName),
            SPHttpClient.configurations.v1,
            {
                headers: {
                    "Accept": "application/json;odata=nometadata",
                    "odata-version": ""
                }
        });
        var json = response.json();
        return h.process(json) as any;
    }
}

However I get this errors

[19:42:56] Error - typescript - src/webparts/factoryMethod/components/IFactory.ts(5,122): error TS2304: Cannot find name 'ReturnType'.
[19:42:56] Error - typescript - src/webparts/factoryMethod/components/ListItemFactory.ts(80,135): error TS2304: Cannot find name 'ReturnType'.
[19:42:56] Error - typescript - src/webparts/factoryMethod/components/IFactory.ts(5,122): error TS4057: Return type of method from exported interface has or is using private name 'ReturnType'.
[19:42:56] Error - typescript - src/webparts/factoryMethod/components/ListItemFactory.ts(80,135): error TS4055: Return type of public method from exported class has or is using private name 'ReturnType'.
[19:42:56] Finished subtask 'tslint' after 2.93 s
[19:42:56] Error - 'typescript' sub task errored after 2.06 s
 TypeScript error(s) occurred.

Upvotes: 4

Views: 505

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250036

I would take out the common parts from each switch and move the different parts in a map object. The uncommon parts seem to be the url and how the result is processed.

Also we can do better with the return type and the list type. We restrict the key to only be one of the supported values, and to have a return type specific to the listName passed in.

export class ListItemFactory implements IFactory {
    handlers = {
        GenericList : {
            url: (siteUrl: string, listName: string) => `${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`,
            async process(result: any)  {
                const json: { value: IListItem[] } = result;
                return json.value.map((v,i)=>({
                    // key: v.id,
                    id: v.Id,
                    title: v.Title,
                    created: v.Created,
                    createdby: v.Author.Title,
                    modified: v.Modified,
                    modifiedby: v.Editor.Title
                }));
            }
        },
        News: {
            url: (siteUrl: string, listName: string) => `${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Modified,Created,Created By,Modified By,newsheader,newsbody,expiryDate`,
            async process(result: any) {
                const json: { value: INewsListItem[] } = result;
                return json.value.map((v,i)=>(
                    { 
                        id: v.Id,
                        title: v.Title,
                        created: v.Created,
                        createdby: v.Author.Title,
                        modified: v.Modified,
                        modifiedby: v.Editor.Title,
                        newsheader: v.newsheader,
                        newsbody: v.newsbody,
                        expiryDate: v.expiryDate
                    }
                ));
            }
        },
        Announcements: {
            url: (siteUrl: string, listName: string) => `${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id,Created,Author/Title,Modified,Editor/Title,announcementBody,expiryDate&$expand=Author,Editor`,
            async process(result: any) {
                const json: { value: IAnnouncementListItem[] } = result;
                return json.value.map((v,i)=>({ 
                    id: v.Id,
                    title: v.Title,
                    created: v.Created,
                    createdby: v.Author.Title,
                    modified: v.Modified,
                    modifiedby: v.Editor.Title,
                    announcementBody: v.announcementBody,
                    expiryDate: v.expiryDate
                }));
            }
        },
        Directory: {
            url: (siteUrl: string, listName: string) => `${siteUrl}/_api/web/lists/getbytitle('${listName}')/items?$select=Title,Id`,
            process(result: any) {
                const json: { value: IDirectoryListItem[] } = result;
                return json.value.map((v,i)=>({
                    id: v.Id,
                    title: v.Title,
                    created: v.Created,
                    createdby: v.Author.Title,
                    modified: v.Modified,
                    modifiedby: v.Editor.Title,
                    firstName: v.firstName,
                    lastName: v.lastName,
                    mobileNumber: v.mobileNumber,
                    internalNumber: v.internalNumber
                }));
            }
        }
    }
    public async getItems<K extends keyof ListItemFactory['handlers']>(requester: SPHttpClient, siteUrl: string, listName: K): Promise<ReturnType<ListItemFactory['handlers'][K]['process']>> {
        var h = this.handlers[listName];
        const response = await requester.get(
            h.url(siteUrl, listName),
            SPHttpClient.configurations.v1,
            {
                headers: {
                    "Accept": "application/json;odata=nometadata",
                    "odata-version": ""
                }
        });
        var json = response.json();
        return h.process(json) as any;
    }
}

let fact = new ListItemFactory();
let requester!: SPHttpClient;
let siteUrl = ''
var genList = fact.getItems(requester, siteUrl, 'GenericList'); // Promise<Promise<{id: any;title: any;created: any;createdby: any;modified: any;modifiedby: any;}[]>>
var dirList = fact.getItems(requester, siteUrl, 'Directory'); // Promise<{id: any;title: any;created: any;createdby: any;modified: any;modifiedby: any;firstName: any;lastName: any;mobileNumber: any;internalNumber: any;}[]> 

Edit

A bit of explanation on the signature of getItems. The method is now generic, with a type parameter K that MUST be a key of the handlers object. This restriction is done using keyof T which gives a type representing the public keys of T and ListItemFactory['handlers'] which is a type query and gives us the type of the handlers field, so putting it all we get K extends keyof ListItemFactory['handlers'].

Now the parameter listName can only have a value of 'GenericList', 'News', 'Announcements' or 'Directory', and when we call the metod, depending on value passed in K will be the string literal type representing one of these values.

We now want to tie the return value of getItems to the return type of the process function of the listName passed in. We have the listName as a string literal type in K so to get the type of the handler, we can use a type query: ListItemFactory['handlers'][K] drilling down to get the type of the process function we get ListItemFactory['handlers'][K]['process'].

To get the return type of this function type we use the built-in conditional type ReturnType<T>. ReturnType<()=> number> will be number, the type does what it's name implies if we pass in a function type we get it's return value

Putting it all together we get a return type of Promise<ReturnType<ListItemFactory['handlers'][K]['process']>>

Edit

For 2.4.2 you can't use conditional types (they are only available since 2.8). You can just use any as the return type as in your original code (not ideal but this is what we can do in 2.4.2, ideally you should try to update to a newer version):

public async getItems<K extends keyof ListItemFactory['handlers']>(requester: SPHttpClient, siteUrl: string, listName: K): Promise<any>

Upvotes: 1

Related Questions