2019-06-25
|~3 min read
|555 words
Instead of documenting an interface that is redundant, but only a partial, we can use Pick
to allow the interface to automatically be linked, w without being overly broad.
Situation: I want to be able to propose changes to a table — but only to two fields - is_enabled
and display_order
.
Imagine I want to create a method that will make changes to a table - but instead of opening up the entire record for modification, I want to limit which attributes to just two (is_enabled
and display_order
).
However, the original table has more fields than that. Here’s an interface for demonstration purposes:
interface IMyTable {
id: string
foreign_id: string
name: string
is_enabled: boolean
is_custom: boolean
display_order?: number
name_en: string
name_sp?: string
name_fr?: string
description_en?: string
description_sp?: string
description_fr?: string
}
One method that would work would be to use a partial of the IMyTable
.
async changeMyTable(proposal: Partial<IMyTable[]>): Promise<...> { /* ... */ }
This has the benefit of keeping the management of interfaces down, but introduces several new issues.
Namely - I could pass any of the fields on the table and my method would either need to handle them or alert the user if not. Also, partials don’t give me the opportunity to require any fields. So, imagine id
is necessary to find the correct record, but it’s not passed — the changes should fail.
Going this route sets me up to have to handle a number of additional fail states or fail silently — creating a lot more work for me or a poor user experience. Not great options.
A better approach would be to create a separate interface to communicate clearly to the user what you’re expecting and, therefore, what’s handled. That interface could look like this:
interface IMyTableProposal {
id: string;
is_enabled?: boolean;
display_order?: number;
}
...
async changeMyTable(proposal: IMyTableProposal): Promise<...> { /* ... */ }
This addresses several of the issues I introduced with the Partial
, however, it creates new ones as well - specifically: I now have a second interface I need to maintain.
Pick
By converting to a type
, however, I can streamline the process using Pick
.
export type MyTableProposal = Pick<myTable, "id" | "is_enabled" | "display_order">;
...
async changeMyTable(proposal: MyTableProposal): Promise<...> { /* ... */ }
Pick
WorksThe API requires two features:
Pick
A few of the perks that come with Pick
include:
IMyTable
changes later, it will flow through automatically to the type
type
anyway).Using the Pick
instead of a Partial
or separate interface in this case increases the specificity of my control with respect to which elements I allow to be updated without having to keep a second interface in-sync (thereby adhering to DRY).
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!