AngularJS Dynamically Update ng-style - angularjs

I'm trying to set an initial background color on a set of div's that are created using ng-repeat. I also want to update the background color for each div on hover.
I'm able to see the correct color on hover, but I'm not sure how I can set the initial background color for each div when I have a variable in the ng-style. I did try looping through the projects in the controller and calling the rgba function in my controller, but it just applies that last background color to all of the divs.
Here is my ng-repeat block:
<section ng-controller="ProjectsCtrl" class="work">
<div ng-repeat="project in projects" class="work-example" ng-style="{'background-color': '{{ project.background_color }}'}">
<a href="#">
<div class="inner" ng-style="{'background-image': 'url({{ project.image_url }})'}">
<div class="type">{{ project.title }}</div>
<div class="client">{{ project.client }}</div>
<div class="overlay" ng-style="background" ng-mouseover="rgba(project.background_color, 0.2)"></div>
</div>
</a>
</div>
</section>
My controller has a function called rgba that will take the hex (coming from rails api call) and turn it into rgba.
App.controller('ProjectsCtrl', ['$scope', 'Projects', function($scope, Projects) {
$scope.rgba = function(hex, opacity) {
var hex = hex.replace('#', ''),
r = parseInt(hex.substring(0,2), 16),
g = parseInt(hex.substring(2,4), 16),
b = parseInt(hex.substring(4,6), 16),
result = 'rgba('+ r + ',' + g + ',' + b + ',' + opacity + ')';
$scope.background = { 'background-color': result }
}
$scope.projects = Projects.query();
}
]);
Here is the service my controller is calling:
App.factory('Projects', ['$resource', function($resource) {
return $resource('/api/projects/:id', {
id: '#id'
});
}
]);
Here is my attempt to update ng-style from the controller (but assigns all divs the last background color):
$scope.projects = Projects.query(function(projects){
angular.forEach(projects, function(value, index) {
$scope.rgba(value.background_color, '0.8');
});
});
I'm pretty new to the AngularJS world, so I hope all of this makes sense. Any help is greatly appreciated. Thanks!

The reason why "it applies that last background color to all of the divs" is because, of the following code.
$scope.rgba = function(hex, opacity) {
var hex = hex.replace('#', ''),
r = parseInt(hex.substring(0,2), 16),
g = parseInt(hex.substring(2,4), 16),
b = parseInt(hex.substring(4,6), 16),
result = 'rgba('+ r + ',' + g + ',' + b + ',' + opacity + ')';
$scope.background = { 'background-color': result }
}
$scope.projects = Projects.query(function(projects){
angular.forEach(projects, function(value, index) {
$scope.rgba(value.background_color, '0.8');
});
});
When your angular.forEach runs, it is invoking $scope.rgba which is in turn updating the value of $scope.background to the latest background color. Inside your HTML markup, you have <div class="overlay" ng-style="background" ng-mouseover="rgba(project.background_color, 0.2)"></div> which looks for a variable called background in $scope.
Now the catch here is, as this markup is inside an ng-repeat every single repetition of the markup will have the same value for ng-style as everything is looking at the same object $scope.background.
Instead, what I would suggest you to do is the following.
Projects.query(function (projects) {
$scope.projects = projects; // <- $scope.projects is set when the query completes
angular.forEach(projects, function (value, index) {
$scope.rgba(project, '0.8');
});
});
$scope.rgba = function(project, opacity) {
var hex = project.background_color.replace('#', ''),
r = parseInt(hex.substring(0,2), 16),
g = parseInt(hex.substring(2,4), 16),
b = parseInt(hex.substring(4,6), 16),
result = 'rgba('+ r + ',' + g + ',' + b + ',' + opacity + ')';
project.backgroundStyle = { 'background-color': result }
}
And then your markup:
<section ng-controller="ProjectsCtrl" class="work">
<div ng-repeat="project in projects" class="work-example" ng-style="{'background-color': '{{ project.background_color }}'}">
<a href="#">
<div class="inner" ng-style="{'background-image': 'url({{ project.image_url }})'}">
<div class="type">{{ project.title }}</div>
<div class="client">{{ project.client }}</div>
<div class="overlay" ng-style="project.backgroundStyle" ng-mouseover="rgba(project.background_color, 0.2)"></div>
</div>
</a>
</div>
</section>
I believe this would solve your issue of every div having the latest background.

$resource.query returns a wrapper object containing a promise instead of the actual results of the query.
So with the following code:
$scope.projects = Projects.query(function(projects){
angular.forEach(projects, function(value, index) {
$scope.rgba(value.background_color, '0.8');
});
})
You are actually setting $scope.projects to the promise wrapper.
You need to change it to something like this:
Projects.query(function (projects) {
$scope.projects = projects; // <- $scope.projects is set when the query completes
angular.forEach(projects, function (value, index) {
$scope.rgba(value.background_color, '0.8');
});
});

Related

AngularJS - Using custom filter in ng-repeat for prefixing comma

Need to remove comma if value is empty works good if I have value
present at start or middle; But same doesn't work in this scenario.
app.filter('isCSV', function() {
return function(data) {
return (data !== '') ? data + ', ' : '';
};
});
Angularjs ng repeat for addressline - Plunker
I would instead operate on arrays of properties and use a pair of filters, one to remove empty values, and one to join the array.
This way it's very explicit about what properties you are displaying.
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in details">
{{ [ item.address0, item.address1, item.address2, item.address3] | removeEmpties | joinBy:', ' }}
</li>
</ul>
</body>
With the following filters:
app.filter('removeEmpties', function () {
return function (input,delimiter) {
return (input || []).filter(function (i) { return !!i; });
};
});
app.filter('joinBy', function () {
return function (input,delimiter) {
return (input || []).join(delimiter || ',');
};
});
Here's the updated Plunkr
Tricky but should work in your case Also no filter need
{{ item.address0 }} <span ng-if="item.address1">,
</span> {{ item.address1}}<span ng-if="item.address2">,</span>{{
item.address2}}
<span ng-if="item.address3">,</span>{{ item.address3}}
Here is working example
I would prefer writing a function instead of adding a filter so many times.
$scope.mergeAddresses = function(item) {
var address = item.address0;
[1,2,3].forEach(function(i) {
var add = item["address"+i];
if (!add) return;
address += (address ? ", " : "") + add;
});
if (address) address += ".";
return address;
}
Plunker

ng-repeat is calling another function more than its objects?

I am having a ng-repeat with one array and three objects within an array.
and I am calling a function from img src="{{}}" which is in ng-repeat.
So as per concept an img src function should get called 3 times (because I have 3 objects in an array), but it gets called 15 times.
<div class="" ng-init="getCurrentModuleId();"> <!-- 1st image row start -->
<div class="" ng-repeat="obj in courseModuleData"
ng-click="getStartFunction($index + 1);">
<a href="javascript:;" class="hoverStyle">
<img id="{{$index + 1}}" src="{{getLockPlayImage($index + 1);}}"
class="tresure_img">
</a>
</div>
</div>
js code:-
function getLockPlayImage(id) {
$log.info("Welcome to getLockPlayImage function");
var el = document.getElementById(id);
el.style.top = $scope.courseModuleData[id - 1].cordinates.x + "px";
el.style.left = $scope.courseModuleData[id - 1].cordinates.y + "px";
if (getModuleId.module_id == id) {
$scope.getImage = "img/start/playbutton_normal.png";
/*var el =document.getElementById(id);
el.setAttribute('ng-click', 'startQuestions()');*/
} else {
$scope.getImage = "img/start/lock_normal.png";
}
return $scope.getImage;
}
function getCurrentModuleId() {
var _promiseObject = new promise.Promise();
$log.info("Welcome to getLock getCurrentModuleId function");
var getActModule = $scope.startModule();
getActModule.then(function (result) {
_promiseObject.done(false, "Downloaded");
getModuleId = result.rows.item(0);
});
}

angularjs ng-map will auto focus on my location for seemingly no reason

I am using angular ng-map in my web app. I built a function that will take the selected user's address, put a marker there, then zoom into it. However, if I do not use a setTimeout to focus on my marker, the map will automatically snap to my geolocation, despite the fact that i dont use geolocate anywhere in my app. Does anyone know how/why this happens?
Controller:
NgMap.getMap({id:'map'}).then(function(map) {
console.log('map', map);
vm.map = map;
//GEOCODER
var patientLat;
var patientLon;
var geocoder = new google.maps.Geocoder();
geocoder.geocode( { "address": vm.myData.addressHome + ' ' + vm.myData.addressCity + ' ' + vm.myData.state + ' ' + 'USA'}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK && results.length > 0) {
var location = results[0].geometry.location;
patientLat = location.lat();
patientLon = location.lng();
console.log("the patient is located at " + patientLat + " " + patientLon);
vm.patientLocation = [
{
"patientLat" : patientLat
, "patientLon" : patientLon
}
]
setTimeout(function(){
patientPos(patientLat, patientLon);
}, 2000) <---------- here is the timeout i need
}
});
});
function patientPos(patientLat, patientLon) {
console.log(patientLat);
console.log(patientLon);
var bounds2 = new google.maps.LatLngBounds();
var latLng2 = new google.maps.LatLng(patientLat, patientLon);
bounds2.extend(latLng2);
vm.map.fitBounds(bounds2);
vm.map.setZoom(10);
}
HTML
<ng-map
id="map"
scrollwheel="false"
>
<marker
ng-repeat="q in vm.patientLocation"
position="{{q.patientLat}}, {{q.patientLon}}"
icon="{
url:'app/assets/img/patienticon.png',
scaledSize:[30,30]
}"
id="lkj98"
></marker>
<marker
ng-repeat="p in locatorGridData track by $index"
position="{{p.Location.Lat}}, {{p.Location.Long}}"
icon="{
url:'app/assets/img/placeholder.png',
scaledSize:[30,30]
}"
id="{{p.id}}"
on-click="vm.showDetail(p)"
class="markers"
></marker>
<shape id="circle" name="circle" centered="true"
stroke-color='#bc1212' stroke-opacity="0.8"stroke-weight="2"
center="current-position" radius={{vm.selectedRadius}} editable="false" ></shape>
<info-window id="infoWindow" visible-on-marker="lkj98yuih">
<div ng-non-bindable="">
<div class="practice-name">{{vm.p.Location.OrgName}}</div>
<div class="name-specialty">{{vm.p.Provider.firstName}} {{vm.p.Provider.lastName}}, {{vm.p.Provider.speciality}}</div>
<div class="other-text-for-info-window">
{{vm.p.Location.Address1}}<br>
{{vm.p.Location.City}}, {{vm.p.Location.State}}<br>
{{vm.p.Location.Zip}}
</div>
</div>
</info-window>
</ng-map>
You are trying to set a position before the controller has finished so the values haven't updated in the $digest cycle of angular. Basically you are setting those values too soon. To render it immediately after you can take advantage of the $scope.$$postdigest method.
Instead of the timeout you can do:
$scope.$$postDigest(function(){
patientPos(patientLat, patientLon);
})
or with the $timeout method:
$timeout(function(){
patientPos(patientLat, patientLon);
},0,false);
//Run immediately after digest (0 ms after) and do not trigger another digest cycle
Assuming you have gotten the $scope or $timeout in your controller already included.
Turns out I had a shape that had a current position in it
<shape id="circle" name="circle" centered="true"
stroke-color='#bc1212' stroke-opacity="0.8"stroke-weight="2"
center="current-position" radius={{vm.selectedRadius}} editable="false" ></shape>
when I removed it, all was solved

Ionic collection-repeat with date dividers

I got a very large list of about 200 items with text and images. ng-repeat is way to slow to render this smoothly. It tried it with this solution. Works nice. But not with collection-repeat.
My web-service return this:
There are events with specific dates. The events should be grouped by date. So in order to use collection repeat, how is it possible to insert dividers, if you cant use angular.filter groupBy?
I can offer you a partial solution which would only work if the dataset is ordered by the displayed field in the divider.
First of all we need to create a fake element in the array so that we can discriminate the divider amongst the other element.
Let's say we have a collection of posts fetched from a webservice:
.controller('mainController', function($scope, dataService) {
$scope.posts = [];
var divider = '';
});
the private field divider will be in use when we load the posts.
And we will have the loadMore method to load extra data when we scroll the list:
$scope.loadMore = function(argument) {
page++;
dataService.GetPosts(page, pageSize)
.then(function(result) {
if (result.data.length > 0) {
angular.forEach(result.data, function(value, key) {
value.divider = false;
if (value.postId !== divider)
{
divider = value.postId;
$scope.posts.push({divider: true, dividerText: value.postId});
}
$scope.posts.push(value);
});
}
else {
$scope.theEnd = true;
}
})
.finally(function() {
$scope.$broadcast("scroll.infiniteScrollComplete");
});
};
When we fetch the data from the web api (and the promise is resolved) we loop through the collection and check if the field is different from the divider. If this is a new divider we store the info and add a new element to the collection:
angular.forEach(result.data, function(value, key) {
value.divider = false;
if (value.postId !== divider)
{
divider = value.postId;
$scope.posts.push({divider: true, dividerText: value.postId});
}
$scope.posts.push(value);
});
As you can see I've added an element:
$scope.posts.push({divider: true, dividerText: value.postId});
I've used a dividerText field which will be displayed later on.
Now we need to create our own directive divider-collection-repeat which should be attached to a collection repeat:
<ion-item collection-repeat="post in posts" item-height="75" divider-collection-repeat>
I guess you're using infinite-scroll, so here is the whole HTML:
<ion-content ng-controller="mainController">
<ion-list>
<ion-item collection-repeat="post in posts" item-height="75" divider-collection-repeat>
{{post.name}}
</ion-item>
</ion-list>
<ion-infinite-scroll ng-if="!theEnd" on-infinite="loadMore()" distance="50%"></ion-infinite-scroll>
</ion-content>
this is the directive:
.directive('dividerCollectionRepeat', function($parse) {
return {
priority: 1001,
compile: compile
};
function compile (element, attr) {
var height = attr.itemHeight || '75';
var itemExpr = attr.collectionRepeat.split(' ').shift();
attr.$set('itemHeight', itemExpr + '.divider ? 40 : (' + height + ')');
attr.$set('ng-class', itemExpr + '.divider ? "item-divider" : ""');
var children = element.children().attr('ng-hide', itemExpr + '.divider');
element.prepend(
'<div ng-show="' + itemExpr + '.divider" class="my-divider" ' +
'ng-bind="' + itemExpr + '.dividerText" style="height:100%;">' +
'</div>'
);
return function postLink(scope, element, attr) {
scope.$watch(itemExpr + '.divider', function(divider) {
element.toggleClass('item-divider', !!divider);
});
};
}
});
The directive prepends an element (html) to the list using the expression you've defined in your collection-repeat.
In my sample I've use collection-repeat="post in posts" so this line:
var itemExpr = attr.collectionRepeat.split(' ').shift();
fetches the item's name; in my case it is going to be post.
We use the height as well cause we might need to have a different height for the divider.
This bit here is the place where all the magic happens:
element.prepend(
'<div ng-show="' + itemExpr + '.divider" class="my-divider" ' +
'ng-bind="' + itemExpr + '.dividerText" style="height:100%;">' +
'</div>'
);
It uses an ng-show for the field 'post.divider' (ng-show="' + itemExpr + '.divider") and binds the our text field ng-bind="' + itemExpr + '.dividerText"
I've also added a custom class my-divider just in case we need to change the layout of our divider a bit.
The final result is here or in this plunker.
As you might have noticed I haven't used a date field as I already had a sample where, sadly, I didn't have any dates.
I guess it should be very easy to adapt to your situation.
The directive is based on a sample I have found on github.
You will find the code for the directive here.

ng-repeat on multiple arrays

Is there a way to loop through multiple arrays in an ng-repeat directive ?
I tried something like
<ul>
<li ng-repeat="sale in randomSales" ng-repeat="image in imageUrls">
<div display-sale></div>
</li>
</ul>
or
<ul>
<li ng-repeat="sale in randomSales, image in imageUrls">
<div display-sale></div>
</li>
</ul>
but it's not working.
I could solve this issue another way, but I'd like to know if this is possible !
EDIT :
Here is my controller & directive :
app.controller('RandomController', ['$rootScope', '$scope',function($rootScope, $scope) {
$scope.randomSales=[];
$scope.imageUrls = [];
for(var i= 0; i < displayrandomSalesNumber ; i++){
var randomNumber = Math.floor((Math.random() * $rootScope.sales.length-1) + 1);
$scope.randomSales.push($rootScope.sales[randomNumber]);
$scope.imageUrls.push($rootScope.sales[randomNumber].image_urls["300x280"][0].url);
}
console.log($scope.randomSales);
}])
.directive('displaySale', function() {
return {
template: '<div class="center"><a href="#/sale/{{sale.store}}/{{sale.sale_key}}">' +
'<header><h2>{{sale.name}}</h2><h4>in {{sale.store}}</h4></header>' +
'<article>' +
'<p>From : {{sale.begins}} To : {{sale.ends}}</p>' +
'<p class="center"><img ng-src="{{image}}"/></p>' +
'<p>{{sale.description}}</p>' +
'</article>' +
'</a></div>'
};
});
the image is already inside $scope.randomSales, I could access it with {{sale.image_urls["300x280"][0].url}} but i'd get a parse error.
to make it work you can try to do following:
- merge 2 arrays to one arrays of objects representing imageUrls and randomSales.
- iterate though them in your template.
For example:
var maxArrayLength = Math.max(randomSales.length, imageUrls.length);
var i = 0;
var result = []; //will put items {sale: ..., image}
for (i =0; i < maxArrayLength; i++){
var currentSale = randomSales[i] != null ? randomSales[i] : null;
var currentImage = imageUrls[i] != null ? imageUrls[i] : null;
result.push({sale: currentSale, image: currentImage})
}
return result;
And them you can use this array to iterate through on your angular template.
The First Solution proposed by you is wrong , we cant have two ng-repeat attributes in one Html tag, I am not sure about the second one . But you could try like this ..i think it would work.
<ul>
<li ng-repeat="sale in randomSales">
<span ng-repeat="image in imageUrls">
<div display-sale></div>
</span>
</li>
</ul>

Resources