How to remove from an array in an angular directive - javascript

I have some code which is working to add and remove entries to and from arrays in my scope. Right now the code isn't reused but rather cut/pasted and tweaked. Also, it rather naughtily uses scope inheritance to access the array. I'm trying to create a directive that will fix these two problems. The directive works fine as long as I add entries to the array. As soon as I remove an entry I appear to break the bi-directional binding. Any clues as to how I should go about this?
Fiddle is here.
It shows the SkillsCtrl which is the old code, and ListEditCtrl which is the new (reproduced below from the fiddle). Adding an entry to either list will update both but removing an entry from either list breaks the binding.
function SkillsCtrl($scope) {
$scope.addSkill = function () {
$scope.profile.skills = $scope.profile.skills || [];
$scope.profile.skills.push($scope.newskill);
$scope.newskill = "";
};
$scope.removeSkill = function () {
$scope.profile.skills = _.without($scope.profile.skills, this.skill);
};
}
function ListEditorCtrl($scope) {
$scope.addItem = function () {
$scope.list = $scope.list || [];
$scope.list.push($scope.newitem);
$scope.newitem = "";
};
$scope.removeItem = function () {
$scope.list = _.without($scope.list, this.item);
};
}

It's because you use http://underscorejs.org/#without, which creates a copy of the array instead of just removing the item. When you remove an item a new array will be linked to the scope, and the new array is not linked with array in the isolate scope.
To solve this problem you can use splice instead, which removes the item from the original array:
$scope.removeSkill = function() {
$scope.profile.skills.splice(_.indexOf($scope.profile.skills, this.skill),1);
};
...
$scope.removeItem = function() {
$scope.list.splice(_.indexOf($scope.list, this.item),1);
};
Updated plunker: http://jsfiddle.net/jtjf2/

Related

Displaying returned list from a function which loops through a viewModel

I've got a function that takes a container and loops through its objects to perform a search and retrieve an array of the found items.
The ViewModel has a container passed like such e.g.:
function ShowRoomBuildingsViewModel() {
this.LoadModel = function (container) {
for (var u in container.Payload.ShowRoom) {
if (container.Payload.ShowRoom[u].Inventory!== undefined)
this.Content.push(container.Payload.ShowRoom[u].HVehicle, container.Payload.ShowRoom[u].Cars);
}
// Definitions
this.Inventory= ko.observableDictionary();
this.Content= ko.observableArray();
I'm trying to create an ObservableArray object to loop through in my front end using <!-- ko foreach -->. The below code should retrieve either one result or multiple.
this.Content = function (container) {
var concatedList;
Content.forEach(element, elementIndex => {
var Content1 = container.Payload.Inventory.HVehicle.find(el => el.ID == element.ID);
if (Content1 != undefined) {
concatedList[elementIndex] = element;
concatedList[elementIndex].push(Content1);
}
});
return concatedList;
};
I've tried putting breakpoints to see what is happening inside, but it doesn't go in. I'm not sure if this is the correct approach or if it even works, but it is not showing any errors. In the first loop, where it populates Content, I can see that there are five results.
Any suggestions would be much appreciated.

AngularJS: about instantiating the service

I've got a controller:
.controller('myCtrl', function (myService) {
var that = this;
that.myService = myService;
});
a service:
.service('myService', function (DataFactory) {
var listData = [];
var mockList = ['aaa','bbb','ccc'];
var handleData = function (data) {
listData = data;
};
and html:
<button ng-click="myCtrl.myService.handleData()">Go</button>
<div ng-repeat="item in myCtrl.myService.listData>
{{ item }}
</div>
The thing is: I've got a list which contains data from myService.listData, and (with all code above) it works as expected - by updating list on button click.
I've add:
that.list = myService.listData
and changed
item in myCtrl.myService.listData to item in myCtrl.list
and the whole thing stopped working.
Could you explain that to me? I feel like I'm missing some really basic stuff and I need the direction to start searching more info.
plunks:
working one
broken
Here's a working plunker.
Your misconception was that you expect that.myService to be updated when underlying list is populated on click by handleData().
However, look at your handleData() implementation:
listData = data;
You don't mutate the initial list, you're pointing to another one (therefore referencing a distinct list) since it acts as a complete re-assignement!
So one way to solve the problem would be to mutate the initial list:
var handleData = function (data) {
// populating elements into the initial list
Array.prototype.push.apply(listData, mockedList);
};

How to make checkboxes work with knockout

I found this example from Ryan Niemeyer and started to manipulate it into the way I write my own code, but then it stopped working. Can anybody tell me why?
Alternative 1 is my variant
Alternative 2 is based upon Ryans solution and does work (just comment/uncomment the Applybindings).
Why doesn´t Alternative 1 work?
My issue is with the filteredRows:
self.filteredRows = ko.dependentObservable(function() {
//build a quick index from the flags array to avoid looping for each item
var statusIndex = {};
ko.utils.arrayForEach(this.flags(), function(flag) {
statusIndex[flag] = true;
});
//return a filtered list
var result = ko.utils.arrayFilter(this.Textbatches(), function(text) {
//check for a matching genré
return ko.utils.arrayFirst(text.genre(), function(genre) {
return statusIndex[genre];
});
return false;
});
console.log("result", result);
return result;
});
I want to filter my Textbatches on the genre-attribute (string in db and the data collected from the db is a string and not an array/object)
Fiddle here: http://jsfiddle.net/gsey786h/6/
You have various problems, most of them can be simply fixed with checking your browser's JavaScript console and reading the exceptions...
So here is the list what you need to fix:
You have miss-typed Textbatches in the declaration so the correct is self.Textbatches = ko.observableArray();
You have scoping problem in filteredRows with the this. So if you are using self you should stick to it and use that:
this.flags() should be self.flags()
this.Textbatches() should be self.Textbatches()
Your genre property has to be an array if you want to use it in ko.utils.arrayFirst
Finally your Textbatch takes individual parameters but you are calling it with an object, so you need to change it to look like:
Textbatch = function(data) {
var self = this;
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.statuses = ko.observableArray(data.status);
self.genre = ko.observableArray(data.genre);
self.createdBy = ko.observable(data.createdBy);
};
or you can of course change the calling places to use individual arguments instead of an object.
Here is a working JSFiddle containing all the above mentioned fixes.

Update ordered list when array changes in Javascript

So I've used Backbone and Angular quite a bit and gotten used to data binding / view updating within those ecosystems, but I don't know how I would achieve this in plain JS (no frameworks/libraries).
Right now I have a simple UserList, and I would like to watch for changes to it and trigger and update of an unordered list when it happens.
var ContactList = {
list: [],
push: function(obj) {
this.storage.push(obj);
},
remove: functon(obj) {
return this.storage.splice(this.storage.indexOf(obj), 1);
}
};
var Contact = function(attributes) {
this.attributes = attributes || {};
};
Contact.prototype.get = function(property) {
return this.attributes[property];
};
Contact.prototype.set = function(property, value) {
this.attributes[property] = value;
};
Ideally the following would automatically add to a list. I could just add a callback to the push and remove methods, but that seems like it doesn't scale very well if I get to a point where I'm adding more methods to operate on my list. I've been reading a bit about the observer pattern, but not sure if that's really what I'm looking for here.
You don't want to pass the callback to every call of ContactList.push and ContactList.remove and all the ContactList methods you are yet to write. Instead, the ContactList will know when he has changed and then announce that fact to the world. In a simple implementation, ContactList could have his own onChange method which he could call:
var ContactList = {
list: [],
push: function (obj) {
this.list.push(obj);
this.onChange();
},
remove: function (obj) {
var index = this.list.indexOf(obj);
if (index > -1) {
var removed = this.list.splice(index, 1);
this.onChange();
return removed;
} else {
return null;
}
}
};
You would then, obviously, have to define ConactList.onChange:
ContactList.onChange = function () {
console.log(this.list);
/* update your ul element */
};
This solution will not allow you to add subscribers dynamically to Contact List's change event, but it might be a helpful starting point.

Issues binding / watching service variable shared between two controllers

I am having a really hard time deciphering what is going on here. I understand the basics of Angular's $digest cycle, and according to this SO post, I am doing things correctly by simply assigning a scoped var to a service's property (an array in this case). As you can see the only way I can get CtrlA's 'things' to update is by re-assigning it after I've updated my service's property with a reference to a new array.
Here is a fiddle which illustrates my issue:
http://jsfiddle.net/tehsuck/Mujun/
(function () {
angular.module('testApp', [])
.factory('TestService', function ($http) {
var service = {
things: [],
setThings: function (newThings) {
service.things = newThings;
}
};
return service;
})
.controller('CtrlA', function ($scope, $timeout, TestService) {
$scope.things = TestService.things;
$scope.$watch('things.length', function (n, o) {
if (n !== o) {
alert('Things have changed in CtrlA');
}
});
$timeout(function () {
TestService.setThings(['a', 'b', 'c']);
// Without the next line, CtrlA acts like CtrlB in that
// it's $scope.things doesn't receive an update
$scope.things = TestService.things;
}, 2000);
})
.controller('CtrlB', function ($scope, TestService) {
$scope.things = TestService.things;
$scope.$watch('things.length', function (n, o) {
if (n !== o) {
// never alerts
alert('Things have changed in CtrlB');
}
});
})
})();
There are two issues with your code:
Arrays don't have a count property; you should use length instead.
$scope.$watch('things.length', ...);
But there's a caveat: if you add and remove elements to/from the things array and end up with a different list with the same length then the watcher callback won't get triggered.
The setThings method of TestService replaces the reference to the things array with a new one, making TestService.things point to a new array in memory while both CtrlA.$scope.things and CtrlB.$scope.things remain pointing to the old array, which is empty. The following code illustrates that:
var a = [];
var b = a;
a = [1, 2, 3];
console.log(a); // prints [1, 2, 3];
console.log(b); // prints [];
So in order for you code to work you need to change the way TestService.setThings updates its things array. Here's a suggestion:
setThings: function (newThings) {
service.things.length = 0; // empties the array
newThings.forEach(function(thing) {
service.things.push(thing);
});
}
And here's a working version of your jsFiddle.
I don't really know why, but it seems to be corrected if you use a function to return the data in your service, and then you watch that function instead of the property. As it seems unclear, you can see it here : http://jsfiddle.net/DotDotDot/Mujun/10/
I added a getter in your service :
var service = {
things: [],
setThings: function (newThings) {
service.things = newThings;
},
getThings:function(){
return service.things;
}
};
then, I modified your code in both controller by this :
$scope.things = TestService.getThings();
$scope.getThings=function(){return TestService.getThings();};
$scope.$watch('getThings()', function (n, o) {
if (n !== o) {
// never alerts
alert('Things have changed in CtrlA');
}
}, true);
and in the HTML :
<li ng-repeat="thing in getThings()">{{thing}}</li>
It defines a function getThings, which will simply get the property in your service, then I watch this function (AFAIK $watch do an eval on the parameter, so you can watch functions), with a deep inspection ( the true parameter at the end). Same thing in your other controller. Then, when you modifies the value of your service, it is seen by the two $watchers, and the data is binded correctly
Actually, I don't know if it's the best method, but it seems to work with your example, so I think you can look in this way
Have fun :)

Categories