How to Auto-Save your model in Angular.js using $watch and a Debounce function.

<h3 id="the-problem">The Problem</h3> <p>Currently, I&rsquo;m working on an Angular app that is very form-centric. Fields and fields and more fields. I want to make the form-filling process as quick and painless as possible, so I&rsquo;m trying to implement auto-save.</p> <p></p> <p><hr></p> <h3 id="update-march-19th-2014">Update (March 19th, 2014)</h3> <p>I&rsquo;ve done quite a bit more auto-saving on my current project and I&rsquo;ve updated my post with some slightly better methods. It also now has a solution for when you are trying to auto-save multiple items in an array. I combined a lot of the info below into <a href="http://jsbin.com/xohugepe/1/edit?html,js,output">this live example</a>.</p> <p><hr></p> <h3 id="first-attempt">First Attempt</h3> <p>My first attempt at an auto-save solution (while still being very new to Angular) was a directive that, based on the type of element, waited for either a <code>change</code> or <code>blur</code> event to occur and communicated to the controller (via a service) that it was time to save the model.</p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">'text'</span> <span class="na">ng-model=</span><span class="s">'myModel.field1'</span> <span class="na">auto-save</span> <span class="nt">/&gt;</span> <span class="nt">&lt;select</span> <span class="na">ng-model=</span><span class="s">'myModel.field2'</span> <span class="na">auto-save</span> <span class="nt">&gt;</span>...<span class="nt">&lt;/select&gt;</span> </code></pre></div> <p>A simpler version of this method might look something like this:</p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">'text'</span> <span class="na">ng-model=</span><span class="s">'myModel.field1'</span> <span class="na">ng-blur=</span><span class="s">'save()'</span> <span class="nt">/&gt;</span> <span class="nt">&lt;select</span> <span class="na">ng-model=</span><span class="s">'myModel.field2'</span> <span class="na">ng-change=</span><span class="s">'save()'</span> <span class="nt">&gt;</span>...<span class="nt">&lt;/select&gt;</span> </code></pre></div> <p>There are a few problems with this method:</p> <ol> <li>I have to add these extra attributes to each and every input field. If I happen to forget one, my user may lose their data since there aren&rsquo;t any explicit &lsquo;Save&rsquo; buttons.</li> <li>It won&rsquo;t auto-save until the user changes the focus of the field. This is particularly problematic with <code>&lt;textarea&gt;</code> fields, where the user may type for long periods of time before changing focus.</li> <li>I&rsquo;d much rather my view not know anything about when my model is or isn&rsquo;t being saved to the server.</li> </ol> <h3 id="new-solution-watch-and-debounce">New Solution: $watch and debounce</h3> <p>My new solution uses the <code>$watch</code> method that Angular provides for watching for changes to scope variables. I don&rsquo;t want to make requests to the server with every keystroke, so I&rsquo;ll use a simple debounce function (using Angular&rsquo;s <code>$timeout</code> service) to limit my requests to happening every few seconds.</p> <p>To implement this solution, there is nothing to add to your view. But in the controller, you&rsquo;ll need to inject <code>$timeout</code> and write a couple <code>$watch</code> statement. I added a watch statement for each individual attribute of the model, but you could also do a deep watch of the entire model. This is slightly more expensive performance-wise.</p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">'text'</span> <span class="na">ng-model=</span><span class="s">'myModel.field1'</span> <span class="nt">/&gt;</span> <span class="nt">&lt;select</span> <span class="na">ng-model=</span><span class="s">'myModel.field2'</span><span class="nt">&gt;</span>...<span class="nt">&lt;/select&gt;</span> </code></pre></div><div class="highlight"><pre class="highlight javascript"><code><span class="nx">app</span><span class="p">.</span><span class="nx">controller</span><span class="p">(</span><span class="dl">'</span><span class="s1">myCtrl</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">$scope</span><span class="p">,</span> <span class="nx">$timeout</span><span class="p">)</span> <span class="p">{</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">myModel</span> <span class="o">=</span> <span class="p">{};</span> <span class="kd">var</span> <span class="nx">timeout</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">saveUpdates</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="c1">// .... save data to server</span> <span class="p">};</span> <span class="kd">var</span> <span class="nx">debounceSaveUpdates</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">newVal</span><span class="p">,</span> <span class="nx">oldVal</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">newVal</span> <span class="o">!=</span> <span class="nx">oldVal</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">timeout</span><span class="p">)</span> <span class="p">{</span> <span class="nx">$timeout</span><span class="p">.</span><span class="nx">cancel</span><span class="p">(</span><span class="nx">timeout</span><span class="p">)</span> <span class="p">}</span> <span class="nx">timeout</span> <span class="o">=</span> <span class="nx">$timeout</span><span class="p">(</span><span class="nx">saveUpdates</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span> <span class="c1">// 1000 = 1 second</span> <span class="p">}</span> <span class="p">};</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">$watch</span><span class="p">(</span><span class="dl">'</span><span class="s1">myModel.field1</span><span class="dl">'</span><span class="p">,</span> <span class="nx">debounceSaveUpdates</span><span class="p">)</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">$watch</span><span class="p">(</span><span class="dl">'</span><span class="s1">myModel.field2</span><span class="dl">'</span><span class="p">,</span> <span class="nx">debounceSaveUpdates</span><span class="p">)</span> <span class="p">});</span> </code></pre></div> <h3 id="potential-gotcha-1-validation">Potential Gotcha #1: Validation</h3> <p>Angular.js doesn&rsquo;t provide any sort of model validation (or any model-related code at all) and instead recommends that you validate the form using a combination of HTML5 validation and a few built in directives. Angular includes a way to access our form and its <code>$valid</code> attribute. First, we need to wrap our fields in a <code>&lt;form&gt;</code> tag with a <code>name</code> attribute and also make our fields required.</p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;form</span> <span class="na">name=</span><span class="s">'myForm'</span><span class="nt">&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">'text'</span> <span class="na">ng-model=</span><span class="s">'myModel.field1'</span> <span class="na">required</span> <span class="nt">/&gt;</span> <span class="nt">&lt;select</span> <span class="na">ng-model=</span><span class="s">'myModel.field2'</span> <span class="na">required</span><span class="nt">&gt;</span>...<span class="nt">&lt;/select&gt;</span> <span class="nt">&lt;/form&gt;</span> </code></pre></div> <p>Next, we need to update our controller a bit to check the validity of our form.</p> <div class="highlight"><pre class="highlight javascript"><code> <span class="kd">var</span> <span class="nx">saveUpdates</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">$scope</span><span class="p">.</span><span class="nx">myForm</span><span class="p">.</span><span class="nx">$valid</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// .... save data to server</span> <span class="p">}</span> <span class="p">};</span> </code></pre></div> <p>If you need more complex validators, you&rsquo;ll need to create custom directives that make use of the <a href="http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController#methods_$setvalidity">$setValidity</a> method.</p> <h3 id="potential-gotcha-2-preventing-double-saves-on-create">Potential Gotcha #2: Preventing Double-Saves on Create</h3> <p>One problem I ran into was that when I auto-saved a brand new object was that the object was replaced with the version from the server, causing the <code>$watch</code> function to be triggered again. The result was that my object was immediately saved a second time, unnecessarily. The solution to this is fairly simple: just set a <code>saveInProgress</code> flag so that it never starts another save before the previous one finishes. This will also prevent a double-save caused by a save call that is slower than the timeout on your debounce function. It might look something like this:</p> <div class="highlight"><pre class="highlight javascript"><code><span class="kd">var</span> <span class="nx">saveInProgress</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">saveFinished</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">saveInProgress</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="p">};</span> <span class="kd">var</span> <span class="nx">saveUpdates</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">$scope</span><span class="p">.</span><span class="nx">myForm</span><span class="p">.</span><span class="nx">$valid</span><span class="p">)</span> <span class="p">{</span> <span class="nx">saveInProgress</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="nx">saveFunction</span><span class="p">().</span><span class="k">finally</span><span class="p">(</span><span class="nx">saveFinished</span><span class="p">);</span> <span class="c1">// The finally method runs regarless of whether the save was successful or not.</span> <span class="p">}</span> <span class="p">};</span> </code></pre></div> <p>A related and much harder problem is how to resolve changes to the model that happen during the round-trip of the save. I recently <a href="https://twitter.com/realtarnschaf/status/407489612227940352">had a discussion on Twitter about this</a>, but I think it really comes down to designing your UX to discourage or prevent your user from making changes to the model while still waiting on that first <code>POST</code> creation request. If you also replace your model with the server&rsquo;s version on subsequent updates (which is generally not recommended), the problem gets a little trickier.</p> <h3 id="potential-gotcha-3-auto-saving-individual-items-in-an-array">Potential Gotcha #3: Auto-Saving individual items in an array</h3> <p>Another issue that you&rsquo;ll often run into is when you have an array of objects, perhaps in a grid interface. While angular does provide a <code>$watchCollection</code> method, it only does a shallow watch on the array as a whole and won&rsquo;t tell you which item was changed.</p> <p>Instead, a better solution is to wrap the fields of each individual item inside a child controller or directive. <a href="http://jsbin.com/xohugepe/1/edit?html,js,output">Here is a live example of how that would work.</a></p> <p>And that&rsquo;s it! This is an extremely simple example, so if you have any questions or comments, give me a shout out <a href="http://twitter.com/adam_albrecht">on Twitter</a>.</p>