{"id":1317,"date":"2016-08-19T14:32:58","date_gmt":"2016-08-19T14:32:58","guid":{"rendered":"https:\/\/ionic.io\/blog\/?p=1317"},"modified":"2016-09-01T05:30:20","modified_gmt":"2016-09-01T05:30:20","slug":"one-mean-ionic-2-todo-app-on-heroku-part-3","status":"publish","type":"post","link":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3","title":{"rendered":"One MEAN Ionic 2 Todo App on Heroku, Part 3"},"content":{"rendered":"<h3>Ionic 2 Frontend: Adding More Functionality<\/h3>\n<p>In the first two parts of this series, we set the stage for an Ionic 2 Todo app that can be hosted on Heroku. We set up the Node.js backend for the app, with an Express server that defined API endpoints for accessing a MongoDB database. We also laid the groundwork for an Ionic 2 app to the point where the Todos in our database were displaying on the screen. If you\u2019re just joining us now, head back over to <a href=\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-1\/\">Part 1<\/a> and start there.<\/p>\n<p>In this third and final part of the series, we\u2019ll beef up the functionality in our Ionic 2 app, play with more cool Angular 2 stuff, and finish up with a very simple, yet very functional app you can show off to all your friends.<br \/>\n<!--more--><\/p>\n<h3>Include Add, Edit, and Delete Functionality<\/h3>\n<p>In the <a href=\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-2\/\">last post<\/a>, we only implemented the Get method to grab todos from MongoDB. We\u2019ll now finish up the <code>TodoService<\/code> with  <code>add<\/code>, <code>update<\/code>, and <code>delete<\/code> functions.<\/p>\n<p>In our <code>loadTodos()<\/code> function, the only parameter we needed to pass to <code>http.get()<\/code> to access our data was the API endpoint: <code>api\/todos<\/code>. In our next three methods, because we are sending some sort of JSON data to our server, we\u2019ll create <a href=\"https:\/\/angular.io\/docs\/ts\/latest\/api\/http\/Headers-class.html\">Headers<\/a> for each HTTP requests.<\/p>\n<h3>Add<\/h3>\n<p>In the add function, we use the same endpoint as we did in <code>loadTodos()<\/code>. In addition, we also set the request body with the new Todo\u2019s <code>description<\/code> and <code>POST<\/code> it to <code>api\/todos<\/code>, where an <code>_id<\/code> will be generated and <code>isComplete: false<\/code> will be appended to the <code>Todo<\/code> before saving it to our <code>Todos<\/code> collection.<\/p>\n<pre><code class=\"javascript\">  \/\/ Add a todo-edit\n  add(todo: string): Observable&lt;Todo&gt; {\n    let body = JSON.stringify({description: todo});\n    let headers = new Headers({&#039;Content-Type&#039;: &#039;application\/json&#039;});\n\n    return this.http.post(this.todosUrl, body, {headers: headers})\n                    .map(res =&gt; res.json())\n                    .catch(this.handleError);\n  }\n<\/code><\/pre>\n<h3>Update<\/h3>\n<p>The <code>update<\/code> method (<code>http.put<\/code>) , receives a <code>Todo<\/code> object from our component. The <code>Todo<\/code> is used to append the <code>todo._id<\/code> to the url, telling our API which <code>Todo<\/code> we\u2019re updating. For the body of our request, we <code>JSON.stringify<\/code> the <code>Todo<\/code> before sending it off.<\/p>\n<pre><code class=\"javascript\">  \/\/ Update a todo\n  update(todo: Todo) {\n    let url = `${this.todosUrl}\/${todo._id}`; \/\/see mdn.io\/templateliterals\n    let body = JSON.stringify(todo)\n    let headers = new Headers({&#039;Content-Type&#039;: &#039;application\/json&#039;});\n\n    return this.http.put(url, body, {headers: headers})\n                    .map(() =&gt; todo) \/\/See mdn.io\/arrowfunctions\n                    .catch(this.handleError);\n  }\n<\/code><\/pre>\n<h3>Delete<\/h3>\n<p>In the <code>delete<\/code> method, we simply send over headers and specify the <code>todo._id<\/code> in the URL, which our server will use to identify and destroy the <code>Todo<\/code> in question.<\/p>\n<pre><code class=\"javascript\">  \/\/ Delete a todo\n  delete(todo: Todo) {\n    let url = `${this.todosUrl}\/${todo._id}`;\n    let headers = new Headers({&#039;Content-Type&#039;: &#039;application\/json&#039;});\n\n    return this.http.delete(url, headers)\n               .catch(this.handleError);\n  }\n<\/code><\/pre>\n<p>The final <code>todo-service.ts<\/code> file should look like this:<\/p>\n<pre><code class=\"javascript\">todo-service.ts\nimport {Injectable} from &#039;@angular\/core&#039;;\nimport {Http, Headers} from &#039;@angular\/http&#039;;\nimport {Observable} from &#039;rxjs\/Observable&#039;;\nimport {Todo} from &#039;..\/..\/todo.ts&#039;;\n\n\n@Injectable()\nexport class TodoService {\n  todosUrl = &quot;\/api\/todos&quot;\n\n  constructor(public http: Http) {}\n\n  \/\/ Get all todos\n  load(): Observable&lt;Todo[]&gt; {\n    return this.http.get(this.todosUrl)\n               .map(res =&gt; res.json())\n               .catch(this.handleError);\n  }\n\n  \/\/ Add a todo-edit\n  add(todo: string): Observable&lt;Todo&gt; {\n    let body = JSON.stringify({description: todo});\n    let headers = new Headers({&#039;Content-Type&#039;: &#039;application\/json&#039;});\n\n    return this.http.post(this.todosUrl, body, {headers: headers})\n                    .map(res =&gt; res.json())\n                    .catch(this.handleError);\n  }\n\n  \/\/ Update a todo\n  update(todo: Todo) {\n    let url = `${this.todosUrl}\/${todo._id}`; \/\/see mdn.io\/templateliterals\n    let body = JSON.stringify(todo)\n    let headers = new Headers({&#039;Content-Type&#039;: &#039;application\/json&#039;});\n\n    return this.http.put(url, body, {headers: headers})\n                    .map(() =&gt; todo) \/\/See mdn.io\/arrowfunctions\n                    .catch(this.handleError);\n  }\n\n  \/\/ Delete a todo\n  delete(todo: Todo) {\n    let url = `${this.todosUrl}\/${todo._id}`;\n    let headers = new Headers({&#039;Content-Type&#039;: &#039;application\/json&#039;});\n\n    return this.http.delete(url, headers)\n               .catch(this.handleError);\n  }\n\n  handleError(error) {\n      console.error(error);\n      return Observable.throw(error.json().error || &#039;Server error&#039;);\n  }\n}\n<\/code><\/pre>\n<p>Now, we have access points for our API that we can use in our app components. Let\u2019s jump back up into our <code>HomePage<\/code> component to create methods that will use our service\u2019s new HTTP methods.<\/p>\n<h3><code>HomePage<\/code> Component: Adding, Updating, and Deleting Todos<\/h3>\n<p>For each of the new methods in our <code>TodoService<\/code>, we want to write a method that our <code>HomePage<\/code> Component template can use to add, update, and delete our todos. From this main list view of our todos, we\u2019ll want to create an <code>addTodo<\/code> method for our input form to use, a <code>toggleComplete<\/code> method for our checkbox to use for updating the <code>isComplete<\/code> status of a given <code>Todo<\/code>, and a <code>deleteTodo<\/code> method for our nice red trash can button.<\/p>\n<p>Below the <code>load<\/code> method in our <code>HomePage<\/code> class, let\u2019s add our new methods.<\/p>\n<h3>The <code>addTodo<\/code> Method<\/h3>\n<p>This first function will take in a <code>string<\/code> value from our input box, and call <code>todoService.add<\/code>. We then <code>subscribe<\/code> to the observable, like we did in the <code>loadTodos<\/code> function, so the request will go out. Inside our <code>subscribe<\/code> statement, we extract the data containing our newly created <code>Todo<\/code> and <code>push<\/code> it onto our <code>todos<\/code> array.<\/p>\n<pre><code class=\"javascript\">  addTodo(todo:string) {\n    this.todoService.add(todo)\n        .subscribe(data  =&gt; {\n          this.todos.push(data)\n        });\n  }\n<\/code><\/pre>\n<h3>The <code>toggleComplete<\/code> Method<\/h3>\n<p>Our second method will receive an actual <code>Todo<\/code> object from the view. The first step is to set the <code>isComplete<\/code> property of our <code>Todo<\/code> equal to its opposite (i.e., <code>true<\/code> becomes false or vice versa). Then we ask our <code>TodoService<\/code> to <code>update<\/code> that particular <code>Todo<\/code>. Because we toggled  a property of a Todo object already in our <code>HomePage<\/code> component, the view has already been updated.<\/p>\n<pre><code class=\"javascript\">  toggleComplete(todo: Todo) {\n    todo.isComplete = !todo.isComplete;\n    this.todoService.update(todo)\n        .subscribe(data =&gt; {\n          todo = data;\n        })\n  }\n<\/code><\/pre>\n<h3>The <code>deleteTodo<\/code> Method<\/h3>\n<p>Our final method will receive a <code>Todo<\/code> object, in addition to a second parameter indicating the index of that <code>Todo<\/code> in our class variable, the <code>todos<\/code> array. We\u2019ll figure out how to get the <code>index<\/code> from our HomePage component template in a moment. This method looks like the other two, except that in our <code>subscribe<\/code> function, we have logic to remove the <code>Todo<\/code> from our <code>todos<\/code> array, once we\u2019ve received confirmation that the <code>Todo<\/code> was deleted from the database.<\/p>\n<pre><code class=\"javascript\">  deleteTodo(todo: Todo, index:number) {\n    this.todoService.delete(todo)\n        .subscribe(response =&gt; {\n          this.todos.splice(index, 1);\n        });\n  }\n<\/code><\/pre>\n<p>Now, we can use these three methods in our <code>HomePage<\/code> component\u2019s template file.<\/p>\n<h3>Event Handling in our <code>HomePage<\/code> Template<\/h3>\n<p>As I pointed out the last time we looked at our <code>home.html<\/code> file, there were several spots where we were listening for events but not doing anything with them. Let\u2019s make our app more interactive, starting with our little Add a Todo form in the template file.<\/p>\n<h3>Add a Todo<\/h3>\n<p>Currently, we have:<\/p>\n<pre><code class=\"html\">  &lt;ion-item no-lines&gt;\n    &lt;ion-input #newTodo (keyup.enter)=&quot;&quot; type=&quot;text&quot; placeholder=&quot;Add new todo...&quot;&gt;     \n    &lt;\/ion-input&gt;\n    &lt;button clear large item-right (click)=&quot;&quot;&gt;\n      &lt;ion-icon name=&quot;add&quot;&gt; &lt;\/ion-icon&gt;\n    &lt;\/button&gt;\n  &lt;\/ion-item&gt;\n<\/code><\/pre>\n<p>Let\u2019s fire update the <code>(keyup.enter)<\/code> event listener, which will fire when a user is in the input box and presses the <code>enter<\/code> or <code>return<\/code> key. We want to add a new todo when a person presses enter, so let\u2019s put our <code>addTodo<\/code> method into that line.<\/p>\n<pre><code class=\"html\">    (keyup.enter)=&quot;addTodo(newTodo.value); newTodo.value=&#039;&#039;&quot;\n<\/code><\/pre>\n<p>Inside the double quotes, we\u2019ve added <code>addTodo<\/code> and passed in <code>newTodo.value<\/code>. In Angular 2 templates, I\u2019m able to specify template reference variables, such as <code>#newTodo<\/code> on my <code>&lt;ion-input&gt;<\/code> element, and work with that element within the template using its reference variable. Thus, we use <code>newTodo<\/code> to get the <code>value<\/code> of our <code>&lt;ion-input&gt;<\/code>, and then set its value to an empty string to clear it out with <code>newTodo.value=&#039;&#039;<\/code>.<\/p>\n<p>Now, let\u2019s do the same thing for our button, should a user want to <code>click<\/code> that, instead of pressing <code>enter<\/code>.<\/p>\n<pre><code class=\"html\">    (click)=&quot;addTodo(newTodo.value); newTodo.value=&#039;&#039;&quot;\n<\/code><\/pre>\n<h3>Delete or Update a Todo<\/h3>\n<p>Next, let\u2019s use our <code>toggleComplete<\/code> and <code>deleteTodo<\/code>. Our Todo List currently looks like this:<\/p>\n<pre><code class=\"html\">  &lt;h2&gt;Todo List &lt;small&gt;Swipe a todo to edit or delete&lt;\/small&gt;&lt;\/h2&gt;\n  &lt;ion-list no-lines&gt;\n    &lt;ion-item-sliding #slidingItem *ngFor=&quot;let todo of todos; let index = index&quot;&gt;\n      &lt;ion-item&gt;\n        &lt;ion-checkbox (click)=&quot;toggleComplete(todo)&quot; [checked]=&quot;todo.isComplete&quot;&gt;&lt;\/ion-checkbox&gt;\n        &lt;ion-item text-wrap item-left [class.completed]=&quot;todo.isComplete&quot;&gt;\n          {{ todo.description }}\n        &lt;\/ion-item&gt;\n      &lt;\/ion-item&gt;\n      &lt;ion-item-options&gt;\n        &lt;button (click)=&quot;&quot;&gt;Edit&lt;\/button&gt;\n        &lt;button danger (click)=&quot;deleteTodo(todo, index)&quot;&gt;\n          &lt;ion-icon name=&quot;trash&quot;&gt;&lt;\/ion-icon&gt;\n        &lt;\/button&gt;\n      &lt;\/ion-item-options&gt;\n    &lt;\/ion-item-sliding&gt;\n  &lt;\/ion-list&gt;\n<\/code><\/pre>\n<p>As you may remember, we need to get access to the index of the current todo with which we\u2019re dealing. Within the scope of our <code>NgFor<\/code> directive, we have access to its local variable \u2018index\u2019. All we have to do is define it right within the quotes of our <code>*ngFor<\/code>:<br \/>\n*ngFor=&#8221;let todo of todos; let index = index&#8221;<\/p>\n<p>Now, we have access to the index of any given <code>Todo<\/code> in our <code>todos<\/code> array within our repeated <code>&lt;ion-item-sliding&gt;<\/code> element. Let\u2019s use it on our beautiful red trash can button:<\/p>\n<pre><code class=\"html\">        &lt;button danger (click)=&quot;&quot;&gt;\n          &lt;ion-icon name=&quot;trash&quot;&gt;&lt;\/ion-icon&gt;\n        &lt;\/button&gt;\n<\/code><\/pre>\n<p>Add the following to our <code>(click)<\/code> event:<\/p>\n<pre><code class=\"html\">(click)=&quot;deleteTodo(todo, index)&quot;\n<\/code><\/pre>\n<p>Finally, for this section, let\u2019s set the <code>(click)<\/code> event on the checkbox to fire our <code>toggleComplete<\/code> method:<\/p>\n<pre><code class=\"html\">&lt;ion-checkbox (click)=&quot;toggleComplete(todo)&quot; [checked]=&quot;todo.isComplete&quot;&gt;\n<\/code><\/pre>\n<h3>A Note on Property Binding<\/h3>\n<p>Although I\u2019d previously added them without explanation, we have both the <code>[checked]<\/code> property of our checkbox and the <code>[class.completed]<\/code> property of our <code>&lt;ion-item&gt;<\/code> bound to the <code>isComplete<\/code> property of each <code>Todo<\/code>. When the page loads, if <code>isComplete<\/code> is true, our checkbox will be checked, and our <code>description<\/code> will be styled by the <code>.completed<\/code> class.<\/p>\n<p>Your Todo App is now almost complete! You can now load, add, update, and delete your todos. The final step we\u2019ll take is to add one more page, where we can view an individual <code>Todo<\/code> and edit its <code>description<\/code>, the TodoEdit page.<\/p>\n<h3>Add the TodoEdit Page<\/h3>\n<p>Using the Ionic CLI, we can easily generate our new page:<\/p>\n<pre><code>$ \/todo-ionic2-heroku &gt; ionic g page TodoEdit\n<\/code><\/pre>\n<p>This just created a folder for us <code>app\/pages\/todo-edit\/<\/code> with HTML, TypeScript, and SCSS files for our new TodoEdit component. To navigate to it from our <code>home<\/code> page to the <code>todo-edit<\/code> page, we\u2019ll need to edit a few things.<\/p>\n<p>Here\u2019s the updated and now final <code>home.ts<\/code> file:<\/p>\n<pre><code class=\"javascript\">\/\/ home.ts\nimport {Component} from &quot;@angular\/core&quot;;\nimport {NavController, ItemSliding, Item} from &#039;ionic-angular&#039;;\nimport {TodoEditPage} from &#039;..\/todo-edit\/todo-edit&#039;;\nimport {TodoService} from &#039;..\/..\/providers\/todo-service\/todo-service&#039;;\nimport {Todo} from &#039;..\/..\/todo.ts&#039;;\n\n@Component({\n  templateUrl: &#039;build\/pages\/home\/home.html&#039;,\n  providers: [TodoService]\n})\nexport class HomePage {\n  public todos: Todo[];\n\n  constructor(public todoService: TodoService,\n              public nav: NavController) {\n    this.loadTodos();\n  }\n\n  loadTodos() {\n    this.todoService.load()\n      .subscribe(todoList =&gt; {\n        this.todos = todoList;\n      })\n  }\n\n  addTodo(todo:string) {\n    this.todoService.add(todo)\n        .subscribe(newTodo  =&gt; {\n          this.todos.push(newTodo);\n        });\n  }\n\n  toggleComplete(todo: Todo) {\n    todo.isComplete = !todo.isComplete;\n    this.todoService.update(todo)\n        .subscribe(updatedTodo =&gt; {\n          todo = updatedTodo;\n        });\n  }\n\n  deleteTodo(todo: Todo, index:number) {\n    this.todoService.delete(todo)\n        .subscribe(res =&gt; {\n          this.todos.splice(index, 1);\n        });\n  }\n\n  navToEdit(todo: Todo, index: number) {\n    this.nav.push(TodoEditPage, {\n      todo: todo,\n      todos: this.todos,\n      index: index\n    });\n  }\n}\n<\/code><\/pre>\n<p>The changes are:<br \/>\n* Added <code>import<\/code> statements for our <code>TodoEditPage<\/code> and <code>NavController<\/code> from <code>ionic-angular<\/code><br \/>\n* Added <code>NavController<\/code> as a dependency in our constructor<br \/>\n* Created a new method <code>navToEdit<\/code>, which uses the <code>NavController<\/code> to <code>push<\/code> on a new page (the <code>TodoEdit<\/code> page). We pass the <code>Todo<\/code> and its <code>index<\/code> from the view, as well as the our <code>todos<\/code> array in the <code>push<\/code> function, as NavParams that we\u2019ll extract in the <code>TodoEdit<\/code> class.<\/p>\n<p>Update your <code>HomePage<\/code> template file (<code>home.html<\/code>), so the edit button will navigate to the <code>TodoEdit<\/code> page and pass along the <code>Todo<\/code>:<\/p>\n<pre><code class=\"html\">&lt;button (click)=&quot;navToEdit(todo, index); slidingItem.close()&quot;&gt;\n  Edit\n&lt;\/button&gt;\n<\/code><\/pre>\n<p>Note: We are also <a href=\"http:\/\/ionicframework.com\/docs\/v2\/api\/components\/item\/ItemSliding\/#close\">closing our sliding item<\/a> when clicking the <code>edit<\/code> button<\/p>\n<p>In Ionic 2 Beta 9, the <code>ionSwipe<\/code> action was introduced. If desired, we could also open a todos by adding this event to our <code>ion-item-options<\/code> component:<\/p>\n<pre><code class=\"html\">&lt;ion-item-options (ionSwipe)=&quot;navToEdit(todo, index); slidingItem.close()&quot;&gt;\n &lt;!-- buttons here --&gt;\n&lt;\/ion-item-options&gt;\n<\/code><\/pre>\n<p>Now, you can also open the TodoEdit page by swiping a todo!<\/p>\n<h3>Adding functionality to our TodoEdit Page<\/h3>\n<p>Awesomeness; now, we can navigate between our two pages! But before we call it a day, we have to finish our <code>TodoEdit<\/code> component.<\/p>\n<p>Head into your <code>todo-edit.ts<\/code> file and update it with the following code:<\/p>\n<pre><code class=\"javascript\">\/\/ app\/pages\/todo-edit\/todo-edit.ts\nimport {Component} from &quot;@angular\/core&quot;;\nimport {NavController, NavParams} from &#039;ionic-angular&#039;;\nimport {TodoService} from &#039;..\/..\/providers\/todo-service\/todo-service&#039;;\nimport {Todo} from &#039;..\/..\/todo.ts&#039;;\n\n@Component({\n  templateUrl: &#039;build\/pages\/todo-edit\/todo-edit.html&#039;,\n  providers: [TodoService]\n})\nexport class TodoEditPage {\n  public todo: Todo;    \/\/ The todo itself\n  public todos: Todo[]; \/\/ The list of todos from the main page\n  public index: number; \/\/ The index of the todo we&#039;re looking at\n\n  constructor(public todoService: TodoService, public nav: NavController, public navParams: NavParams ) {\n    this.todo = navParams.get(&#039;todo&#039;);\n    this.todos = navParams.get(&#039;todos&#039;);\n    this.index = navParams.get(&#039;index&#039;);\n  }\n\n  saveTodo(updatedDescription: string) {\n    this.todo.description = updatedDescription;\n    this.todoService.update(this.todo)\n        .subscribe(response =&gt; {\n          this.nav.pop(); \/\/ go back to todo list\n        });\n  }\n\n  deleteTodo() {\n    this.todoService.delete(this.todo)\n      .subscribe(response =&gt; {\n        this.todos.splice(this.index, 1); \/\/ remove the todo\n        this.nav.pop(); \/\/go back to todo list\n      });\n  }\n}\n<\/code><\/pre>\n<p>At this point, most of this should look pretty similar to our <code>home.ts<\/code>. The only new features are <code>NavParams<\/code>, which we use to extract the parameters we passed over when we did <code>nav.push<\/code> from our <code>HomePage<\/code> class.<\/p>\n<p>We also use the TodoService in a similar fashion to save (update) or delete a todo. In both of those methods, upon receiving our response from our TodoService, we navigate back to our previous page, the Home page, using the <code>NavController<\/code>\u2019s <code>pop<\/code> method.<\/p>\n<h3>Updating the TodoEdit Template<\/h3>\n<p>In the template for our <code>TodoEdit<\/code> page, we\u2019ll use an <a href=\"http:\/\/ionicframework.com\/docs\/v2\/api\/components\/input\/TextArea\/#text-area\">Ionic TextArea component<\/a> to display and allow for editing of our todo. Then we simply need a Save button to update the Todo, a Cancel button to ABORT MISSION and go back to the <code>Home<\/code> page, and a large, red trash can button, because I\u2019m really awful at writing todo lists and need multiple opportunities to delete them.<\/p>\n<p>Edit <code>todo-edit.html<\/code> to contain the following.<\/p>\n<pre><code class=\"html\">&lt;ion-header&gt;\n  &lt;ion-navbar primary&gt;\n    &lt;ion-title&gt;Edit&lt;\/ion-title&gt;\n  &lt;\/ion-navbar&gt;\n&lt;\/ion-header&gt;\n&lt;ion-content padding class=&quot;todo-edit&quot;&gt;\n  &lt;h2&gt;Description&lt;\/h2&gt;\n  &lt;ion-textarea #updatedTodo value=&quot;{{todo.description}}&quot;&gt;&lt;\/ion-textarea&gt;\n  &lt;small&gt;Status: {{ todo.isComplete ? &quot;Complete&quot; : &quot;Incomplete&quot; }}&lt;\/small&gt;\n\n  &lt;ion-row&gt;\n    &lt;ion-col width-40&gt;\n      &lt;button block (click)=&quot;saveTodo(updatedTodo.value)&quot;&gt;\n        Save\n      &lt;\/button&gt;\n    &lt;\/ion-col&gt;\n    &lt;ion-col width-40&gt;\n      &lt;button light block nav-pop&gt;\n        Cancel\n      &lt;\/button&gt;\n    &lt;\/ion-col&gt;\n    &lt;ion-col width-20&gt;\n      &lt;button danger block (click)=&quot;deleteTodo()&quot;&gt;\n        &lt;ion-icon name=&quot;trash&quot;&gt;&lt;\/ion-icon&gt;\n      &lt;\/button&gt;\n    &lt;\/ion-col&gt;\n  &lt;\/ion-row&gt;\n&lt;\/ion-content&gt;\n<\/code><\/pre>\n<p>Note: I\u2019m using Ionic\u2019s snazzy <a href=\"http:\/\/ionicframework.com\/docs\/v2\/components\/#grid\"><code>Grid<\/code> components<\/a> to create a row of different-sized buttons.<\/p>\n<p>And just like that (drum roll, please), we\u2019re done with development. Now, all you have to do is commit your changes:<\/p>\n<pre><code>$ \/todo-ionic2-heroku &gt; git add -A\n$ \/todo-ionic2-heroku &gt; git commit -m &quot;Second Commit, also I rock for finishing this app&quot;\n$ \/todo-ionic2-heroku &gt; git push heroku master\n<\/code><\/pre>\n<p>Again, make sure your app is running:<\/p>\n<pre><code>$\/todo-ionic2-heroku &gt; heroku ps:scale web=1\n<\/code><\/pre>\n<p>And open your app:<\/p>\n<pre><code>$\/todo-ionic2-heroku &gt; heroku open\n<\/code><\/pre>\n<p>How awesome is Heroku? So easy, right?<\/p>\n<h3>Some Final Thoughts<\/h3>\n<p>Congratulations! You made it, and we covered quite a bit of ground. We built a Node.js backend using Express and MongoDB, defining API routes for our frontend to use. We built an Ionic 2 Todo app using Angular 2 and TypeScript that interacts with our API to get, add, update, and delete todos. Lastly, we set up the final app on Heroku. This app, while bare-bones, was intended to demonstrate Ionic 2 and Angular 2 concepts, as they interact with a Node.js backend, and how to host an app on Heroku. The repository will continually be updated as new versions of Ionic are released.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ionic 2 Frontend: Adding More Functionality In the first two parts of this series, we set the stage for an Ionic 2 Todo app that can be hosted on Heroku. We set up the Node.js backend for the app, with an Express server that defined API endpoints for accessing a MongoDB database. We also laid [&hellip;]<\/p>\n","protected":false},"author":30,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"publish_to_discourse":"","publish_post_category":"","wpdc_auto_publish_overridden":"","wpdc_topic_tags":"","wpdc_pin_topic":"","wpdc_pin_until":"","discourse_post_id":"","discourse_permalink":"","wpdc_publishing_response":"","wpdc_publishing_error":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[23,13,25],"class_list":["post-1317","post","type-post","status-publish","format-standard","hentry","category-all","tag-framework","tag-ionic-2","tag-tutorials"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v23.0 (Yoast SEO v23.0) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>One MEAN Ionic 2 Todo App on Heroku, Part 3 - Ionic Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"One MEAN Ionic 2 Todo App on Heroku, Part 3\" \/>\n<meta property=\"og:description\" content=\"Ionic 2 Frontend: Adding More Functionality In the first two parts of this series, we set the stage for an Ionic 2 Todo app that can be hosted on Heroku. We set up the Node.js backend for the app, with an Express server that defined API endpoints for accessing a MongoDB database. We also laid [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3\" \/>\n<meta property=\"og:site_name\" content=\"Ionic Blog\" \/>\n<meta property=\"article:published_time\" content=\"2016-08-19T14:32:58+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2016-09-01T05:30:20+00:00\" \/>\n<meta name=\"author\" content=\"Justin Leatherwood\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:site\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Justin Leatherwood\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3#article\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3\"},\"author\":{\"name\":\"Justin Leatherwood\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/ebd8f9961dbecd11be74bf51d440689b\"},\"headline\":\"One MEAN Ionic 2 Todo App on Heroku, Part 3\",\"datePublished\":\"2016-08-19T14:32:58+00:00\",\"dateModified\":\"2016-09-01T05:30:20+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3\"},\"wordCount\":1584,\"commentCount\":10,\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"keywords\":[\"Framework\",\"Ionic 2\",\"Tutorials\"],\"articleSection\":[\"All\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3\",\"url\":\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3\",\"name\":\"One MEAN Ionic 2 Todo App on Heroku, Part 3 - Ionic Blog\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/#website\"},\"datePublished\":\"2016-08-19T14:32:58+00:00\",\"dateModified\":\"2016-09-01T05:30:20+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/ionic.io\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"One MEAN Ionic 2 Todo App on Heroku, Part 3\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/ionic.io\/blog\/#website\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"name\":\"ionic.io\/blog\",\"description\":\"Build amazing native and progressive web apps with the web\",\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/ionic.io\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/ionic.io\/blog\/#organization\",\"name\":\"Ionic\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"width\":1920,\"height\":854,\"caption\":\"Ionic\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/ionicframework\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/ebd8f9961dbecd11be74bf51d440689b\",\"name\":\"Justin Leatherwood\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/b618dcf46835261d07da1f0caf5847b8cf86ac4aae30f6073764ed6747e57544?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/b618dcf46835261d07da1f0caf5847b8cf86ac4aae30f6073764ed6747e57544?s=96&d=mm&r=g\",\"caption\":\"Justin Leatherwood\"},\"url\":\"https:\/\/ionic.io\/blog\/author\/justin\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"One MEAN Ionic 2 Todo App on Heroku, Part 3 - Ionic Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3","og_locale":"en_US","og_type":"article","og_title":"One MEAN Ionic 2 Todo App on Heroku, Part 3","og_description":"Ionic 2 Frontend: Adding More Functionality In the first two parts of this series, we set the stage for an Ionic 2 Todo app that can be hosted on Heroku. We set up the Node.js backend for the app, with an Express server that defined API endpoints for accessing a MongoDB database. We also laid [&hellip;]","og_url":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3","og_site_name":"Ionic Blog","article_published_time":"2016-08-19T14:32:58+00:00","article_modified_time":"2016-09-01T05:30:20+00:00","author":"Justin Leatherwood","twitter_card":"summary_large_image","twitter_creator":"@ionicframework","twitter_site":"@ionicframework","twitter_misc":{"Written by":"Justin Leatherwood","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3#article","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3"},"author":{"name":"Justin Leatherwood","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/ebd8f9961dbecd11be74bf51d440689b"},"headline":"One MEAN Ionic 2 Todo App on Heroku, Part 3","datePublished":"2016-08-19T14:32:58+00:00","dateModified":"2016-09-01T05:30:20+00:00","mainEntityOfPage":{"@id":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3"},"wordCount":1584,"commentCount":10,"publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"keywords":["Framework","Ionic 2","Tutorials"],"articleSection":["All"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3","url":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3","name":"One MEAN Ionic 2 Todo App on Heroku, Part 3 - Ionic Blog","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/#website"},"datePublished":"2016-08-19T14:32:58+00:00","dateModified":"2016-09-01T05:30:20+00:00","breadcrumb":{"@id":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/ionic.io\/blog\/one-mean-ionic-2-todo-app-on-heroku-part-3#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ionic.io\/blog"},{"@type":"ListItem","position":2,"name":"One MEAN Ionic 2 Todo App on Heroku, Part 3"}]},{"@type":"WebSite","@id":"https:\/\/ionic.io\/blog\/#website","url":"https:\/\/ionic.io\/blog\/","name":"ionic.io\/blog","description":"Build amazing native and progressive web apps with the web","publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ionic.io\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/ionic.io\/blog\/#organization","name":"Ionic","url":"https:\/\/ionic.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","width":1920,"height":854,"caption":"Ionic"},"image":{"@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/ionicframework"]},{"@type":"Person","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/ebd8f9961dbecd11be74bf51d440689b","name":"Justin Leatherwood","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/b618dcf46835261d07da1f0caf5847b8cf86ac4aae30f6073764ed6747e57544?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b618dcf46835261d07da1f0caf5847b8cf86ac4aae30f6073764ed6747e57544?s=96&d=mm&r=g","caption":"Justin Leatherwood"},"url":"https:\/\/ionic.io\/blog\/author\/justin"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/1317","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/users\/30"}],"replies":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/comments?post=1317"}],"version-history":[{"count":0,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/1317\/revisions"}],"wp:attachment":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media?parent=1317"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/categories?post=1317"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/tags?post=1317"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}