Angular 1 (ng1) vs Angular 2 (ng2) Two-Way Data-Binding for (nested) Components in 2017

Update: An article focusing more strongly on Angular 2 can be found here https://www.bersling.com/2017/05/24/communication-between-components-in-angular-2/. It also elaborates the observables concept in more detail.


Two-Way Data-Binding has long been one of the unique selling points of Angular 1 (ng1 in the following), until people started to complain that it slows down the apps too much. This was one of the core reasons, why the Angular Team opted to create Angular 2 (ng2 in the following): to make Angular faster by “removing” two-way data binding. But did they really remove it? And what’s the suggested way of data binding then? That’s the topic which we’ll tackle here.

Let’s start with a common misconception:

In ng2, the parent holds the data and the child can’t modify it.

That’s not entirely correct. Correct would be:

In ng2, property bindings (the ones with square braces [] ) are PASSED BY REFERENCE. Therefore the data / view is updated app-wide, when a field of a bound object is changed.

Let me prove this to you in the following ng2 app:

This was achieved by simply using

[(ngModel)]="item.name"

on the innermost element. So wait, didn’t I read like 1 Million times that when I pass down an object with [] that the child can’t modify it? Well yes, but that’s a lie. The child can modify it because it’s passed by reference, unless it’s a string or a number or another primitive that’s passed by value. Let’s capture the difference:

ng2 and ng1 data binding are only different in case of the entire bound object being replaced. This is always true for strings, numbers, booleans and other primitives.

In the example above, this would mean if I clear or reassign (e.g. item = {} ) on any nested child, I break the link of the updates in ng2 but NOT in ng1. ng1 will look through all components, searching for item in order to update it.

Okay, so this begs some questions.

  • Is modifying the data in a child-component encouraged?
  • What kinds of problems could arise leveraging the reference-binding?
  • What should I do when I need the parent to do something on the change of the data?
  • What should I do if I don’t have an object, but instead primitives, or I want to swap the entire object?

A lot of questions. Let’s first do a quick recap of the “old way” of doing two way data binding in ng1, and then looking at the new, recommended way(s).

Angular 1 two-way data-binding in components

Here’s a quick reminder how two-way binding in ng1 worked. Let’s say we have a wrapper component, holding two child components: one to edit the data and one to display the data of the wrapper.

<my-prop-wrapper my-prop="myProp">
  
  <edit-my-prop my-prop="myProp"></edit-my-prop>
  <display-my-prop my-prop="myProp"></display-my-prop>
  
</my-prop-wrapper>

The display got immediately updated when you bound my-prop to the directive like so:

scope: {
  myProp: '='
}

But this won’t work in ng2, since communication only happens from parent to child in property binding. The edit-my-prop directive doesn’t propagate changes upwards to the parent and then from the parent back to the display anymore. You’ll have to do that yourself. So how can you do it?

“Official” ng2 way to update data across components

In order to get the information from the edit to the display, you’ll  somehow need to get the wrapper to listen to events in the edit. In ng2 a [] bracket signifies a flow of information from parent to child and a () bracket signifies a flow from child to parent. So your template would need to look like so:

<my-object-wrapper my-object="myObject">
  
  <edit-my-object [myObject]="myObject" (myObjectChanged)="doOnChange($event)">
  </edit-my-object>
  <display-my-object [myObject]="myObject"></display-my-object>
  
</my-object-wrapper>

The display only needs to receive information (i.e. []), but the edit needs to receive and emit information. Now inside of the edit-compontent.ts (or whatever name it may have), the emission of information must be triggered. How? With ng2’s EventEmitter and Output classes. Import those into your file, then:

@Output() myObjectChanged = new EventEmitter();

You can then trigger an event by myObjectChanged.emit({value:this.myObject}).

This will trigger the doOnChange Function in the parent, which could for example do the following:

doOnChange(evt){
  this.myObject = evt;
}

HOWEVER, notice that it doesn’t have to be done like this. You could just as well utilise the Observable Pattern with RxJS. That’s actually what I’d recommend you to do, we’ll discuss it in a moment.

How you could do it in ng1, like you would do it in ng2

In ng1 we can bind properties with =, with @ and with & . I personally never really used & since there’s already too many concepts, but with this you can simulate the ng2 behaviour.

For example you can have in your parent template

bla bla <inner do-on-event=myFn($event, someData)></inner>

and then bind it to the child (called inner here) like so (in typescript):

<button ng-click="omgImClicked($event, 'cool stuff')">Click me</button>
bindings: {
  doOnEvent: "&"
}
...
public omgImClicked(e, data) {
  doOnEvent({
    $event: e,
    someData: data
  });
}

In case you were like “wtf” here a lot, maybe read this article: http://www.codelord.net/2016/05/13/understanding-angulars-and-binding/

However, note that this can become cumbersome quickly, as components become nested!

Deeply Nested Components

As you write a complex app and reuse more and more components, the components get smaller and and more nested as part of a process. So it’s important to understand how nested components will work.

Function Binding in ng1 (not recommended)

In ng1, if you want to go with the function binding approach, you’ll have to do something like the following. Let’s say we have a somewhat complex update function in our “data owner”:

public updateTask(updateInformation: {
  task: Task,
  field?: TaskProperty,
  listindex?: number
}) {

  let errorFn = (errorResp) => {
    this.Notify.errorResponse(errorResp);
  };

  let successFn = (resp) => {
    this.task = angular.copy(resp.data.task);
  };

  if (!updateInformation.field) {
    this.TaskService.update(updateInformation.task).then(successFn, errorFn);
  } else if (!updateInformation.listindex) {
    this.TaskService.updateProperty(updateInformation.task[updateInformation.field], updateInformation.task, updateInformation.field).then(successFn, errorFn);
  } else {
    this.TaskService.updateListItem(updateInformation.listindex, updateInformation.task[updateInformation.field][updateInformation.listindex], updateInformation.task, updateInformation.field).then(successFn, errorFn);
  }

}

I’ve already aligned this for proper usage for passing down with &, since if you would use multiple arguments instead of a json, you’re gonna have a bad time. Well actually, if you use this method you’re gonna have a bad time anyways.

youre-going-to-have-a-bad-time

This is because you’ll have to define a new function in every nested component, if you use multiple arguments. You’ll see what I mean if you try it out, but I’ll spare you this here.

Next, in the template we can call it like this:

<child update-task="$ctrl.updateTask({task: task, field: field, listindex: listindex})">
</child>

and then in the grandchild we could also use

<grandchild update-task="$ctrl.updateTask({task: task, field: field, listindex: listindex})">
</grandchild>

(exactly the same!). Let’s say the event happens in the grandchild, so we could use something like:

public updateTitle() {
  this.updateTask({
    task: this.task,
    field: 'title'
  });
}

Well, that’s it.

Anyways, I have a few problems with this passing down of an “updating function”, which now seems to be the recommended way:

  • It’s a second “update-object/function”, alongside the main object task that needs passing down. What used to be magic and nice with two-way binding, assuring my “task” was the same everywhere now requires a second object/function being passed around.
  • It’s a lot of duplicated entries (“field: field” etc), but that’s necessary for multiple arguments.
  • Those entries are in the html, how ugly is that?
  • When someone else reads my code, how is this person supposed to know what updateTask does?!?! Before it was kind of clear because I just used a service and the databinding took care of itself, but now I have to go 10 directives/components up to find the “owner of the data” and check what this owner intends to do with the data.

Using $emit (not recommended)

With emit, you’d let the child (or grandchild or whatever) “bubble up” and event. For example the innermost child can tell it’s parent “Hey I had an event, I made poopoo”. Then the parent can tell the grandparent and so forth, until the one responsible for cleaning up the poopoo will take care of the mess. This is also not really the best choice, since it’s semi-global events flying around. You’ll emit a string like OBJECT_CHANGED which then can be viewed by all parents up to the rootScope. When you accidentally give the same name to multiple events you’re going to have a bad time.

Using $broadcast (not recommended)

Instead of bubbling up with $emit, you could also broadcast an event from the rootScope. That’s also bad, since all the components with a listener $scope.on('BROADCASTED_EVENT') will react to this, so it’s global and, well, you’re going to have a bad time…

Using Observables with RxJS (RECOMMENDED)

The observable pattern is mighty, flexible and the way forward in many systems. Meteor, ng2 and ReactJS all adopt the concept, just to name a few. But observables aren’t just something you can have only in some frameworks, it’s basically just a programming pattern. And it’s quite simple, you just subscribe to events that are published. There is the framework agnostic library RxJS (Reactive Extensions for Javascript) that’s maintained by Microsoft and used by almost everyone dealing with observables in the Javascript World.

There’s plenty of pages explaining the nitty gritty of it, so here’s just the broader overview:

    1. You hold the data in a service. To do so, a kind of unique id/key is required to store the data in a map (key value pairs). Database entries are especially well suited for this, since they usually come with an id anyways.
    2. The service has a changeMyData method.
    3. The parent is subscribed to an observable in that service.
    4. When the service changes it’s data, the parent is notified, adapts its own data, and the changes are propagated to all children.

Here’s a quick example how you would go about data binding with RxJS.

In this example, a central item-service holds the data. It is always immediately clear who the “owner of the data” is (the service). It is also always clear where to apply the update, it’s also the service. What’s even nicer, the binding syntax becomes completely unnecessary. Notice how both <detail> and <remove-item> components don’t take any input properties, yet the object updates:

 <b>Angular 2 Component Using Observables!</b>
 
 <div style="border: 1px solid blue; padding: 5px; margin-bottom: 2px">
 <detail></detail>
 </div>
 
 <div style="border: 1px solid red; padding: 5px">
 <remove-item></remove-item>
 </div>

This is because <detail> listens to changes in the items stored in the item-service. That’s the simplest, cleanest and fastest approach if you ask me.

Of course this is only useful if we’re actually talking about data. If you have simple UI components that are like <my-circle color="red" size="15"></my-circle> it’s still simpler to just use property binding. But in the case of actual “data-objects”, like my-item it’s better use the approach with the service. As a rule of thumb, you could use:

If your object has an id, use the Observable + Service pattern.

Conclusion

As always in the fronend world, it’s a bit of a mess. Many concepts, many possibilities to achieve the same results, what’s the best choice? Some people would say “it depends”, but sometimes we say “it depends” too much because devs can only get really familiar with a certain number of concepts and some things are just plain stupid. The Front-End world is slowly consolidating towards observables for handling events.  And so should you.

WINNER: Observables with RxJS

6407041

Leave a Reply

Your email address will not be published.