Using a checkbox to trigger a secondary action (call a function) in Angular

I’ve got a check box on one of the lines in the code below. Currently the ng-model directive is used to set when the todo has been completed, which works just fine. What I need to be able to do though is to also record the date/time at which this happens and save that in my array of todo objects.

What seemed the most obvious solution though, isn’t recommended - ng-checked - as it says

Note that this directive should not be used together with ngModel, as this can lead to unexpected behaviour.

My reading so far hasn’t come up with a way round this; at least not one that currently makes sense to me. Could someone give me a few pointers as to how I should approach this?

<!-- begin show all remaining todos -->
<section class="todo-list">
  <ul>
    <li ng-repeat="todo in todos.todosRemaining() track by $index">
      <input class="checkbox" type="checkbox" ng-model="todo.done" id="todo-{{$index}}"> // <-- THIS LINE HERE
      <label for="todo-{{$index}}" ng-class="{done: todo.done}">{{ todo.task }}</label>
      <a ui-sref="todosShow({ id: $index })">more info</a>
      <button class="btn btn-delete" ng-click="todos.todosDelete(todo)">x</button>
      <div class="todoDates"><em>Created:</em> {{ todo.createDte | date:'d MMM yyyy, HH:mm:ss' }}. <em>Due:</em> {{ todo.dueDte | date:'d MMM yyyy, HH:mm:ss' }}</div>
    </li>
  </ul>
</section>
<!-- end show all todos -->

Hi @chrisofarabia, I do not know much about Angular, but couldn’t you use a standard ‘onchange’ event listener on the checkbox input to achieve what you need?

1 Like

Hey Chris,

I think what you’d want to do in this situation is set up a watch on the todo.done property and add the additional logic there.

1 Like

Thanks @Andres_Vaquero and @fretburner,

I’d come across onchange in my travels, but not watch. Having had a very quick look at the article linked to, I’ve a feeling that watch would be more in keeping with the idea of Angular being data driven, as opposed to event driven. I’ll give that a try once I have a moment and report back.

I think that may be what angular uses in the end anyway and it’s pretty simple e.g

var input = document.getElementById("myInput");
input.addEventListener("change", function(event) {
	if (input.checked) {
		// do something
	} else {
		// do something else
	}
});

or something like the following may work too (please note I have not tested it)

<input class="checkbox" onchange="myFunction(this)" type="checkbox" ng-model="todo.done" id="todo-{{$index}}">
<script>
var myFunction = function(input) {
	if (input.checked) {
		// do something
	} else {
		// do something else
	}
};
</script>
1 Like

I tried the onchange version, but it’s tripping me up at the moment with a reference error

Uncaught ReferenceError: todos is not defined
at HTMLInputElement.onchange ((index):1)
onchange @ (index):1

I’ve pasted the relevant code below. What am I missing?

###Todos controller

angular
  .module('todoApp')
  .controller('ToDosCtrl', ToDosCtrl);

ToDosCtrl.$inject = [`$stateParams`];
function ToDosCtrl($stateParams){
  const vm = this;

  vm.todos = [
    { task: 'Build an awesome todo app', done: false, createDte: new Date('2017-07-05T10:20:05Z'), dueDte: new Date('2017-07-31T23:59:59Z'), completedDte: null },
    { task: 'Get super good at Angular', done: false, createDte: new Date('2017-08-01T12:00:05Z'), dueDte: new Date('2017-08-30T23:59:59Z'), completedDte: null },
    { task: 'Party on code', done: false, createDte: new Date('2017-08-02T14:35:25Z'), dueDte: new Date('2017-08-30T23:59:59Z'), completedDte: null },
    { task: 'Tackle the bonus challenges for this lesson', done: false, createDte: new Date('2017-08-04T18:00:45Z'), dueDte: new Date('2017-08-30T23:59:59Z'), completedDte: null },
    { task: 'Take a nap', done: false, createDte: new Date('2017-08-05T12:00:00Z'), dueDte: new Date('2017-08-30T23:59:59Z'), completedDte: null }
  ];

  // RESTful actions
  vm.todosCreate    = todosCreate;
  vm.todosShow      = todosShow;
  vm.todosDelete    = todosDelete;

  // Other functions
  vm.todosCompleted = todosCompleted;
  vm.todosRemaining = todosRemaining;
  vm.updateCompletedDate = updateCompletedDate;

  // Function that allows us to add new todos to our todos
  function todosCreate(){
    vm.todos.push({ task: vm.text, done: false, createDte: new Date(), dueDte: vm.dueDate, completedDte: null });
    vm.text = null;
    vm.dueDate = null;
    // console.log(vm.todos);
  }

  function updateCompletedDate(todo) {
    if (todo.checked) {
		  // do something
  	} else {
  		// do something else
  	}
  }

  // Get one todo based on the $stateParams
  function todosShow() {
    return vm.todos[$stateParams.id];
  }

  // Function that allows us to delete specific todos from our todos
  function todosDelete(todo){
    vm.todos = vm.todos.filter(x => x.task !== todo.task);
  }

  // Returns a count of the tasks that have been marked as done
  function todosCompleted(){
    return vm.todos.filter(x => x.done === true);
  }

  // Returns a count of the tasks that have not been marked as done
  function todosRemaining(){
    return vm.todos.filter(x => x.done === false);
  }
}

###index.html

<!DOCTYPE html>
<html ng-app="todoApp" lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <title>To Do Or Not To Do</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <base href="/" />
  <!-- inject:js -->
  <!-- endinject -->
  <!-- inject:css -->
  <!-- endinject -->
</head>

<body ng-controller="ToDosCtrl as todos">
  <header>
    <h1>YOU'VE GOT {{ todos.todosRemaining().length }} THINGS TO DO!</h1>
    <h4>{{ todos.todosCompleted().length }} things completed | {{ todos.todosRemaining().length }} things remaining</h4>
  </header>
  <nav class="tabs">
    <a ui-sref-active="active" ui-sref="home">List</a>
    <a ui-sref-active="active" ui-sref="archive">Archive</a>
  </nav>
  <main ui-view></main>
</body>
</html>

###home.html

<!-- begin add new todo -->
<form class="add-todo" ng-submit="todos.todosCreate()">
  <fieldset>
    <h4>Add New Todo...</h4>
    <p>
      <input id="newTodoTxt" class="text-box" type="text" placeholder="I need to..." ng-model="todos.text">
    </p>
    <p>
      <label for="newTodoDate">Due Date (optional)</label>
      <input id="newTodoDate" class="date-box" type="date" ng-model="todos.dueDate">
      <input type="submit" class="btn btn-add" value="+">
    </p>
  </fieldset>
</form>
<!-- end add new todo -->

<!-- begin show all remaining todos -->
<section class="todo-list">
  <ul>
    <li ng-repeat="todo in todos.todosRemaining() track by $index">
      <input class="checkbox" type="checkbox" onchange="todos.updateCompletedDate(todo)" ng-model="todo.done" id="todo-{{$index}}">
      <label for="todo-{{$index}}" ng-class="{done: todo.done}">{{ todo.task }}</label>
      <a ui-sref="todosShow({ id: $index })">more info</a>
      <button class="btn btn-delete" ng-click="todos.todosDelete(todo)">x</button>
      <div class="todoDates"><em>Created:</em> {{ todo.createDte | date:'d MMM yyyy, HH:mm:ss' }}. <em>Due:</em> {{ todo.dueDte | date:'d MMM yyyy, HH:mm:ss' }}</div>
    </li>
  </ul>
</section>
<!-- end show all todos -->

Forget that one - I used onchange instead of ng-change - my mistake

Things eventually ended up as

<input class="checkbox" type="checkbox" ng-change="todos.updateCompletedDate(todo)" ng-model="todo.done" id="todo-{{$index}}">

…calling

  function updateCompletedDate(todo) {
    if (todo.completedDte) {
      // console.log('todo.checked returns true');
      todo.completedDte = null;
  	} else {
      // console.log('todo.checked returns false');
      todo.completedDte = new Date();
  	}
  }

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.