angularjs - Sharing data between controllers through service - angularjs

I have a 2 controllers [FirstController,SecondController] sharing two arrays of data (myFileList,dummyList) through a service called filecomm.
There is one attribute directive filesread with isolated scope that is bound to a file input in order to get the array of files from it.
My problem is that myFileList array in my service never gets updated when I select the files with the input. However, dummyList array gets updated immediately in the second div (inner2). Does anybody know why is this happening?
For some reason in the second ngrepeat when I switch from (fi in secondCtrl.dummyList) to (fi in secondCtrl.myFileList) it stops working.
Any help would be greatly appreciated.
Markup
<div ng-app="myApp" id="outer">
<div id="inner1" ng-controller="FirstController as firstCtrl">
<input type="file" id="txtFile" name="txtFile"
maxlength="5" multiple accept=".csv"
filesread="firstCtrl.myFileList"
update-data="firstCtrl.updateData(firstCtrl.myFileList)"/>
<div>
<ul>
<li ng-repeat="item in firstCtrl.myFileList">
<fileuploadrow my-file="item"></fileuploadrow>
</li>
</ul>
</div>
<button id="btnUpload" ng-click="firstCtrl.uploadFiles()"
ng-disabled="firstCtrl.disableUpload()">Upload
</button>
</div>
<div id="inner2" ng-controller="SecondController as secondCtrl">
<ul ng-repeat="fi in secondCtrl.dummyList">
<li>Hello</li>
</ul>
</div>
</div>
JS
angular.module('myApp', [])
.controller('FirstController',
['$scope','filecomm',function ($scope,filecomm) {
this.myFileList = filecomm.myFileList;
this.disableUpload = function () {
if (this.myFileList) {
return (this.myFileList.length === 0);
}
return false;
};
this.uploadFiles = function () {
var numFiles = this.myFileList.length;
var numDummies = this.dummyList.length;
filecomm.addDummy('dummy no' + numDummies + 1);
console.log('Files uploaded when clicked:' + numFiles);
console.log('dummy is now:'+ this.dummyList.length);
};
this.updateData = function(newData){
filecomm.updateData(newData);
console.log('updated data first controller:' + newData.length);
};
this.dummyList = filecomm.dummyList;
console.log('length at init:' + this.myFileList.length);
}]) //FirstController
.controller('SecondController',
['$scope', 'filecomm', function($scope,filecomm) {
var self = this;
self.myFileList = filecomm.myFileList;
self.dummyList = filecomm.dummyList;
console.log('SecondController myFileList - length at init:' +
self.myFileList.length);
console.log('ProgressDialogController dummyList - length at init:' +
self.dummyList.length);
}]) //Second Controller
.directive('filesread',[function () {
return {
restrict: 'A',
scope: {
filesread: '=',
updateData: '&'
},
link: function (scope, elm, attrs) {
scope.$watch('filesread',function(newVal, oldVal){
console.log('filesread changed to length:' +
scope.filesread.length);
});
scope.dataFileChangedFunc = function(){
scope.updateData();
console.log('calling data update from directive:' +
scope.filesread.length);
};
elm.bind('change', function (evt) {
scope.$apply(function () {
scope.filesread = evt.target.files;
console.log(scope.filesread.length);
console.log(scope.filesread);
});
scope.dataFileChangedFunc();
});
}
}
}]) //filesread directive
.directive('fileuploadrow', function () {
return {
restrict: 'E',
scope: {
myFile: '='
},
template: '{{myFile.name}} - {{myFile.size}} bytes'
};
}) //fileuploadrow directive
.service('filecomm', function FileComm() {
var self = this;;
self.myFileList = [];
self.dummyList = ["item1", "item2"];
self.updateData = function(newData){
self.myFileList= newData;
console.log('Service updating data:' + self.myFileList.length);
};
self.addDummy = function(newDummy){
self.dummyList.push(newDummy);
};
}); //filecomm service
Please see the following
JSFiddle
How to test:
select 1 or more .csv file(s) and see each file being listed underneath.
For each file selected the ngrepeat in the second div should display Hello. That is not the case.
Change the ngrepat in the second div to secondCtrl.dummyList
Once you select a file and start clicking upload, you will see that for every click a new list item is added to the ul.
Why does dummyList gets updated and myFileList does not?

You had a couple of issues.
First, in the filecomm service updateData function you were replacing the list instead of updating it.
Second, the change wasn't updating the view immediately, I solved this by adding $rootScope.$apply which forced the view to update.
Updated JSFiddle, let me know if this isn't what you were looking for https://jsfiddle.net/bdeczqc3/76/
.service('filecomm', ["$rootScope" ,function FileComm($rootScope) {
var self = this;
self.myFileList = [];
self.updateData = function(newData){
$rootScope.$apply(function(){
self.myFileList.length = 0;
self.myFileList.push.apply(self.myFileList, newData);
console.log('Service updating data:' + self.myFileList.length);
});
};
}]); //filecomm service
Alternately you could do the $scope.$apply in the updateData function in your FirstController instead of doing $rootScope.$apply in the filecomm service.
Alternate JSFiddle: https://jsfiddle.net/bdeczqc3/77/
this.updateData = function(newData){
$scope.$apply(function(){
filecomm.updateData(newData);
console.log('updated data first controller:' + newData.length);
});
};

Related

How to detect all imges loading finished in AngularJS

I want to use ng-repeat to show more then 100 images in a page. Those images are taking significant time in loading and i don't want to show them getting loaded to the users. So, I only want show them after all of them are loaded in the browser.
Is there a way to detect, if all the images are loaded?
you can use load event like this.
image.addEventListener('load', function() {
/* do stuff */
});
Angular Directives
Solution for single image
HTML
<div ng-app="myapp">
<div ng-controller="MyCtrl1">
<loaded-img src="src"></loaded-img>
<img ng-src="{{src2}}" />'
</div>
</div>
JS
var myApp = angular.module('myapp',[]);
myApp
.controller('MyCtrl1', function ($scope) {
$scope.src = "http://lorempixel.com/800/200/sports/1/";
$scope.src2 = "http://lorempixel.com/800/200/sports/2/";
})
.directive('loadedImg', function(){
return {
restrict: 'E',
scope: {
src: '='
},
replace: true,
template: '<img ng-src="{{src}}" class="none"/>',
link: function(scope, ele, attr){
ele.on('load', function(){
ele.removeClass('none');
});
}
};
});
CSS
.none{
display: none;
}
http://jsfiddle.net/jigardafda/rqkor67a/4/
if you see the jsfiddle demo, you will notice src image is only showing after image is fully loaded whereas in case of src2 you can see image loading.(disable cache to see the difference)
Solution for multiple images
HTML
<div ng-app="myapp">
<div ng-controller="MyCtrl1">
<div ng-repeat="imgx in imgpaths" ng-hide="hideall">
<loaded-img isrc="imgx.path" onloadimg="imgx.callback(imgx)"></loaded-img>
</div>
</div>
</div>
JS
var myApp = angular.module('myapp',[]);
myApp
.controller('MyCtrl1', function ($scope, $q) {
var imp = 'http://lorempixel.com/800/300/sports/';
var deferred;
var dArr = [];
var imgpaths = [];
for(var i = 0; i < 10; i++){
deferred = $q.defer();
imgpaths.push({
path: imp + i,
callback: deferred.resolve
});
dArr.push(deferred.promise);
}
$scope.imgpaths = imgpaths;
$scope.hideall = true;
$q.all(dArr).then(function(){
$scope.hideall = false;
console.log('all loaded')
});
})
.directive('loadedImg', function(){
return {
restrict: 'E',
scope: {
isrc: '=',
onloadimg: '&'
},
replace: true,
template: '<img ng-src="{{isrc}}" class="none"/>',
link: function(scope, ele, attr){
ele.on('load', function(){
console.log(scope.isrc, 'loaded');
ele.removeClass('none');
scope.onloadimg();
});
}
};
});
To detect if all images are loaded,
for each image i generated a deferred object and passed its deferred.resolve as a image onload callback of the directive and then pushed that deferred objects promise in an array. and after that i used $q.all to detect if all those promise are yet resolved or not.
http://jsfiddle.net/jigardafda/rqkor67a/5/
UPDATE: angular way added.
UPDATE: added solution for loading multiple images.
Check if all images are loaded
jQuery.fn.extend({
imagesLoaded: function( callback ) {
var i, c = true, t = this, l = t.length;
for ( i = 0; i < l; i++ ) {
if (this[i].tagName === "IMG") {
c = (c && this[i].complete && this[i].height !== 0);
}
}
if (c) {
if (typeof callback === "function") { callback(); }
} else {
setTimeout(function(){
jQuery(t).imagesLoaded( callback );
}, 200);
}
}
});
Callback occurs when all images are loaded
image load errors are ignored (complete will be true)
Use:
$('.wrap img').imagesLoaded(function(){
alert('all images loaded');
});
Note : this code worked for me, Source :
http://wowmotty.blogspot.in/2011/12/all-images-loaded-imagesloaded.html

angularjs scope function of a repeated directive

I am trying to have a directive with a repeat on it and have it call a function on the parent control as well as child controls. however when I add a scope: { function:&function}
the repeat stops working properly.
fiddle
the main.html is something like
<div ng-app="my-app" ng-controller="MainController">
<div>
<ul>
<name-row ng-repeat="media in mediaArray" on-delete="delete(index)" >
</name-row>
</ul>
</div>
</div>
main.js
var module = angular.module('my-app', []);
function MainController($scope)
{
$scope.mediaArray = [
{title: "predator"},
{title: "alien"}
];
$scope.setSelected = function (index){
alert("called from outside directive");
};
$scope.delete = function (index) {
alert("calling delete with index " + index);
}
}
module.directive('nameRow', function() {
return {
restrict: 'E',
replace: true,
priority: 1001, // since ng-repeat has priority of 1000
controller: function($scope) {
$scope.setSelected = function (index){
alert("called from inside directive");
}
},
/*uncommenting this breaks the ng-repeat*/
/*
scope: {
'delete': '&onDelete'
},
*/
template:
' <li>' +
' <button ng-click="delete($index);">' +
' {{$index}} - {{media.title}}' +
' </button>' +
' </li>'
};
});
As klauskpm said is better to move common logic to an independent service or factory. But the problem that i see is that the ng-repeat is in the same element of your directive. Try embed your directive in an element inside the loop and pass the function in the attribute of that element or create a template in your directive that use the ng-repeat in the template
<li ng-repeat="media in mediaArray" >
<name-row on-delete="delete(media)" ></name-row>
</li>
As I've suggested you, the better approach to share methods is building a Factory or a Service, just like bellow:
app.factory('YourFactory', function(){
return {
setSelected: function (index){
alert("called from inside directive");
}
}
};
And you would call it like this:
function MainController($scope, YourFactory) {
$scope.setSelected = YourFactory.setSelected;
// Could even use $scope.yf = YourFactory;, and call yf.setSelected(index);
// at your view.
(...)
module.directive('nameRow', function(YourFactory) {
(...)
$scope.setSelected = YourFactory.setSelected;
(...)
Hope it will help you.

Angularjs Hash linking redirects to a new URL

I want to link to an element on the same page. I followed the link below but for some reason it redirecting me to the same URL with #id attached to the URL, I have html5Mode enabled.
How to handle anchor hash linking in AngularJS
directive code
var commonComponentsApp = commonComponentsApp || angular.module('cpUtils', []);
commonComponentsApp.directive("highlight", function ($location, $compile, $anchorScroll) {
'use strict';
return {
restrict: "A",
controller: function ($scope) {
$scope.scrollTo = function (id) {
console.log(id);
$location.hash(id);
$anchorScroll();
}
},
link: function (scope, element, attrs) {
var hightedText = attrs.highlight;
hightedText = hightedText.replace("<em", "<em ng-click=scrollTo('" + attrs.id + "')");
var item = '<p>' + hightedText + '</p>';
var e = angular.element(item);
e = $compile(e)(scope);
element.html(e);
}
};
});
Any help will be much appreciated. Thanks
UPDATE
Its inside a ng-repeaet
<li id="document-{{$index}}" ng-repeat="document in query.response.result" ng- mouseenter="select(document, $index)" bubble>
<div class="description-wrapper">
......
<hr />
<p highlight="{{query.response.highlighted[document.id].text[0]}}" id="{{document.id}}"></p>
.....
</div>
</li>
as you hover over a list item, it shows the complete record on the side which has the link it should scroll to.
Does your attrs.id value contain the hash? If so, it shouldn't. You only need the id value, without the hash.
Also, your ng-click=scrollTo(id) is missing quotes:
<em ng-click="scrollTo('idName')">

Accessing hammer event info from Angular directive inside controller

I'm new to Angular, and I'm trying to get the XY coordinates of a tap using angular-hammer.js directives. Here's how the directives are set up:
var hmTouchevents = angular.module('hmTouchevents', []),
hmGestures = ['hmHold:hold',
'hmTap:tap',
'hmDoubletap:doubletap',
'hmDrag:drag',
'hmDragup:dragup',
'hmDragdown:dragdown',
'hmDragleft:dragleft',
'hmDragright:dragright',
'hmSwipe:swipe',
'hmSwipeup:swipeup',
'hmSwipedown:swipedown',
'hmSwipeleft:swipeleft',
'hmSwiperight:swiperight',
'hmTransform:transform',
'hmRotate:rotate',
'hmPinch:pinch',
'hmPinchin:pinchin',
'hmPinchout:pinchout',
'hmTouch:touch',
'hmRelease:release'];
angular.forEach(hmGestures, function(name){
var directive = name.split(':'),
directiveName = directive[0],
eventName = directive[1];
hmTouchevents.directive(directiveName, ["$parse", function($parse) {
return {
scope: true,
link: function(scope, element, attr) {
var fn, opts;
fn = $parse(attr[directiveName]);
opts = $parse(attr["hmOptions"])(scope, {});
scope.hammer = scope.hammer || Hammer(element[0], opts);
return scope.hammer.on(eventName, function(event) {
return scope.$apply(function() {
return fn(scope, {
$event: event
});
});
});
}
};
}
]);
});
My html looks like this:
<div ng-controller="IndexCtrl" >
<div class='tap-area' hm-tap="tap();">
</div>
</div>
My controller looks like this:
App.controller('IndexCtrl', function ($scope, Myapp) {
$scope.tap = function(ev){
//How do I get the event.gesture.center.pageX in here?
};
});
I figured out how to make this work. After return scope.hammer.on(eventName, function(event) { I added scope.event = event; and then in my controller I can get XY coords of a tap by using this.event.center.pageX or this.event.center.pageY.
It was posted long time ago but here is another solution.
Just add $event to your html

AngularJS Passing Variable to Directive

I'm new to angularjs and am writing my first directive. I've got half the way there but am struggling figuring out how to pass some variables to a directive.
My directive:
app.directive('chart', function () {
return{
restrict: 'E',
link: function (scope, elem, attrs) {
var chart = null;
var opts = {};
alert(scope[attrs.chartoptions]);
var data = scope[attrs.ngModel];
scope.$watch(attrs.ngModel, function (v) {
if (!chart) {
chart = $.plot(elem, v, opts);
elem.show();
} else {
chart.setData(v);
chart.setupGrid();
chart.draw();
}
});
}
};
});
My controller:
function AdListCtrl($scope, $http, $rootScope, $compile, $routeParams, AlertboxAPI) {
//grabing ad stats
$http.get("/ads/stats/").success(function (data) {
$scope.exports = data.ads;
if ($scope.exports > 0) {
$scope.show_export = true;
} else {
$scope.show_export = false;
}
//loop over the data
var chart_data = []
var chart_data_ticks = []
for (var i = 0; i < data.recent_ads.length; i++) {
chart_data.push([0, data.recent_ads[i].ads]);
chart_data_ticks.push(data.recent_ads[i].start);
}
//setup the chart
$scope.data = [{data: chart_data,lines: {show: true, fill: true}}];
$scope.chart_options = {xaxis: {ticks: [chart_data_ticks]}};
});
}
My Html:
<div class='row-fluid' ng-controller="AdListCtrl">
<div class='span12' style='height:400px;'>
<chart ng-model='data' style='width:400px;height:300px;display:none;' chartoptions="chart_options"></chart>
{[{ chart_options }]}
</div>
</div>
I can access the $scope.data in the directive, but I can't seem to access the $scope.chart_options data.. It's definelty being set as If I echo it, it displays on the page..
Any ideas what I'm doing wrong?
UPDATE:
For some reason, with this directive, if I move the alert(scope[attrs.chartoptions]); to inside the $watch, it first alerts as "undefined", then again as the proper value, otherwise it's always undefined. Could it be related to the jquery flot library I'm using to draw the chart?
Cheers,
Ben
One problem I see is here:
scope.$watch(attrs.ngModel, function (v) {
The docs on this method are unfortunately not that clear, but the first argument to $watch, the watchExpression, needs to be an angular expression string or a function. So in your case, I believe that you need to change it to:
scope.$watch("attrs.ngModel", function (v) {
If that doesn't work, just post a jsfiddle or jsbin.com with your example.

Resources