AngularJS watch service value change instead of scope inheritance - angularjs

I just give a try to AngularJS. I try to do something quite simple but I'd like to do it the good way.
I got a list of items in a table which displays name and quantity for each item.
I have a form under the table.
When I click on an item name from the table I'd like the given item to be updatable through the form.
I achieve to do thing with scope inheritance as in fiddle http://jsfiddle.net/5cRte/1/
View :
<tr ng-repeat="item in items">
<td>{{item.name}}</td>
<td>{{item.quantity}}</td>
</tr>
Controllers :
function ItemListController($scope){
$scope.items = [{name:'item1', quantity:10}, {name:'item2', quantity:5}];
$scope.selectCurrentItem = function(currentItem) {
$scope.currentItem = currentItem;
}
}
function ItemFormController($scope){
$scope.$watch('currentItem', function() {
$scope.item = $scope.currentItem;
});
}
But has I read in some topics, it is not a good practice to couple controllers scopes this way, and preferably I'll wan't to use a service to store variables shared between controllers.
I was able to put a static variable in a service and retrieve it in another controller, but I can't make it updated when clicking on the item from the table, as watch not working on services variable. Have you an hint, for this ?
Thanks in advance

I don't know whether this is optimal but this what I could come up with
angular.module('myApp', []);
angular.module('myApp').factory('myService', function(){
var items = [{name:'item1', quantity:10}, {name:'item2', quantity:5}, {name:'item3', quantity:50}];
var current = {};
return {
getItems: function(){
return items;
},
setCurrentItem: function(item){
current.item = item;
},
removeCurrentItem: function(){
delete current.item;
},
getCurrent: function(){
return current;
}
}
});
function ItemListController($scope, myService){
$scope.items = myService.getItems();
$scope.selectCurrentItem = function(currentItem) {
myService.setCurrentItem(currentItem);
}
}
function ItemFormController($scope, myService){
$scope.current = myService.getCurrent();
}
Demo: Fiddle

Related

How to transfer data of ng-repeat items by clicking from one view to another view if both views have only one controller?

I have two views(2 html templates) both have one common controller
now in view 1 which contains ng-repeat items
for example :
In "view 1" : ng-repeat = "monthid in years"
it shows:
month 1,
month 2,
...
month 12
So now i want to click on "month 1" and send its value suppose its name like "january" to "view 2"
In view 2, there will be only one thing just month 1's name(january).
Like this,
If i click on "month 2" there will be only one thing just month 2's name (February) in view 2.
How to achieve this?
I can see three solutions to share data between controllers:
Store your variable in a service and inject your service in both controller.
Use events (i.e $broadcast, $on methods) to notify controllers.
Use scope inheritance and store your variable in a parent controller. This should be avoided most of the time since refactoring and unit tests may be hard.
In my opinion, the best way to share variables between two controllers is to use a service (simple, easy to test):
Store the month in your service.
Inject the service in both controller.
Here is an example:
angular.module('app', [])
.service('filterService', function() {
var _year;
this.getYear = function() {
return _year;
};
this.setYear = function(year) {
_year = year;
};
})
.controller('Ctrl1', function($scope, filterService) {
$scope.chooseYear = function(year) {
filterService.setYear(year);
};
})
.controller('Ctrl2', function($scope, filterService) {
$scope.year = filterService.getYear();
$scope.$watch(filterService.getYear, function(newValue) {
$scope.year = newValue;
});
});
Note that if you can use Object.defineProperty method (available with IE >= 9), your controllers can be refactored to use $scope.year everywhere (and you can remove the call to the $watch mhttps://plnkr.co/edit/G2pVFMV5Z2yXYSCkgZYs?p=infoethod):
angular.module('app', [])
.service('filterService', function() {
var _year;
this.getYear = function() {
return _year;
};
this.setYear = function(year) {
_year = year;
};
})
.controller('Ctrl1', function($scope, filterService) {
Object.defineProperty($scope, 'year', {
get: filterService.getYear,
set: filterService.setYear
});
})
.controller('Ctrl2', function($scope, filterService) {
Object.defineProperty($scope, 'year', {
get: filterService.getYear,
set: filterService.setYear
});
});
Here is a plunkr if you want to test : https://plnkr.co/edit/G2pVFMV5Z2yXYSCkgZYs?p=info

angular scoped variable in controller not updating when value changed in service

I'm developing a mini-basket in angular for an ecommerce application but have a problem with a scoped variable not updating via a service.
When i click on an add to basket button in the product grid it fires the upDateMiniBasket function in the product grid controller which has the UpdateMiniBasket service injected into it.
The controller:
whiskyControllers.controller('whiskyListCtrlr', ['$scope', 'UpdateMiniBasket', '$http',
function($scope, UpdateMiniBasket, $http){
$http.get('json/whiskies.json').success(function(data){
$scope.whiskies = data;
})
$scope.updateMiniBasket = function(e){
var targetObj = e.target.getAttribute('data-item');
UpdateMiniBasket.getUpdate(targetObj);
}
}
])
Here is the service:
whiskyrumApp.factory('UpdateMiniBasket', [function(){
var miniBasketTotal = 0,
itemCount = 0;
var miniBasketItems = [{
imageUrl : '',
name : 'There are currently no items in your basket',
price: 0
}]
var getUpdate = function(obj){
miniBasketTotal = 0;
if(obj) {
if(miniBasketItems[0].price === 0) miniBasketItems.pop();
obj = jQuery.parseJSON(obj);
miniBasketItems.push(obj);
}
for(var i = 0, j = miniBasketItems.length; i < j; i++){
miniBasketTotal += parseFloat(miniBasketItems[i].price);
}
itemCount = miniBasketItems[0].price === 0 ? 0 : miniBasketItems.length;
return {
miniBasketItems : miniBasketItems,
miniBasketTotal : miniBasketTotal,
itemCount : itemCount
}
}
return {
getUpdate : getUpdate
}
}]);
The problem is that when I add a product, the function fires and calls the service ok, but a scoped variable that should update the amount of items in the basket is not updating. This variable lives in another controller for the minibasket that also has teh UpdateMiniBasket service injected.
whiskyControllers.controller('miniBasketCtrlr', ['$scope', 'UpdateMiniBasket',
function($scope, UpdateMiniBasket){
var mbItems = UpdateMiniBasket.getUpdate();
$scope.miniBasketItems = mbItems.miniBasketItems;
$scope.itemCount = mbItems.itemCount;
}])
And this is html:
<div class="mini-basket grey-box" ng-controller="miniBasketCtrlr">
<a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
{{itemCount}} Items
</a>
<!-- the ng-repeat code for the mini basket which works fine -->
I just assumed that when the variables in the service are updated, that would feed through to the scoped var in the other controller as they are both linked to the same service. I thought maybe I need to add a $watch as I cant see why this {{itemCount}} is not updating.
Any help would be greatly appreciated.
If you change the return object of your UpdateMiniBasket factory to instead of returning the primitive value for the count of items but to a function like below:
return {
miniBasketItems : miniBasketItems,
miniBasketTotal : miniBasketTotal,
itemCount : function () {
return itemCount;
}
}
And then in your controller change the binding to $scope to this:
$scope.itemCount = function () {
return mbItems.itemCount();
};
You should then just have to change the binding in the html to {{ itemCount() }} and this value should then successfully update when the values in the factory update.
I have a simplified solution to this on jsbin here: http://jsbin.com/guxexowedi/2/edit?html,js,output

using comparator filter in list filter

I'm a bit stuck trying to create a filter that can be used in a ng-repeat as well as in the controller.
I've created some controller filters of the form
$scope.myfilter = function($item){
if($item == "blah"){
return true;
}
return false;
}
but discovered the ones you attach to the module, that accept lists instead.
angular.module('filters',[])
filter(function(){
return function(list){
//filter code goes here.
}
});
obviously it can run the comparator versions on lists since it does it for ng-repeats. Is there a easy way to run the comparator filters inside the list based ones? I'm a bit confused as to when you would use one over the other?
Pplease see here http://jsbin.com/wajecu/2/edit
more documentation you can find here https://docs.angularjs.org/tutorial/step_09
filter:
app.filter('myfilter', function() {
return function(input) {
var filtered = [];
angular.forEach(input, function (value){
if(value == "blah"){ filtered.push(value); }
});
return filtered;
};
});
Controller:
app.controller('firstCtrl', function($scope,$filter){
$scope.items = ["blah", "ech", "mech"];
//you can use your filter inside controller in that way:
$scope.filtered= $filter('myfilter')($scope.items);
});
html:
<p ng-repeat="item in items | myfilter ">{{item}}</p>

AngularJS: Trying to make filter work with delayed data

I have this html:
<div ng-app='myApp'>
<div ng-controller='testCtrl'>
<h3>All possesions</h3>
{{possessions}}
<h3>Green cars</h3>
{{greenishCars}}
</div>
</div>
And this script:
angular.module('myApp', [])
.factory('getPossessionsService', ['$timeout',
function($timeout) {
var possessions = {};
$timeout(function() {
possessions.cars = [{
model: "Mazda 6",
color: "lime green"
}, {
model: "Audi A3",
color: "red"
}, {
model: "Audi TT",
color: "green"
}, {
model: "Volkswagen Lupo",
color: "forest green"
}];
possessions.jewelry = [{
type: "ring",
metal: "gold"
}, {
type: "earring",
metal: "silver"
}];
}, 1000);
return possessions;
}
])
.controller('testCtrl', ['$scope', 'filterFilter', 'getPossessionsService',
function($scope, filterFilter, getPossessionsService) {
$scope.possessions = getPossessionsService;
$scope.greenishCars = filterFilter($scope.possessions.cars, carColorIsGreenShade);
function carColorIsGreenShade(car) {
return ['green', 'forest green', 'lime green'].indexOf(car.color) != -1;
}
}
]);
I am trying to get $scope.greenishCars to update correctly when the data is available, but it is not. I understand that this has because $scope.possessions.cars is an array and therefore not a reference to the data, so it is not updated. But how should I alter my script so that greenishCars get updated when the data arrives? I am guessing I should use $scope.possessions "directly", but I do not quite see how I should rewrite this nicely....
See this plunk.
Edit: Thoughts on which answer to choose
As in comments in the answers to Oliver and MajoB, I ended up with using both filter and watch. In my special case the request for the data was made in another place (not in the controller of my page in question), so it was not so easy to act on the resolving of the promise (with promise.then as suggested by Oliver), I therefore used a watch. But there is a couple of things to be aware of with watches. If the variable you want to watch is not on the scope, then you must provide the variable by returning it from a function. And if you want to watch for a change in an existing property (say somebody repaints my existing Mazda 6 in a different color), then none of the watch-answers works, as you need to add "true" when calling $watch. When you add 'true' as the second paramenter to $watch, then it watches for changes in the actual values of the variable, and it also does this check for values deeply in an object/array (without it just checks references, see this blog). I ended up with this controller/$watch for my real-life use-case (which is a little bit different that in my example above, and is based on Olivers filter-plunk), and it looks like this (changed to fit the plunk):
.controller('testCtrl', ['$scope', 'greenFilter', 'getPossessionsService',
function($scope, greenFilter, getPossessionsService) {
var none-scope-possessions = getPossessionsService; // I do not want to expose all properties on the scope....
$scope.greenishCars = [];
$scope.$watch(function() {return none-scope-possessions.cars}, function(newValue){
if (newValue) {
$scope.greenishCars = greenFilter(newValue);
}
},true);
}
]);
You can watch the data changes:
$scope.$watch('possessions.cars', function(){
$scope.greenishCars = filterFilter($scope.possessions.cars, carColorIsGreenShade);
});
I would recommend just building your own custom filter like so:
.filter('green', function() {
return function(possessions) {
if (!possessions) return null;
var filtered = [];
angular.forEach(possessions, function(possesion){
if (['green', 'forest green', 'lime green'].indexOf(possesion.color) != -1) {
filtered.push(possesion);
}
});
return filtered;
};
})
Then pass your collection through that filter like so:
<div ng-controller='testCtrl'>
<h3>All possesions</h3>
{{possessions}}
<h3>Green cars</h3>
{{possessions.cars|green}}
</div>
Everything else will happen automatically. See the updated plunkr.
You can watch possesions collection for changes:
$scope.$watchCollection('possessions', function (newValue) {
if (newValue)
{
$scope.greenishCars = filterFilter(newValue.cars, carColorIsGreenShade);
}
});
http://plnkr.co/edit/Sl6dW0pqKr593OXrgDb9?p=preview
First thing is , var possessions = {}; you have declared an object , cars is an field in this object , which has array of cars . so either return possessions.cars or in the markup call it via possessions.cars[0].. if you want to call only one value ..or if you want to display all the values the use ng-repeat=" car in possessions.cars" .

AngularJS: accessing/printing a property of a related collection

I've defined two collections in Angular:
app.controller('MainCtrl', function($scope, PieceService, TeamService) {
PieceService.query(function(data){
$scope.pieces = data;
});
TeamService.query(function(data){
$scope.teams = data;
});
});
pieces is a collection that looks like this: [{name:'Petra',team_id:2},{name:'Jan',team_id:3}]
team is a collection that looks like this: [{name:'TeamA',id:2},{name:'Team',id:3}]
In the template I am iterating over the pieces collection:
<tr data-ng-repeat="piece in pieces">
How can I print the teamname of each piece?
I've tried creating a filter that does this, but as I don't see how I can access the scope in a filter I'm without luck so far.
Not sure if this is the sexiest angular way to do it, but I would create a function inside your controller to do the lookup. I also decided to cache the team_id to name lookup, but I realize it's not really necessary for a 2x2 search.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.pieces = [{name:'Petra',team_id:2},{name:'Jan',team_id:3}];
$scope.teams = [{name:'TeamA',id:2},{name:'TeamB',id:3}];
var idcache = {};
$scope.getTeam = function(id) {
if(idcache[id]) {
return idcache[id];
}
//cache this
$scope.teams.forEach(function(team) {
idcache[team.id] = team.name;
});
return idcache[id];
}
});
I've created a plunkr with the example code.
http://plnkr.co/edit/DsafIfMfARurNeVcl9Qd?p=preview
Use team ID as an object key, like this:
$scope.teamLookup = {}
// do this in the query callback
angular.forEach($scope.teams, function(val, key){
$scope.teamLookup[val.id] = val.name
});
Then, your markup can look like this:
<tr data-ng-repeat="piece in pieces">
<td>{{teamLookup[piece.team_id]}}</td>
<td>{{piece.name}}</td>
</tr>

Resources