Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create $scope functions that never reads $scope?

I want to create a $scope function that will only manipulate the variables that it receives.

I've made a working Plunker to test things out.

I have a ng-repeat that is just listing names and id of kitties. I also have an input form to receive a name of a new kitty. Then, I have 3 buttons, each one accessing a different $scope function that will manipulate the $scope in different ways.

The goal of every function is to read the name of the kitty from the input form, read the last id used on the kitties array, assign that id+1 to the new kitty, push the new kitty to the array of kitties and delete the form data.

  • The first function, $scope.addFullScope(), will receive nothing as arguments and will read and manipulate everything from $scope.

  • The second function, $scope.addJustDelete(kitty, kitties), will receive both the new kitty and the kitties array as argument. But, on the end, to clean up the form, it will do $scope.kitty = undefined

  • The third function, $scope.addNoScope(kitty, kitties), will receive both the new kitty and the kitties array as argument. But, on the end, to clean up the form, it will do kitty = undefined. But the form won't clean up! and everything will star to bug out.

How can I make this third function, or something like it, work so I have a fully independent function to work with?

Appendix:

Html:

  <body ng-app='app' ng-controller="ctrl">

    <h3 ng-repeat="kitty in kitties">
      {{kitty.name}}: {{kitty.id}} //Kitties list
    </h3>

    <input placeholder='Kitty name to add' class='form form-control' 
           type="text" ng-model="kitty.name" />

    <h3> $scope use on adding kitty:</h3>
    <button ng-click="addFullScope()">Full Scope.</button>
    <button ng-click="addJustDelete(kitty, kitties)">Just delete.</button>
    <button ng-click="addNoScope(kitty, kitties)">None. Buggy</button>
  </body>

Controller:

.controller('ctrl', function($scope) {
  $scope.kitties = [
    //Let's imagine kitties in here.
    {name: 'Purple kitty', id:35},
    //Kittie 36 died in a car accident. :(
    {name: 'Rodmentou cat', id: 37},
    {name: 'Fatty kitty', id: 38}

    ];

  $scope.addFullScope = function () {
    var size = $scope.kitties.length;
    var id = $scope.kitties[size-1].id + 1;

    $scope.kitty.id = id;
    $scope.kitties.push($scope.kitty);
    $scope.kitty = undefined;
  };

  $scope.addJustDelete = function (kitty, kitties) {
    var size = kitties.length;
    var id = kitties[size-1].id + 1;

    kitty.id = id;
    kitties.push(kitty);
    $scope.kitty= undefined;

  };

  $scope.addNoScope = function (kitty, kitties) {
    var size = kitties.length;
    var id = kitties[size-1].id + 1;

    kitty.id = id;
    kitties.push(kitty);
    kitty = undefined; //Don't work
  };



});
like image 278
Rodmentou Avatar asked Dec 06 '22 19:12

Rodmentou


1 Answers

Under the hood Angular needs a way to watch if a ngModel is changed and uses $scope.$watch to accomplish this. $watch is considered a Equality Watcher, where it checks if one specific property has changed and not if the object underlying it has changed, which would be a Reference Watcher.

I found a good description and image of this on Tero Parviainen's blog

enter image description here

So the issue with your code is that you are setting kitty = undefined, but ngModel and by extension $scope.$watch is looking for a change in kitty.name. To prove this was the case I added a watch on kitty.name and when you set kitty = undefined the watch never gets triggered. Setting kitty to undefined changes the underlying object, but not it's properties (pretty confusing)! I created a plunker example with updated code.

HTML

For clarity sake, I made the model we are updating kitten so it is not confused with the kitties we are iterating over.

<h3 ng-repeat="kitty in kitties">
  {{kitty.name}}: {{kitty.id}}
</h3>
<input placeholder='Kitty name to add' class='form form-control' type="text" ng-model="kitten.name" />
<h3> $scope use on adding kitty:</h3>
<button class='btn btn-success' ng-click="addNoScope(kitten, kitties)">None. Buggy</button>
<button class='btn btn-danger' ng-click="noWatch(kitten)">Full Scope.</button>

Javascript

The big update is creating a clone kitten out of the model's kitten, so that it is not referenced within the array. Also when we want to reset the model's kitten we set the kitten.name and kitten.id properties directly.

angular.module('app', [])
.controller('ctrl', function($scope) {

  $scope.kitten = {
    name: undefined, id: undefined
  };

  $scope.$watch('kitten.name', function() { console.log("changed"); }, true);

  $scope.kitties = [
    //Let's imagine kitties in here.
    {name: 'Purple kitty', id:35},
    //Kittie 36 died in a car accident. :(
    {name: 'Rodmentou cat', id: 37},
    {name: 'Fatty kitty', id: 38}

  ];

  $scope.addNoScope = function (kitten, kitties) {
    if (!$scope.kitten || !$scope.kitten.name) return;
    var size = kitties.length;
    var id = kitties[size-1].id + 1;
    var newKitten = {
      name: kitten.name, id: id
    }
    kitties.push(newKitten);
    kitten.name = undefined;
    kitten.id = undefined;
    // kitten = undefined;
  };

  $scope.noWatch = function (kitten) {
    kitten = undefined;
    console.log('not watching');
  };

});
like image 94
jjbskir Avatar answered Jan 07 '23 22:01

jjbskir