Tallan Blog

Tallan’s Experts Share Their Knowledge on Technology, Trends and Solutions to Business Challenges

Demystifying a Common “Bug” Encountered by Developers new to Angular

I often work with clients that are transitioning their legacy MVC or WebForms web applications over to using Angular and Web API instead. After the initial ramp-up period with the framework, I’ve often had developers on these teams tell me how much easier it is to debug logic in Angular versus “how it used to be.” Writing logic with Angular and TypeScript provides these developers with a level of code organization on the front-end that they’ve previously only seen in their back-end .NET code. As a result, I think the framework especially appeals to developers who have previously shied away from front-end development.

With that said, there is an “Angular bug” I’m often asked to help remedy that I’d like to share in this article. The “bug” often comes up when new Angular developers begin sharing data between components. As you may have guessed by my use of quotes, the issue isn’t a bug so much as its misunderstanding of how JavaScript passes along data—more on that later.
To replicate the kind of situation developers run into when encountering this issue, I’ll be demonstrating component interaction via input binding. Input binding is one of the simplest ways to share data between components in Angular. If you need a refresher, visit the official documentation on component interaction from Angular.

Consider the diagram below:

Parent Child 1

I’ve set up a simple application in Angular consisting of two components that have a parent-child relationship. In this example, specifically an obje each component has a counter that is displayed in its respective template. I’d like to connect these two counters with input binding so that they always display the same value. If I perform an addition or subtraction operation in the parent, it should be reflected in both components. If I perform a reset operation in the child and set the child counter to 0, the parent counter should also be reset. This behavior is referred to as two-way binding and is frequently employed between components. To begin, look at how I implemented the code for the ChildComponent class:

ChildComponent 1

On line 4, we see that the selector for this component is ‘app-child’—we’ll use this when creating an instance of this component in the ParentComponent’s template. Line 9 is where we initialize and expose the public class variable childCounter for input binding via the @Input() attribute. Finally, line 15 defines a simple function to reset the childCounter to zero. You can see this used in the ChildComponent’s template below:

ChildComponent 2

Next look at the code for the ParentComponent class below:

ParentComponent 1

In the ParentComponent class, we see that parentCounter is just a public class variable that we perform various actions on. The functions add() and subtract() are public and attached to buttons on the ParentComponent’s template which is shown below. Note that on line 13 the ChildComponent is referenced via its selector and its exposed childCounter variable is bound to the parentCounter.

parentCounter 1

Let’s see what this looks like in action:

Parent Child 2

Clicking on the Add and Subtract buttons works as expected, when the parentCounter changes the childCounter does as well. So far so good but, what happens when “Reset Counter” is clicked?

Parent Child 3

“Reset Counter” only resets the value of the childCounter and not the parentCounter. This example has therefore not successfully implemented two-way binding—what’s going on here? The answer lies in the fact that although we’re using Angular, the rules of JavaScript still govern our logic. Like any programming language, it’s important to be aware of when you’re “passing by value” vs. “passing by reference.” When JavaScript primitive datatypes are passed as arguments, they are “pass by value.”

What this means for the example above is that parentCounter and childCounter are maintaining their values in two separate locations in memory. The code is simply copying parentCounter’s value to childCounter’s address in memory via Angular’s input binding. Note that updates made specifically to the childCounter are not affecting the parentCounter because input binding only works from parent to child component.

If JavaScript treated primitive arguments as “pass by reference”, input binding would have passed parentCounter’s memory address, and therefore childCounter would point to the same location. This would achieve the two-way binding behavior we wanted to achieve—so what’s the work around? See my refactor of the previous example below, starting with ChildComponent:

ChildComponent 3

Instead of using input binding with a primitive I’ll use a non-primitive this time; specifically an object. By doing this, I can take advantage of “pass by reference,” so the parent and child components are manipulating data in the same place in memory. Line 10 is where I define this object as an instance of InputModelA. InputModelA has a single parameter representing the counter; its class definition is shown below.

InputModelA 1

The last bit of refactoring involved changing my references to childCounter to reflect the new childInputModel. These changes are reflected on line 17 of the ChildComponent class and line 5 of the ChildComponent template below.

ChildComponent 4

Lastly, I refactored the ParentComponent in a similar way so that it also used InputModelA. Look at the ParentComponent class and template below.

ParentComponent 2

Take note that on line 12 of the ParentComponent template I am instead using input binding on the inputModel and NOT its counter property.

ParentComponent 3

With all these changes in place, let’s perform the same actions as last time. As shown below, adding and subtracting work as they did before.

Parent Child 4

Resetting the counter from the child component now also works; thus, we’ve achieved the two-way binding we were looking to implement!

Parent Child 5

Now you may be thinking, “Ok so if I want two-way binding on a primitive data type, I’ll wrap it in a model. An array is a non-primitive; therefore, I can get by without wrapping it in a model.” This logic is sound; however, there is a situation that will break your array’s two-way binding. Consider this second example which works just like the first except it passes in an array instead of a primitive:

two-way binding 1

two-way binding 2

two-way binding 3

two-way binding 4

Running this example, we see that both arrays display the same items as they are removed from the ParentComponent. We can even remove all the items in the array then add more back, and the two-way binding between the components remains.

Parent Child 6

The problem occurs when we click “Reset Array” on the child component. The two-way binding breaks when the child component assigns its childArray to a new array—how do we explain that? JavaScript abstracts away a lot of low-level programming concepts like memory management, but that doesn’t mean they don’t still rule it. When the code first initialized input binding ensured parentArray and childArray were pointing to the same memory address because of “pass by reference.” By assigning childArray to a new array, it began pointing to a different memory address thus, breaking the binding. Note this issue isn’t unique to arrays; the problem can occur to any non-primitive. If we had used an object instead, assigning a new instance of that object in the ChildComponent would break the two-way binding. So, what’s the workaround?

Going back to our array example; one solution is making sure never to assign childArray to a new array in ChildComponent. I find that hard to enforce, especially with multiple developers in the source code—this solution seems unintuitive. An alternative is anchoring down your reference to your non-primitive by wrapping it in an object (a non-primitive) as we did with the counterexample. See InputModelB defined below:

InputModelB

By wrapping your non-primitive in an object, you’ll be passing a reference that contains another reference. By passing an instance of InputModelB from ParentComponent to ChildComponent via input binding, you’re safe to reassign the array parameter. That’s because both components will still share a reference to an object that will point to that new array’s address in memory.

Trying to resolve two-way binding issues can become a nightmare if you’re unaware of how JavaScript passes around different types of data. However, if you’re aware of when JavaScript will “pass by reference” or “pass by value,” you can more consciously control behavior between components. My recommendation is if two-way binding doesn’t matter, and you want to pass a value from parent to child pass the argument directly. If two-way binding does matter to you, I recommend you wrap your intended argument in an object and pass that instead. I hope is that this advice saves you or someone on your team from dealing with the situations demonstrated in this article.


Learn more about Tallan or see us in person at one of our many Events!

Share this post:

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

\\\