Go an object in your Angular app that you need to duplicate?

Well I'd like to give you my usual "you've come to the right place" line but in this situation there are some caveats.

So I'd better explain those before I go any further.

Shallow vs. Deep

When it comes to cloning objects in TypeScript, there's a difference between a shallow copy and a deep copy.

A shallow copy looks like the original, but if the original includes composites (complex objects, arrays, etc.) then those objects get included in the copy by reference.

That means if you make changes to the original object, those changes get reflected in the cloned object.

But again: that's only for composites. It's not for basic "primitive" types like strings and numbers.

That might be good enough for you.

If not, then you need to make a deep copy. And I think the best way to do that is to write the mapping code manually. 

There's not too many "one size fits all" solutions for deep copies because they're dependent on the underlying structure of the original object.

In this guide, I'll cover shallow copies. So if you need a deep copy you won't find much help here.

Another Caveat

Another thing you need to know: some of these cloning methods won't copy methods. They'll just copy properties.

Again, that might be all you need. It's possible you're looking to just duplicate an object that only contains properties.

But if you also want to copy methods, make sure you pick the right cloning method (the last one in the list below).

A Sample Class

Before I get into the cloning methods, let's take a look at a sample class that I'll use for demonstration purposes.

Here's a simplified class that includes details about an appointment within a CRM application:

class Appointment {

  constructor(public dateTime: string, public contactName: string) { }

  recall(): string {
    let txt: string = `This appointment occurred at ${this.dateTime}`;
    return txt;
  }
}

That's fairly straightforward.

It includes a constructor that accepts two public parameters: the date and time of the meeting (as a string) and the name of the contact in the meeting.

The recall() method simply return a sentence about when the appointment occurred.

Now let's say you instantiate the class above as follows:

let app: Appointment = new Appointment('01/01/2021 08:00AM PST', 'John Smith');

And then print out some stuff to the console like this:

console.log(app);
console.log(app.recall());

That's going to give you the following lines in the console:

Appointment {dateTime: "01/01/2021 08:00AM PST", contactName: "John Smith"}
contactName: "John Smith"
dateTime: "01/01/2021 08:00AM PST"
__proto__: Object

And

This appointment occurred at 01/01/2021 08:00AM PST

So now you know what to look for in your clones.

Spread It Out

The first, and easiest, method for cloning objects is to use an object spread. It looks like this:

let appClone = { ...app };

That ellipsis you see in the braces is the notation for an object spread. It's designed to make a copy of the object that follows the ellipsis.

And that's exactly what it does here. Almost.

Try running this code:

console.log(appClone);
console.log(appClone.recall());

I said "try" running that code because you can't run that code. The second line won't compile.

Remember I mentioned above that some cloning options don't copy methods? This is one of those options.

The first line works just fine, though. So comment out the second line and give it a run.

But this is a shallow copy. See above.

Your Assignment, Should You Choose to Accept It

Another option for cloning: Object.assign(). The code looks like this:

let appClone: Appointment = Object.assign({}, app);

That will also give you a shallow copy.

That first parameter, by the way, is the target object. It's empty here because that's probably how you want to start out when cloning objects.

Now try this:

console.log(appClone);
console.log(appClone.recall());

This time, that code will compile. But it won't run.

You'll see an error in the console.

So, once again, the methods don't get copied.

Stringify Then Parse

Another option:

let appClone: Appointment = JSON.parse(JSON.stringify(app));

That code takes the original object and stringifies it into JSON format. Then, it uses JSON.parse() to parse the stringified object back into a clone.

Good news: that's a deep copy. But I'm reluctant to endorse at as a solution for deep copies because some objects might not stringify well.

So I stand by my original claim that you should consider writing custom mapping logic if you're into deep copies.

But, once again, that clone won't include the methods from the original.

The One That Copies Methods

Okay. Here's the one that copies methods:

let appClone: Appointment = Object.create(app);
console.log(appClone);
console.log(appClone.recall());

The magic happens with the Object.create() method.

BUT... that's not really a cloning operation. Instead, it's just creating a new object from a prototype.

BUT... is that really that bad? Probably not.

You get what you wanted: a new object that looks like the old object.

And this time you get the methods as well. Hooray!

Still a shallow copy, though. So be careful.

Wrapping It Up

Now you're smarter.

You know a few ways to copy objects within your Angular source code.

Pick the one that works best for you and use it.

Have fun!

Photo by cottonbro from Pexels