Isolate State Mutations in Angular Components

Lukas Ruebbelke
InstructorLukas Ruebbelke
Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 5 years ago

Managing state is one of the hardest things to do in any application. Angular 2 tackles this problem by making it easy to implement a reactive, uni-directional data flow that favor immutable operations. We are moving in the right direction in Angular 1 by moving our state and logic to models but invariably this raises a question. If we are moving to an immutable world, how do you manage mutable operations like forms in Angular? In this lesson, we are going to learn a surprisingly simple technique to isolate state mutations within a component using component lifecycle hooks.

[00:00] Managing state is one of the hardest things to do in any application. Angular 2 tackles this problem head-on by making it easy to implement a reactive unidirectional data flow that favors immutable operations.

[00:12] We're definitely moving in the right direction in Angular 1 by moving our state and logic to models. Invariably, the question comes up, if we're moving to an immutable world, how do we manage mutable operations like forms in Angular?

[00:24] Mutable state in itself is not a bad thing as long as you're explicit about the boundaries around it. It is shared mutable state that is the devil's playground. In this lesson, we're going to see a surprisingly simple technique to isolate state mutations within a component.

[00:39] To get started, I've updated the bookmarks model to simulate CRUD functionality. We've imported a few methods from LoDash, and then I've added create(), update(), and delete() methods to simulate the back end. Definitely not something I would put into production, but for the case of illustrations, this will work.

[00:59] I've also started to build out the saveBookmark component. The first thing to look at here is the saveBookmark component itself, paying attention to the bindings that I've defined.

[01:13] We have a bookmarkInput and a save and cancel output. We're passing in a bookmark that we're going to edit. Then, when we're ready to save, we'll basically emit that event, or if we cancel it, we'll just cancel that operation.

[01:28] In the template, you can see here that the form is binding to the bookmark object on our saveBookmark controller via the title and the URL. Then, when a user creates cancel, then we emit the cancel event. When they click submit or hit enter, then we're emitting the save event, and we're just sending the bookmark back up to the bookmark controller for processing.

[01:57] Then, within the bookmark controller, we're creating a local reference to the deleteBookmark() method. We have a reset() method that is resetting the currentBookmark to null. CurrentBookmark is essentially the placeholder that we're using to keep track of the bookmark that we're editing.

[02:17] If it's a new bookmark, then we're calling this initBookmark() method, and we're just returning a pristine, new bookmark object that's just keeping track of the category that you're currently in.

[02:29] Within our bookmarks.html, I am going to add ng-click, and we'll go ahead and call deleteBookmark() on the delete button, and we'll pass in the bookmark.

[02:46] Within the edit button, we're going to call ng-click again, and we'll call the editBookmark() method, and we'll pass in the bookmark that we want to edit.

[03:02] Just below that, you'll see that we have a div that contains a button. We're not going to show that if we're not currently on a category, but if we are on a category and there is not a current bookmark, when we click it, we're going to call bookmarksListController.createBookmark(), and then we're going to put it into create mode.

[03:25] What we have here is createBookmark(). It just initializes the new bookmark. EditBookmark() sets currentBookmark to the new bookmark we pass in. From there, when we emit the save event, then it'll call saveBookmark(). If there's an ID, then it'll update the bookmark. If not, it will create the bookmark. This is a simulated upsert pattern here.

[03:49] Once it's been saved, then we reset and we go from there. Now, we're going to add in the saveBookmark component and wire this up. We'll add the saveBookmark component to the DOM.

[04:04] We're going to toggle the visibility here. First and foremost, we're only going to show this component if there is a current bookmark. From here, we're going to pass in the bookmark that we want to edit. Then, let's hook up our output. We'll go save, and when save is emitted, we'll call onSave() and pass back the bookmark object.

[04:34] On cancel, we're just going to call reset(), and that's just going to set currentBookmark to null.

[04:39] Let's hop into the browser and check this out. Now you can see that when we're on a category, we can click Create Bookmark and create the bookmark. If we click the Edit icon, it's passing that bookmark in, and we can edit it.

[04:58] The problem is you can see as I typed in the title, it immediately updated it in not only the form but in the bookmarks list, as well, and if I canceled it, there's no way to back out of that. This is the problem of shared mutable state. How do we isolate that mutable operation so that we can back out of it or that change does not affect other places in the application?

[05:26] The problem is that this form is bound directly to the bookmark object, and we need to work around that. The way that we're going to do that is we're going to create a saveBookmarkController. From here, we're going to define our saveBookmarkController class. Then, we're just going to hook into the onChanges event hook.

[05:56] Let's just log this out real quick. When this event is fired, we're just going to log out onChangeFired. Let's hook this into our component. We'll add this to the configuration object here. Let's refresh the page. Now, let's clear this, and we'll now select a bookmark to edit.

[06:33] You can see here that when we are updating currentBookmark, that we're firing the console log. This is a closer approximation to a unidirectional data flow in Angular 1 in one-way data binding that when we change the object, we're firing this onChanges event.

[06:50] What we're going to do is we're going to create a new property called editedBookmark, and we're just going to use object.assign to return a new object that's a clone of the bookmark object. We'll update the form now to set the mutations on editedBookmark, leaving the bookmark that we sent in intact. We're just creating a local copy.

[07:13] Now, if I go here and I update this bookmark to point to the new angular.io site, you can see here that it's not updating, but when I save it, we're passing that up, and it is working.

[07:28] You can see here that I can create something, and I can cancel it out, no harm, no foul. If I want to update it, because I'm editing the editedBookmark local object, then it's basically isolated to that component. Then, it's only updated when we send it back.

[07:47] This is how you isolate state mutations within a component. You create a local object using object.assign, perform the mutable operations on that. If you want to cancel it, you throw it away. If you want to persist it, then you emit it back up to the parent or smart component.

Mike
Mike
~ 8 years ago

Is there any reason you would choose Object.assign instead of angular.copy?

Lukas Ruebbelke
Lukas Ruebbelkeinstructor
~ 8 years ago

I generally prefer native implementations unless there are good reasons to use an alternate implementation. As far as I know there is not a version of angular.copy in Angular 2 while Object.assign is here to stay. With that said, you could use angular.copy and it would work just fine.

Yeray
Yeray
~ 8 years ago

Hi Lukas, Is there any advantage of using controllerAs syntax and not the value "$ctrl" set by default in the components

Lukas Ruebbelke
Lukas Ruebbelkeinstructor
~ 8 years ago

This is in part due to personal preference but I find that explicitly defining controllerAs is more self-documenting than using $ctrl across the board. I have also found that in pre-existing code bases, it helps with consistency as most everything else is using controllerAs.

Michael
Michael
~ 8 years ago

We've used typescript since day 1 with angular, am curious if you think this would be a viable approach https://hotell.gitbooks.io/ng-metadata/content/ for 1x code bases versus your es6 approach.

Lukas Ruebbelke
Lukas Ruebbelkeinstructor
~ 8 years ago

I definitely think that is a viable approach and for the most complimentary to my approach. I see the TypeScript approach being an extension of what I am talking about. ES6 and TypeScript are great because they encourage a better architecture at the language level.

Markdown supported.
Become a member to join the discussionEnroll Today