Javascript has been around for quite some time, first coming out almost 30 years ago. Because of its rich history, it’s gathered quite a bit of functionality over the years.
More recently, in about 2012, TypeScript attempted to give types to JavaScript. Fortunately, because developers are a relatively unopinionated bunch (and easily agree on everything) there hasn’t ever been much furor on the JavaScript vs. TypeScript debate. Not.
Whether you’re part of the typed club or not, one function within TypeScript that can make life a lot easier is object destructuring. TypeScript object destructuring is a bit of a weird one; sometimes you can listen to what something is and assume a certain functionality.
For example, you know what a promise is, or an observable. But destructuring? It doesn’t lend itself to a credible idea of what it actually is.
I went literally years without ever using object destructuring, so if you’re not immediately aware of what it is or why you’d want to use it, that’s okay.
However, now that I know about it, it makes my life dramatically easier when dealing with operators that take an input and produce other variables. For example, an observable may emit many values that go through a pipe
, and with each operator, it could get harder to work out exactly what variable is where in the subsequent responses.
That specific use case we’ll get into a little bit later. For now, let’s start slow and use simple examples to understand how they work.
Consider this humble array:
let simpleArray = [1,2,3,4,5];
If we wanted to get the first two values of this array, typically we could use slice
or some other operator. But with destructuring, we can do this instead:
const [a,b] = simpleArray
It feels weird assigning bits of an array to our const
, but we’re actually just popping out the first two values of that array and assigning it to a
and b
. If we wanted to access the remaining values, we could even do something like this:
const [a, b, ...remaining] = simpleArray
That’s a nice way of seeing how destructuring works, but it also seems a bit pointless. If you saw that in a vacuum, you’d likely think it was like slice
but with more steps. You’d be forgiven for thinking that.
As developers, a lot of our complexity can come from things happening at some point in the future. Of course, there is the humble Promise
, or asynchronous object that returns at some point in the future. But before long, we’ve graduated to Observables
and a temporal element is introduced. These things emit over time, baby.
Would we encounter these every day as a developer? Well, if you have a form on your website where people can enter data, and they can view results, what are our sources of events? There’s the user clicking on the search button; that’s true. But what about sorting and filtering the data? It would be nice to do this all out of the one Observable
and not stash our half-sorted array into a temporary variable while we’re working it out.
That’s such a good application for destructuring. Let’s see why.
Let’s start out by subscribing to a sortOrder
variable. This is the name of the header that the data would be sorted by. In our handy-dandy editor, it tells us the type of data that will come from this Observable
, as suggested by the type system:
Unsurprisingly, it’s an Observable<string>
. It’s going to emit over time when someone clicks on the Sort Order button. But, we want to combine this observable with other observables. What happens when we do that?:
Ah that’s not very…. descriptive. Now it’s an Observable
of type string, string
. TypeScript pulls this variable out of thin air as it describes our data shape.
Annoyingly, we’d have to access the values in this by using an index, so we’d have to remember when we assigned each variable in the pipe
operator. For reasonably complex pipe()
chains, before long we’d look like this:
Where this gets really exciting is that, with each subsequent call to combineLatestWith
, we receive our original tuple, with the newest combineLatestWith
value tacked on the end.
Rapidly this descends into chaos as our type information gets truncated due to the sheer length:
Hovering over the object to see the type shows us what we’re dealing with:
This does not spark joy.
If we had some sort of even more complex system, and we had to listen to many Observables
to produce a result, then it wouldn’t be so hard to imagine a deeply, deeply nested set of tuples and objects. And we’d have to pluck them out by index.
So basically, just surround the whole code block with “NEVER REFACTOR THIS YOU WILL BE FIRED” and move on with your life.
Fortunately, destructuring can really help with this proposition.
Let’s dummy up a simple HTML table with a few fields, and sorting:
We use forms that are at least this complex in the day-to-day, but there’s quite a bit going on here. We want to react instantly when someone presses the Search button, and the Sort button… but we also want to take the latest values from other fields, like the name or profession input boxes.
Fortunately, we can use RxJS operators to achieve just this:
this.tableData$ = this.sortOrder.pipe( combineLatestWith(this.header), combineLatestWith(this.searchButton$), withLatestFrom(this.formGroup.valueChanges) )
But after, we want to use a switch map to terminate any in-flight requests if new requests come through. What’s the signature of the object that is passed into the switchMap
operator though?:
For every new RxJS operator we use, it gets wrapped in a tuple. So, imagine that we have a data source (like an HTTP API for example) that has a signature like this:
fakeAsyncronousDataSource(name: string, profession: string, header: Header | undefined, sortOrder: SortOrder, page: number)
Our entire chain winds up looking like this:
this.tableData$ = this.sortOrder.pipe( combineLatestWith(this.header), combineLatestWith(this.searchButton$), withLatestFrom(this.formGroup.valueChanges), switchMap(x => { return this.fakeAsyncronousDataSource(x\[1].name ?? '', x[1].profession ?? '', x[0\][0]\[1], x[0\][0][0], 0) }), startWith(testData.slice(0, 10)) ) >
Imagine trying to present that straight-faced at a code review.
“Oh and this is the bit where I pull values out of a tuple layered three layers deep and pass it to a function. I do it entirely using index values that I and only I know when I wrote the code”.
And then the laughter gives way to long stares as they realize — you’re actually being serious. No no, let’s not be that person.
Fortunately, we can “unwrap” the layered tuple by using TypeScript object destructuring. It’s kind of a quick transition, but our RxJS chain now looks like this:
this.tableData$ = this.sortOrder.pipe( combineLatestWith(this.header), combineLatestWith(this.searchButton$), withLatestFrom(this.formGroup.valueChanges), switchMap(([[[sort, header], _], formData]) => { return this.fakeAsyncronousDataSource(formData.name ?? '', formData.profession ?? '', header, sort, 0) }), startWith(testData.slice(0, 10)) )
This has many benefits.
First, and most obviously, we know what the variables are when they are used in the function call. We don’t have to pluck values out by index, so readability improves substantially.
Secondly, we can mentally associate each tuple value in the destructuring to the respective pipe operators before it. So, if we want to add things to the pipe, it’s not a huge inconvenience.
To be fair, normally it takes quite a long tour-de-force through a language feature to really tease out its benefits. But, in this case, it’s fairly obvious. When you have a complex type that is being produced by something like an observable chain, don’t be afraid to reach for something like destructuring.
In this article, we learned how to destructure objects in a wide variety of cases. We saw how they could be used in arrays, but also how they could be used in an Observable
chain. However we use them, they help us to write clean, maintainable code.
Feel free to clone the sample on GitHub here. After you have done so, and have started the project, you can also access the simple example on http://localhost:4200/simple, and the complex example on http://localhost:4200/complex, respectively.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowuseState
useState
can effectively replace ref
in many scenarios and prevent Nuxt hydration mismatches that can lead to unexpected behavior and errors.
Explore the evolution of list components in React Native, from `ScrollView`, `FlatList`, `SectionList`, to the recent `FlashList`.
Explore the benefits of building your own AI agent from scratch using Langbase, BaseUI, and Open AI, in a demo Next.js project.
Demand for faster UI development is skyrocketing. Explore how to use Shadcn and Framer AI to quickly create UI components.