store key in a variable for each ng-repeat - angularjs

I am fairly new to angular js.
I have two different scope data in my controller ($scope.itemdata & $scope.skudata, both are simplified in the plucker). Both are guaranteed to have the same structure for any key.
I have a nested ng-repeat for item data and I want to store/keep a track of keys inside each ng-repeat so that I can use the path variable to get the value out of skudata.
Problem: {{path}} expression in HTML is not resolving to correct key value.
Looking at the console log the keys are added and removed in the correct order.
I am not sure where I am going wrong.
Here is plucker : http://plnkr.co/edit/0q8tZ9JLsGdfR03YbpYB?p=info
HTML Code:<body ng-controller="MainCtrl">
<p>Hello</p>
<script type="text/ng-template" id="field_renderer.html">
<div ng-init="addkeyToPath(key)"></div>
{{key}}
{{value}}
<!--output should be skudata.value but instead its skudata-->
{{path}}
<div ng-init="removeKeyFromPath(key)"></div>
{{path}}
</script>
<div>
<div ng-repeat="(key ,value) in itemData" ng-include="'field_renderer.html'"></div>
</div>
Controller Code:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.path = 'skudata';
$scope.isThisAnObject = function(input) {
return angular.isObject(input) ? true : false;
}
$scope.addkeyToPath = function(input) {
console.log("Adding");
console.log($scope.path);
$scope.path = $scope.path + "." + input;
console.log($scope.path);
};
$scope.removeKeyFromPath = function(input) {
console.log("remove");
console.log($scope.path);
$scope.path = $scope.path.substring(0,$scope.path.lastIndexOf("."));
console.log($scope.path);
};
$scope.itemData =
{ value:'item_name',
length:'10'
};
$scope.skudata =
{ value:'sku_name',
length:'20'
};
});

The problem is that you're using the $scope path variable, when really you want to display path variable of the item itself. Make a few changes to get this working plunkr
Template:
<div ng-init="addkeyToPath(this, key)">
{{key}}: {{value}}
{{path}}
</div>
Controller:
$scope.addkeyToPath = function(item, input) {
item.path = $scope.path + "." + input;
};
$scope.itemData = {
value:'item_name',
length:'10'
};

Related

Binding expression in ng-repeat

I have the list of items
<ul>
<li><a>ItemA</a></li>
<li><a>ItemB{{vm.count}}</a></li>
</ul>
I am preparing the list in angular in Controller:
vm.listofItems =[{"textDisplay":"ItemA","Statego":""},
{"textDisplay":"ItemB{{vm.count}}","Statego":""}];
After some service call I'll get vm.count value? how to Bind now.
Ok just try this code, First of all i will suggest you to use $scope. I have assigned your listOfItems in watch of count, it will work for you, whenever you change the value of $scope.count
$scope.count;
$scope.$watch('count', function() {
$scope.listofItems =[
{"textDisplay":"ItemA","Statego":""},
{"textDisplay":"ItemB" +$scope.count,"Statego":""}
];
});
In Html
<div ng-app="app">
<div ng-controller="MainCtrl as main">
<ul>
<li ng-repeat='list in main.listofItems'>
{{list.textDisplay}}
</li>
</ul>
<input ng-model='main.count' ng-value='main.count'> {{main.count}}
</div>
</div>
In controller
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
var vm = this;
vm.count = 90;
vm.listofItems = [{
"textDisplay": "ItemA",
"Statego": ""
}, {
"textDisplay": "ItemB " + vm.count + "",
"Statego": ""
}];
$scope.$watch(angular.bind(this, function() {
return this.count;
}), function(newVal) {
vm.listofItems[1].textDisplay = "ItemB " + newVal;
// console.log(' change ' , newVal);
});
});
This the solution I figured out.Is this approach is correct?
jsFiddle for the same: https://jsfiddle.net/nj5/ve4xmhtj/

AngularJS: ng-repeat track by obj.id doesn't reinitialise transcluded content when obj.id changes

function ComponentController() {
var vm = this;
this.$onInit = function() {
vm.link = 'http://example.com/obj/' + vm.obj.id;
}
}
function MainController($scope, $timeout) {
$scope.arrObjs = [{id: 1, name: "object1"},{id: 2, name: "object2"}];
console.log('object1\'s id is ', $scope.arrObjs[0].id);
$timeout(function() { // simulates a call to server that updates the id
$scope.arrObjs[0].id = '3';
console.log('object1\'s new id is ', $scope.arrObjs[0].id, '. Expected the link above to be updated with the new ID');
}, 1000);
}
var someComponent = {
bindings: {
obj: '='
},
template: '<div>URL: <span>{{$ctrl.link}}</span></div>',
controller: ComponentController
};
angular.module('myApp', []);
angular
.module('myApp')
.controller('MainController', MainController)
.controller('ComponentController', ComponentController)
.component('someComponent', someComponent);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MainController">
<div ng-repeat="obj in arrObjs track by obj.id">
<some-component obj="obj"></some-component>
</div>
</div>
</div>
After the ng-repeat and its someComponent are rendered one of the objects' obj.id changes (using a $timeout in the above example). The vm.link for that object still carries the old id!
Now, I know that the $onInit() is only run once inside someComponent, but why doesn't track by re-initialise the component because the obj.id changed?
If Angular truly tracked an array by the obj.ids, it should treat an obj whose id changes as a completely different object and re-initialise it, no?
Obviously a $watch on vm.obj.id within someComponent will fix it, but is there a way without adding yet another $watch?
NOTE: previously I was using
<div ng-repeat="objID in vm.arrObjIDs track by objID" ng-init="obj = vm.fnLookUpObjByID(objID)">
<someComponent obj="obj"></someComponent>
</div>
And that works perfectly! This is exactly how I expected the track by obj.id to work. But I'm trying to move away from the ng-init pattern.
You're missing something somewhere in your code that you're not showing us.
The following snippet works.
init() is not a standard function though. You probably mean $onInit()
function ComponentController() {
var vm = this;
console.log("not in init: " + this.obj.id);
this.$onInit = function() {
console.log("in init: " + vm.obj.id);
}
}
function MainController($scope) {
$scope.arrObjs = [{id: 1, name: "object1"},{id: 2, name: "object2"}];
}
var someComponent = {
bindings: {
obj: '='
},
template: '<div>ID: <span>{{$ctrl.obj.id}}</span></div>',
controller: ComponentController
};
angular.module('myApp', []);
angular
.module('myApp')
.controller('MainController', MainController)
.controller('ComponentController', ComponentController)
.component('someComponent', someComponent);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MainController">
<div ng-repeat="obj in arrObjs track by obj.id">
<some-component obj="obj"></some-component>
</div>
</div>
</div>
EDIT:
If you add an asynchronous function in the middle, the compiling of the code will always be faster than the return of the async function.
Using $watch is the standard way of updating the view when data changes.
There's not other way.
Note: With components you can use $onChanges() but in this particular case it won't trigger since you have to change the reference of the object for it to update. $onChanges() calls $watch in any case.

Factory value not updated in model ...what I am doing wrong?

I am new to angular-js. I have two controllers (welcomeContoller,productController) and both handling the same model within the factory.
When the model getting updating by one controller(productController) it should reflect the update in another controller. (welcomeContoller)
But its not happening now.
HTML code :
<body ng-app="myApp">
<div ng-controller="welcomeContoller">
{{totalProductCnt}}
</div>
<div ng-controller="productController">
<div class="addRemoveCart">
<span class="pull-left glyphicon glyphicon-minus" ng-click="removeProduct()"></span>
<span class="pull-right glyphicon glyphicon-plus" ng-click="addProduct(1)"></span>
</div>
</div>
JS code
var myApp = angular.module("myApp", ['ui.bootstrap']);
myApp.factory("productCountFactory", function() {
return {
totalProducts:0
};
});
myApp.controller("welcomeContoller", function($scope, productCountFactory)
{
$scope.totalProductCnt = productCountFactory.totalProducts;
});
myApp.controller("productController", function($scope, productCountFactory) {
$scope.addProduct = function() {
productCountFactory.totalProducts++;
alert(productCountFactory.totalProducts);
};
$scope.removeProduct = function() {
if(productCountFactory.totalProducts >=1)
productCountFactory.totalProducts--;
alert(productCountFactory.totalProducts);
};
});
Even after the addProduct is called the totalProductCnt is displaying as zero. I want to display the value for each increment.
Plunkr Link
Put the factory object reference on scope:
myApp.controller("welcomeContoller", function($scope, productCountFactory) {
$scope.productCountFactory = productCountFactory;
});
Watch the property of the object.
{{productCountFactory.totalProducts}}
The DEMO on PLNKR.
By putting a reference on scope, on every digest cycle the watcher looks up the value of the property and updates the DOM if there is a change.
The totalProductCnt from your welcomeController isn't updated because it is assigned only once when the controller is created.
You can use several solutions to refresh the displayed value. Use a getter for your totalProducts in the factory :
myApp.factory("productCountFactory", function() {
var totalProducts = 0;
return {
getTotalProducts: function() {
return totalProducts;
},
addProduct: function() {
totalProducts++;
},
removeProduct: function() {
totalProducts--;
}
};
});
myApp.controller("welcomeContoller", function($scope, productCountFactory) {
$scope.getTotalProducts = productCountFactory.getTotalProducts;
});
myApp.controller("productController", function($scope, productCountFactory) {
$scope.addProduct = function() {
productCountFactory.addProduct();
};
$scope.removeProduct = function() {
if (productCountFactory.getTotalProducts() >= 1)
productCountFactory.removeProduct();
};
});
And update the view accordingly:
<div ng-controller="welcomeContoller">
{{getTotalProducts()}}
</div>
Plunkr Link

passing ng-show between two different controllers

I have a button which falls into Controller B and two block of HTML code which kind of falls under controller A...............and button falls into one block of HTML code
Example:
<div ng-controller="A">
<div ng-show="now">
<div>
<Button ng-controller="B"></Button>
</div>
</div>
<div ng-show="later">
</div>
</div>
On one button click I show up now block and later on button click of B controller I kind of hide now block and display later block.
How do I achieve this functionality?? I am not able to pass ng-show varibales between two different controller files......what should I use???
Hope this helps...!
angular.module('app', [])
.controller('A', function($scope) {
console.log('A');
$scope.state = {
now: true
};
$scope.showLater = function() {
$scope.state.later = true;
};
})
.controller('B', function($scope) {
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="A" ng-app="app">
<div ng-show="state.now">
<div>
<button ng-controller="B" ng-click="showLater()">Show Later</button>
</div>
</div>
<div ng-show="state.later">LATER
</div>
<p> <pre ng-bind="state | json"></pre>
</p>
</div>
You could use a simple service that stores the state.
Example:
angular.module('mymodule').service('ActiveService', function() {
var service = {};
var status = false;
service.getStatus = function() {
return status;
}
service.toggle = function() {
status = !status;
}
return service;
})
And in your controller:
angular.module('mymodule').controller('SomeController', function(ActiveService) {
$scope.status = ActiveService.getStatus;
})
The Angularjs service is a singelton, so it will hold your values for you across different controllers, directives or pages.
Could also be used directly:
// Controller
$scope.service = ActiveService;
// Html
<div ng-show="service.getStatus()">
...
</div>
You can also achieve this by declaring the variable in $rootScope and watching it in controller A,
app.controller('A', function($scope, $rootScope) {
$rootScope.now = true;
$rootScope.later = false;
$rootScope.$watch("now", function() {
$scope.now = $rootScope.now;
$scope.later = !$rootScope.now;
})
});
In Controller B, you just change the value of now based on previous value like this on ng-click,
app.controller('B', function($scope, $rootScope) {
$scope.testBtn = function() {
$rootScope.now = !$rootScope.now;
}
});
I have implemented a button within different divs(now and later) in a plunker,
http://embed.plnkr.co/xtegii1vCqTxHO7sUNBU/preview
Hope this helps!

angularjs: loop through an array crossing values with another array in a view

I am struggling with the following. I have a global array1 with the values FileA, FileB, FileC, FileD and FileE. Then I have a specific array2 with the values FileA and FileC.
The output I would want is something like
<div class="matched">FileA</div>
<div class="not_matched">FileB</div>
<div class="matched">FileC</div>
<div class="not_matched">FileD</div>
<div class="not_matched">FileE</div>
I was thinking in a nested ng-repeat with a custom filter, but I am not able to see how to do it.
Here it is an attempt that is not even compiling
html
<body ng-app="myModule">
<div ng-controller="myController">
<div ng-repeat="entity in entities">
<div ng-repeat="myEntity in myEntities | lookInside(entity)">
{{myEntity.match}} - {{myEntity.name}}
</div>
</div>
</div>
</body>
and js
var myModule = angular.module('myModule', []);
myModule.controller('myController', ['$scope', function($scope) {
$scope.entities = ['fileA', 'fileB', 'fileC', 'fileD', 'fileE'];
$scope.myEntities = ['fileA', 'fileC'];
}]);
myModule.filter('lookInside', function(){
return function(items, name){
var arrayToReturn = [];
var name = {};
for (var i=0; i<items.length; i++){
name.match = 'no';
name.name = items[i];
if (items[i] == name) {
name.match = 'si';
}
arrayToReturn.push(name);
}
return arrayToReturn;
};
});
http://jsfiddle.net/C5gJr/46/
What's the best approach to follow here?
Cheers
UPDATE:
I've solved just by using a filter for each entry that checks if it is inside the array
<body ng-app="myModule">
<div ng-controller="myController">
<div ng-repeat="entity in entities">
{{entity | lookInside: myEntities}}
</div>
</div>
</body>
and js
var myModule = angular.module('myModule', []);
myModule.controller('myController', ['$scope', function($scope) {
$scope.entities = ['fileA', 'fileB', 'fileC', 'fileD', 'fileE'];
$scope.myEntities = ['fileA', 'fileC'];
}]);
myModule.filter('lookInside', function(){
return function(item, array){
var name = 'no';
for (var i=0; i<array.length; i++){
if (array[i] == item) {
name = 'si';
}
}
return name;
};
});
http://jsfiddle.net/C5gJr/48/
However, the impact in the performance of the data processing is very high (large lists of data). This may be unavoidable, but any comment on that is very well welcomed.
Cheers
If all you need to do is switch a class based on the other array, try using ng-class and a scope function to check the secondary array.
http://jsfiddle.net/VrB3H/
<div ng-repeat="entity in entities" ng-class="{'matched': isMatch(entity), 'not_matched': !isMatch(entity)}">
{{isMatch(entity)}} - {{entity}}
</div>
myModule.controller('myController', ['$scope', function($scope) {
$scope.entities = ['fileA', 'fileB', 'fileC', 'fileD', 'fileE'];
$scope.myEntities = ['fileA', 'fileC'];
$scope.isMatch = function(entity)
{
return $scope.myEntities.indexOf(entity) >= 0;
}
}]);
Updated in the question, solved in an easy_to_understand code (IMO), but with a high impact in the perfomance of my code (large lists of data). Any improvement in this sense is very well welcomed

Resources