On our Angular application, we get data from a database on many actions (shocking- I know). To normalize the process (making sure the right authentication is passed with each request, that the data returned is in the proper format, etc), we created a service- “backendService”. This wraps around Angular’s http module, so it returns an Observable to the component that calls to it.
Generally, that observable provides an array of objects- let’s say “companies” in this case. The company will have a name, location, number of users, etc. When our backendService fetches the company list, there are a few tasks we need to do with the data.
- There is a d3-driven chart for the top 5 companies by user count (the 5 companies with the most users).
- There is a drop down to jump to a list of that company’s users.
For task 1, we manipulate the data into the top 5 by user count and sort accordingly (most users to least within those 5). For task 2, we just sort alphabetically by company name.
Experienced JS developers are probably already seeing where I’m going with this. And I should have seen it too- but theoretically knowing that an original array is modified when a shallow copy of that array is modified is a bit different from managing that process in practice.
In the subscription to the data, we had simply assigned the response to the class ‘companies’ variable (an array initialized with no items) so it could be passed around to other methods and even other components/services. Something like this (general idea only):
this.companies = ; //class variable this.backendService.getData(urlBaseHere).subscribe(response => this.companies = response);
Works fine- as long as you don’t need that ‘response’ array for anything else. When we added the d3 graphs, we (I) forgot to go back and check the source. We just added a service to filter and sort this.companies to get the top 5 by number of users. It wasn’t even in the same JS file. It’s generally best practice to break out your code into small, reusable components, but there are some drawbacks, particularly when working on a team. If you’re sharing a reference to an array, one file may manipulate it one way while another is trying to do something different.
So, when we added the chart code (order by number of users), I found that I couldn’t sort the drop down alphabetically anymore). Initially, I blamed the back end. I started on a PHP/MySQL stack, so I know a bit of SQL, but this project uses SQLserver- the Microsoft flavor, and I’m less experienced with that specific brand. I thought maybe the default sort I put on it (ORDER BY company_name) was flawed. I like working on the backend, but with a language like C# (that is, a compiled language), it’s a bit slower development process. Make a change, kill the dev server, re-compile, and restart the server. I know- it’s not a big deal, but again, the cool frontend frameworks have spoiled me with their ‘hot reloading’ and ‘automate all the things’ awesomeness.
Anyway, after wasting the better part of an hour researching MsSql syntax and discovering it was not the culprit, I went back and double checked the component getting the actual data. When I logged the ‘response’ parameter directly, I noticed that it was not giving me the array sorted by company_name- it was giving me the array sorted by number of users, not alphabetically by company_name. I’d manipulated this.companies and therefore manipulated the base array returned.
The answer was easy- though there are a few options we could have gone with. We just initialized two arrays: this.companies and this.companiesForChart. Then, we assigned both to the response before doing any manipulation.
this.companies = response.map(company => company); this.companiesForChart = response.map(company => company);
Using .map ensures that each is a deep copy of the array- not just a copy of the reference, and also reminds anyone else using the code to use the same process (and that they can do any manipulations there as well). Each one gets a reference to the original data, but this.companiesForChart is free to do it’s own thing and not mess with the default sorting (alpha). It’s passed off to our chart service file where it’s .filter and .sorted as much as we need. Another option would be to use the slice method:
this.companies = response.slice(0);
Although we didn’t test this- I think it might also cause issues when we manipulate this.companies, so the need to have the second array (this.companiesForChart) would remain – also assigned to response.slice(0) in this case. Either way, it made a bit more sense to me to have two copies of the array, as there were two tasks and two different files using those arrays. It might be a little slower to initialize 2 arrays (not sure on that), but for other people on the team looking at the code, this makes it a bit more explicit exactly what each one is for.
So- the moral here is: don’t forget the basics. When I logged the ‘response’ to the console (right after it was retrieved from the backend) and saw the array ordered by number of users, I assumed the problem was with the backend sorting. How could anything in my front end code manipulate the array that just came back? But the nature of JS arrays was the real issue- actually, me forgetting about reference types vs value types was the real issue.