AngularJS change variable inside an ng-repeat - javascript

I'm trying to set a variable depending on the button clicked.
Here's my code:
'use strict'
angular.module('myApp')
.controller('AlineacionCtrl', function ($scope, $meteor) {
$scope.activeIndex = {index: 0};
$meteor.subscribe('kits').then(function (){
$scope.kits = $meteor.collection(Kits, false);
$scope.activeCategory = $scope.kits[0].name;
console.log($scope.activeCategory);
$scope.log = function (){
console.log($scope.activeCategory);
};
});
});
.
<section layout="row" layout-align="center center" layout-wrap ng-init="activeIndex; activeCategory">
<md-button flex="auto" flex-sm="45" flex-xs="100" ng-repeat="kit in kits | orderBy: 'order'" ng-class="{active: (activeIndex.index == $index)}" class="md-raised">
{{kit.name}}
</md-button>
</section>
ng-click="activeIndex.index = $index; activeCategory = kit.name"; log()
I'm trying to set activeCategory to be the current clicked button kit.name but everytime the log() functions logs the first kit.name and doesn't change.
What am I doing wrong here?
Thanks!

ng-repeat creates a own scope. that's why when you do
activeCategory = kit.name;
you do not actually change $scope.activeCategory, but the variable activeCategory on the sub-scope of ng-repeat.
this way $scope.activeCategory never actually gets changed, hence it will always return the first entry.
what you have to do is do use a "dotted" variable to avoid this problem.
this is actually encouraged by google all the time.
try something like this:
angular.module('myApp')
.controller('AlineacionCtrl', function ($scope, $meteor) {
$scope.activeIndex = {index: 0};
$scope.activeCategory = { category: undefined };
$meteor.subscribe('kits').then(function (){
$scope.kits = $meteor.collection(Kits, false);
$scope.activeCategory.category = $scope.kits[0].name;
console.log($scope.activeCategory.category);
$scope.log = function (){
console.log($scope.activeCategory.category);
};
});
});
and
<section layout="row" layout-align="center center" layout-wrap ng-init="activeIndex; activeCategory">
<md-button flex="auto" flex-sm="45" flex-xs="100" ng-repeat="kit in kits | orderBy: 'order'" ng-class="{active: (activeIndex.index == $index)}" class="md-raised">
{{kit.name}}
</md-button>
</section>
see a post about this problem here:
Why don't the AngularJS docs use a dot in the model directive?
and a description of why it occurs with ng-model here:
http://excellencenodejsblog.com/angularjs-directive-child-scope-ng-repeat-ng-model/

Related

ng-if should show only one object

Hi dear Stackoverflow Community i have a problem. First here is my Code:
html:
<md-card md-theme-watch flex="100" ng-repeat="offer in company.offers">
<md-button class="md-fab md-mini md-primary md-fab-oi" aria-label="copy" ng-click="company.setEditVisibility()">
<oi-offer-edit offer="offer" is-change="true" ng-if="company.isEditVisible">
</oi-offer-edit>
</md-card>
My controller:
function setEditVisibility(){
vm.isEditVisible = !vm.isEditVisible;
}
it work just fine the problem is that it shows oi-offer-edit directive for every repeated Object.
If you need more info pls dont hesitate to ask!
If you don't want to touch your markup and want the oi-offer-edit element to be repeated, you have to use a boolean property on the offer object itself:
<md-card md-theme-watch flex="100" ng-repeat="offer in company.offers">
<md-button class="..." ng-click="offer.formVisible = !offer.formVisible">
<oi-offer-edit offer="offer" is-change="true" ng-if="offer.formVisible">
</oi-offer-edit>
</md-card>
Solution before i realized, that you want to have that directive in every md-card:
You might want to place your oi-offer-edit element outside your ng-repeat container, because as far as i see it in your snippet, you only need one with the offer-data of the selected company.offers.
So you could just cache the offer on the click handler and make your oi-offer-edit visible. Something like this:
<md-card md-theme-watch flex="100" ng-repeat="offer in company.offers">
<md-button class="..." ng-click="company.setEditVisibility(offer)">
</md-card>
<oi-offer-edit offer="currentSelectedOffer" is-change="true" ng-if="company.isEditVisible">
</oi-offer-edit>
function setEditVisibility(selectedOffer){
vm.currentSelectedOffer = selectedOffer;
vm.isEditVisible = !vm.isEditVisible;
}
it will because you have bounded to each ng-repeat object .
If you want to toggle the visibility of oi-offer-edit independently of each offer object then you should move the boolean flag you're checking in the ng-if directive into the offers array.
Check the below example it will help you accomplish what you want to do.
angular
.module('demo', [])
.controller('DefaultController', DefaultController);
function DefaultController() {
var vm = this;
vm.company = {
offers: [
{ id: 1, name: 'Offer 1' },
{ id: 2, name: 'Offer 2' },
{ id: 3, name: 'Offer 3' }
]
};
vm.setEditVisibility = setEditVisibility;
function setEditVisibility(id) {
for (var i = 0; i < vm.company.offers.length; i++) {
if (vm.company.offers[i].id === id) {
vm.company.offers[i].isEditVisible = !vm.company.offers[i].isEditVisible;
}
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<div ng-repeat="offer in ctrl.company.offers">
{{offer.name}}
<button ng-click="ctrl.setEditVisibility(offer.id)">Toggle Edit Visibility</button>
<span ng-if="offer.isEditVisible">{{offer.name}} Edit Details</span>
</div>
</div>
</div>

Remove object from ng-repeat

I have a PhoneGap + Onsen UI + AngularJS app in the works, where I have a list in the view, where the items will be fetched from the controllers variable.
I want to be able to remove items from this list, by clicking on them.
The list looks like this:
<ons-list>
<ons-list-item modifier="tappable" class="item" ng-repeat="citem in completeditems" ng-click="delete(citem)">
<ons-row>
<ons-col>
<div class="titlediv">
<header>
<span class="item-title">{{citem.name}}</span>
</header>
</div>
<div class="item-dates">
<span class="item-start">{{citem.start}}</span>
</div>
</ons-col>
</ons-row>
</ons-list-item>
</ons-list>
The completeditems object in the $scope looks like this:
var completeditemname = "item" + i;
$scope.completeditems[completeditemname] = {
id : "ID",
name : "Name for it",
start: "Start date"
}
Tried the following method, but it didn't work out:
$scope.delete = function(item) {
var index = $scope.completeditems.indexOf(item);
$scope.completeditems.splice(index,1);
//$scope.completeditems.remove(item); //tried this aswell
$scope.$apply() //i need this to update the view
}
You do not need the $scope.$apply() invocation. As you are making alterations to scope variables the digest cycle will be triggered anyhow and you will be encountering an error because of this I believe.
UPDATED:: You're working with an actual object by the looks of it so I've updated the code in the plunker to help you out. It means altering the ng-repeat to use both key and value.
Here is a simple plunkr showing a basic example of what you are trying to do with a one liner in the delete function http://plnkr.co/edit/NtQD....
<body ng-app="myApp">
<div ng-controller="myController as ctrl">
<ul ng-repeat="(key, value) in ctrl.items track by key">
<li ng-click="ctrl.delete(key)">{{value}}</li>
</ul>
</div>
</body>
var myApp = angular.module('myApp', [])
.controller('myController', [
'$scope',
function($scope) {
var self = this;
self.items = {
item1: {
id: 1,
name: 'a'
},
item2: {
id: 2,
name: 'b'
},
item3: {
id: 3,
name: 'c'
}
};
self.delete = function(key) {
delete self.items[key];
};
}
]);
Hope that helps you out!
$scope.$apply() should only be used when changes are coming in from outside the Angular framework. Since your delete() function is being called from an ng-click, it is already being managed by Angular and calling $apply() will raise a "$digest is already in progress" error (check your browser console). Removing that call will most likely get your code working.

AngularJs - binding passed in paramter to object from different controller

I'm having difficulties in solving this. What I'm trying to achieve is to update iterated objects which is passed in to a function in a different controller.
Here is my controllers -
angular.module('eatmapp.controllers', ['eatmapp.services'])
.controller('AppCtrl', function($scope) {
$scope.intoCart = function(item) {
if(item.type == 'variations'){
item = newItemObj;
}
}
})
.controller('BrowseCtrl', function($scope, dataService, $localstorage) {
dataService.getItems().then(function(returnData) {
$scope.items = returnData.products;
})
});
Here is my view -
<div ng-controller="BrowseCtrl">
<div class="list card product" ng-repeat="item in items" ng-click="intoCart(item)">
<div class="item item-text-wrap">
<span class="ifs-productcat" ng-repeat="category in item.categories">{{category}}<span ng-if="$index != item.categories.length - 1">,</span></span><br>
<h3>{{item.title}}</h3>
<h3>Rs.{{item.price}}</h3>
</div>
</div>
</div>
I need to update item object with newItemObject in iteration(ng-repeat) implemeted in template view after doing some condition check with method (intoCart) in another controller(AppCtrl). I'm fairly new to javascript programming and I'm looking for some help.
The problem I had was not able to get access to 'ng-repeat' child scope in controller.
I solved this using 'this.item' in controller rather than passing the whole object or index.
HTML -
<div class="list card product" ng-repeat="item in items" ng-click="intoCart()"></div>
Controller -
angular.module('eatmapp.controllers', ['eatmapp.services'])
.controller('AppCtrl', function($scope) {
$scope.intoCart = function() {
item = this.item; // current (ng-click) child scope of ng-repeat
if(item.type == 'variations'){
item = newItemObj;
}
}
})
Now, whenever I made changes to 'item' object, it automatically updates scope in view (ng-repeat).
Once way I like to handle this is by using services as setters and getters. The problem is you have to include the service with every controller that needs to access it, but if you don't have too many it's no big deal. So something like this:
.service('userFirstName', function() {
var userFirstNameProp;
return {
getter: function() {
return userFirstNameProp;
},
setter: function(value) {
userFirstNameProp = value;
}
};
})
Then you can call userFirstName.getter() or userFirstName.setter("John") as appropriate.

Changing $scope inside controller

I want to change $scope within controller from the wrapping div to the object I'm currently clicking on. My code is as follows:
var blogApp = angular.module('blogApp', ['ngSanitize', 'ngRoute']);
blogApp.controller('blogPostsCtrl', function($scope, $http) {
$http.get('http://jsonplaceholder.typicode.com/posts').success(function(data) {
$scope.posts = data;
$scope.postsLoaded = 'article--loaded';
});
$scope.getPost = function(postID) {
var currentPost = document.getElementById('post-'+postID);
$scope.postsLoaded = 'article--loaded';
$http.get('http://jsonplaceholder.typicode.com/posts/'+postID).success(function(data) {
$scope.body = data.body;
currentPost.insertAdjacentHTML('beforeend', '<div class="body body--hidden" id="body-'+postID+'">'+$scope.body+'</div>');
var currentBody = document.getElementById('body-'+postID);
setTimeout(function() { currentBody.className = currentBody.className + ' body--visible'; }, 1000);
currentPost.classname = 'article one-half desk-one-whole';
});
};
});
html:
<div class="site-wrapper">
<div class="grid-wrapper" ng-controller="blogPostsCtrl">
<article ng-repeat="post in posts" ng-class="postsLoaded" class="article one-half desk-one-whole" id="post-{{post.id}}" ng-click="getPost(post.id)">
<header><h2>{{post.title}}</h2></header>
</article>
</div>
</div>
As you can see, I use function getPost inside controller and there I'm using $scope, but it's (as it should be) set for, like I said, global wrapper. How can I solve this? Please note I'm new to Angular, so I don't know if it's the valid way ;-)
I agree with #Matthew Green. Your question a bit confused. But as far as I can see, you should use directives.
Create a directive and assign to it own $scope.
Hope that will help you.

AngularJS directive - ng-class in ng- repeat should it be a $watcher to toggle style?

I am currently implementing a spike to further my understanding on angular directives etc.
The premise is to create a FX watch list on a number of currency pairs.
My data feed is set up for my price updates via socket.io.
The stumbling block that i have is being able to change the css dependent on price change ie up arrow for up, down arrow for down.
I feel a watcher function is what i need but struggled on where to start so was looking for some sort of expression in ng-class to do the job ... but the method not only started to look like a $watcher it was also flawed as saving the previous price to scope on my directive meant there was only ever one old value not one for each price.
There for my question is : Is the solution with ng-class or in setting up a $watcher function ?
Heres my code ...
HTML template
<div ng-repeat="rate in rates" ng-click="symbolSelected(rate)">
<div class="col-1-4">
{{rate.symbol}}
</div>
<div class="col-1-4">
<span ng-class='bullBear(rate.bidPoint)' ></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
</div>
<div class="col-1-4">
<span ng-class='bullBear(rate.offerPoint)' ></span> {{rate.offerBig}}<span class="point">{{rate.offerPoint}}</span>
</div>
<div class="col-1-4">
{{rate.timeStamp | date : 'hh:mm:ss'}}
</div>
</div>
My directive currently looks like this ... as noted this will not work and the bullBear method was starting to look like a $watcher function.
.directive('fxmarketWatch', function(fxMarketWatchPriceService){
return {
restrict:'E',
replace:'true',
scope: { },
templateUrl:'common/directives/fxMarketWatch/marketwatch.tpl.html',
controller : function($scope, SYMBOL_SELECTED_EVT,fxMarketWatchPriceService){
$scope.symbolSelected = function(currency){
$scope.$emit(SYMBOL_SELECTED_EVT,currency);
}
$scope.bullBear = function(newPrice){
if ($scope.oldPrice> newPrice ){
return ['glyphicon glyphicon-arrow-down','priceDown'];
}
else if ($scope.oldPrice > newPrice ){
return ['glyphicon glyphicon-arrow-up','priceUp'];
}
}
$scope.$on('socket:fxPriceUpdate', function(event, data) {
$scope.rates = data.payload;
});
}
}
})
You could modify the ng-class and move the logic into the view, because styling and placing classes shouldn't be done in code.
<div class="col-1-4">
<span class="glyphicon" ng-class="{'glyphicon-arrow-up priceUp': oldPrice > rate.bidPoint, 'glyphicon-arrow-down priceDown':oldPrice > rate.bidPoint}"></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
</div>
Or like this:
<span class="glyphicon {{oldPrice > rate.bidPoint ? 'glyphicon-arrow-down priceDown':'glyphicon-arrow-up priceUp'}}></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
I will recommend you to use both ng-class and $watcher. The two can actually compliment each other:
UPDATE: To make the code works with ng-repeat, we need to migrate all of CSS classes logic to another controller:
app.controller('PriceController', function($scope) {
// we first start off as neither up or down
$scope.cssBid = 'glyphicon';
$scope.cssOffer = 'glyphicon';
var cssSetter = function(newVal, oldVal, varName) {
if (angular.isDefined(oldVal) && angular.isDefined(newVal)) {
if (oldVal > newVal) {
$scope[varName] = 'glyphicon glyphicon-arrow-down priceDown';
} else if (newVal > oldVal) {
$scope[varName] = 'glyphicon glyphicon-arrow-up priceUp';
} else {
$scope[varName] = 'glyphicon';
}
}
};
// watch for change in 'rate.bidPoint'
$scope.$watch('rate.bidPoint', function(newVal, oldVal) {
cssSetter(newVal, oldVal, 'cssBid');
});
// watch for change in 'rate.offerPoint'
$scope.$watch('rate.offerPoint', function(newVal, oldVal) {
cssSetter(newVal, oldVal, 'cssOffer');
});
});
Next, we bind this PriceController onto ng-repeat div. By doing so, Angular will create one controller instance for each rate in rates. So this time rate.bidPoint and rate.offerPoint should be available for $watch-ing:
<div ng-repeat="rate in rates" ng-click="symbolSelected(rate)" ng-controller="PriceController">
<div class="col-1-4">
<span ng-class='cssBid'></span> {{rate.bidBig}}<span class="point">{{rate.bidPoint}}</span>
</div>
<div class="col-1-4">
<span ng-class='cssOffer'></span> {{rate.offerBig}}<span class="point">{{rate.offerPoint}}</span>
</div>
</div>
Now, directive's controller will be much shorter than before:
controller: function($scope, SYMBOL_SELECTED_EVT, fxMarketWatchPriceService){
$scope.symbolSelected = function(currency) {
$scope.$emit(SYMBOL_SELECTED_EVT, currency);
}
$scope.$on('socket:fxPriceUpdate', function(event, data) {
$scope.rates = data.payload;
});
}

Categories

Resources