Basically I have a timeline with posts that is a $firebaseArray and any change to this array is getting binded properly. But when I want to bind any other data it only binds when ngInfiniteScroll is trying to retrieve more data from firebase, so only when I scroll down.
In the code bellow I'm calling {{getMoreDetails()}} and this data is binded when the first set of data is being retrieved with ngInfiniteScroll but as soon as it is loaded the bind breaks and only binds again when scrolling.
My concerns here are:
Was ngInfiniteScroll designed to work this way?
Is there any workaround in this scenario?
Stack:
"firebase": "2.4.2","angularfire": "~1.2.0","firebase-util": "0.2.5","ngInfiniteScroll": "1.2.2"
timeline.html
<div ng-controller="TimelineController">
<section class="entrys main-content" infinite-scroll="posts.scroll.next(3)" infinite-scroll-distance="0.3">
<div class="inner">
<div ng-repeat="post in filteredPostsResults = (posts | filter:postIdFilter)">
<article class="entry">
<img ng-if="post.sourceType=='IMAGE'" data-ng-src="{{getPostData(post)}}"/>
<div class="entry-info">
<h3><div ng-bind-html="post.description | emoticons"></div></h3>
<small>posted on <time>{{getDateInFormat(post.createdAt)}}</time></small>
{{getMoreDetails()}}
</div>
</article>
</div>
</div>
</section>
</div>
timeline.js
(function (angular) {
"use strict";
var timeline = angular.module('myApp.user.timeline', ['firebase', 'firebase.utils', 'firebase.auth', 'ngRoute', 'myApp.user.timelineService']);
timeline.controller('TimelineController', [ '$scope', '$routeParams', 'TimelineService', '$publisherServices', '$securityProperties', function ($scope, $routeParams, TimelineService, $publisherServices, $securityProperties) {
if (!$scope.posts){
$scope.posts = TimelineService.getPosts($routeParams.userId);
}
$scope.posts.$loaded(function(result) {
$scope.isPostsLoaded = true;
});
$scope.getMoreDetails = function() {
console.log("LOGGED ONLY WHEN SCROLLING");
return $publisherServices.getDetails();
};
$scope.getPostData = function(post) {
if (!post.dataUrl){
post.dataUrl = $publisherServices.getAwsFileUrl(post.fileName);
}
return post.dataUrl;
};
$scope.postIdFilter = function(post) {
if ($routeParams.postId){
if (post.$id == $routeParams.postId) return post;
} else { return post; }
};
$scope.getDateInFormat = function(timestamp){
var date = new Date();
date.setTime(timestamp);
return date;
};
}]);
})(angular);
timelineService.js
(function (angular) {
"use strict";
var timelineService = angular.module('myApp.user.timelineService', []);
timelineService.service('TimelineService', ['$routeParams', 'FBURL', '$firebaseArray', function ($routeParams, FBURL, $firebaseArray) {
var posts;
var currentUserIdPosts;
var postsRef;
var self = {
getPosts: function(userId){
if (!posts || userId != currentUserIdPosts){
currentUserIdPosts = userId;
postsRef = new Firebase(FBURL).child("posts").child(userId);
var scrollRef = new Firebase.util.Scroll(postsRef, "createdAtDesc");
posts = $firebaseArray(scrollRef);
posts.scroll = scrollRef.scroll;
}
return posts;
}
}
return self;
}]);
})(angular);
I am assuming that you want the post details updated when the data from your Firebase changes.
When Firebase changes are applied to your scope, it seems that it doesn't trigger a digest cycle, so you probably need to do it manually every time you get updates from Firebase.
Take a look at $$updated in $firebaseArray.$extend (see docs).
// now let's create a synchronized array factory that uses our Widget
app.factory("WidgetFactory", function($firebaseArray, Widget) {
return $firebaseArray.$extend({
// override the update behavior to call Widget.update()
$$updated: function(snap) {
// we need to return true/false here or $watch listeners will not get triggered
// luckily, our Widget.prototype.update() method already returns a boolean if
// anything has changed
return this.$getRecord(snap.key()).update(snap);
}
});
});
I hope this helps.
Related
I need to pass an object from one controller to another and have used this solution but it is not working.
Here the code:
angular.module("customerApp", [])
.controller('MainCtrl', function ($scope, myService, $http, $location) {
var vm = this;
vm.pinFormCheck = function () {
vm.count++;
if (vm.pinForm.$valid && vm.details.PIN === vm.pin && vm.count <= 2) {
location.href = "http://localhost:51701/Home/MainMenu";
$scope.obj = {
'cid': 'vm.details.CID',
'name': 'vm.details.Name',
'pin': 'vm.details.PIN',
'bal': 'vm.details.Bal',
'status': 'vm.details.cardStatus'
};
console.log(vm.details.Bal);//the correct balance get displayed in console
} else {
vm.failPin = true;
}
};
})
.controller('CheckCtrl', function ($scope, myService) {
$scope.data = myService.getObj();
})
.factory('myService', function () {
var obj = null;
return {
getObj: function () {
return obj;
},
setObj: function (value) {
obj = value;
}
}
});
Here is the view from which the first object is passed:
<body ng-app="customerApp">
<div ng-controller="MainCtrl as vm">
<form name="vm.pinForm">
<input type="password" ng-model="vm.pin" ng-required="true" />
<p><button ng-disabled="vm.count >=3" ng-click="vm.pinFormCheck();" ng-init="vm.count=0">Proceed</button></p>
</form>
...
Here' the second view where I need the object
<html ng-app="customerApp">
<body ng-controller="CheckCtrl">
<div>
<h1>your balance is {{data.bal}}</h1>
....
The balance from vm.details.Bal from the first view must appear in data.bal in the second view, but nothing is appearing.
You can just save vm.details in some factory.
And then get it in CheckCtrl from this factory.
Factories in AngularJS implement singleton pattern. So saved data will be kept in until your app exists.
You tried to do next thing myService.getObj(); But you didn't save anything to the service.
Inject myService to the MainCtrl and then save details into it.
I have a Parent Angular Controller that has a method that needs to be shared with some other controller. The parent controller looks like this:
"use strict";
(function() {
var ParentCtrl = function($scope, atomico, Asset) {
var _this = this;
_this.busy = atomico.identity == null;
_this.oldestTimestamp = null;
_this.assets = [];
/**
* Infinite scrolling, fetches more assets when the user scrolls down.
*/
_this.fetch = function() {
if (_this.noMoreAssets) { return; }
_this.busy = true;
Asset.all(atomico.metadata['campaign'].id, _this.oldestTimestamp, $scope.dates.start, $scope.dates.end, function(assets) {
_this.busy = false;
if (assets.length > 0) {
_this.assets = _this.assets.concat(assets);
_this.oldestTimestamp = moment(assets[assets.length - 1].start).unix();
} else {
_this.noMoreAssets = true;
}
});
};
};
ParentCtrl.$inject = [ '$scope', 'atomico', 'Asset' ];
angular.module('myModule').controller('ParentCtrl', ParentCtrl);
})();
I am extending this controller in another one to have infinite scrolling to work in a view. This is the child controller:
"use strict";
(function() {
var ChildCtrl = function(atomico, userState, $controller, $scope) {
var _this = this;
angular.extend(_this, $controller('ParentCtrl', {$scope: $scope}));
// Fetch assets after user, campaign and account data is available.
atomico.ready(function(){
var dates = userState.getCampaignViewData(atomico.metadata['campaign'].id).list_view;
$scope.dates = _.isEmpty(dates) ? {start: moment(), end: moment()} : dates;
_this.busy = false;
});
};
CampaignListCtrl.$inject = [ 'atomico', 'userState', '$controller', '$scope' ];
angular.module('myModule').controller('ChildCtrl', ChildCtrl);
})();
And in my view i have this:
<div id='agenda_viewer' ng-controller="ChildCtrl as ctrl">
<p class="at-text-center at-block-center c-empty-list" ng-hide='ctrl.assets.length || ctrl.busy'>
There are no assets to show for this day
</p>
<div class="agenda-flight__content at-row" infinite-scroll='ctrl.fetch()' infinite-scroll-disabled='ctrl.busy' infinite-scroll-parent="true">
<div class="agenda-flight at-row agenda-asset__live" ng-repeat='asset in ctrl.assets' ng-init='asset.collapsed = false'>
<directive-list-row asset='asset'></directive-list-row>
<directive-list-expanded asset='asset' ng-if='asset.collapsed'></directive-list-expanded>
</div>
</div>
<div class='c-loading' ng-show='ctrl.busy'>Loading data...</div>
</div>
The problem i am having is that the ctrl.assets is always empty even thought the service returns the data. Is this an issues with ctrl.assets being defined in the parent controller and not visible in the child controller? How can i make that assets object shared to the child controller so i can see the data in the UI?
What I ended up doing is moving some of this controller variables into the $scope and now seems to be working good. $scope is being shared across children
I have seen an unexpected behaviour in Angularjs with its factories.
I used a factory to communication between two controllers.
In the first scenario it is working fine but not in second one. The only difference between them is that in first example I am accessing the name from the view but in second one I am accessing in scope variable.
Scenario 1
<div ng-controller="HelloCtrl">
<a ng-click="setValue('jhon')">click</a>
</div>
<br />
<div ng-controller="GoodbyeCtrl">
<p>{{fromFactory.name}}</p>
</div>
//angular.js example for factory vs service
var app = angular.module('myApp', []);
app.factory('testFactory', function() {
var obj = {'name':'rio'};
return {
get : function() {
return obj;
},
set : function(text) {
obj.name = text;
}
}
});
function HelloCtrl($scope, testFactory) {
$scope.setValue = function(value) {
testFactory.set(value);
}
}
function GoodbyeCtrl($scope, testFactory) {
$scope.fromFactory = testFactory.get();
}
Scenario 2
<div ng-controller="HelloCtrl">
<a ng-click="setValue('jhon')">click</a>
</div>
<br />
<div ng-controller="GoodbyeCtrl">
<p>{{fromFactory}}</p>
</div>
//angular.js example for factory vs service
var app = angular.module('myApp', []);
app.factory('testFactory', function() {
var obj = {'name':'rio'};
return {
get : function() {
return obj;
},
set : function(text) {
obj.name = text;
}
}
});
function HelloCtrl($scope, testFactory) {
$scope.setValue = function(value) {
testFactory.set(value);
}
}
function GoodbyeCtrl($scope, testFactory) {
$scope.fromFactory = testFactory.get().name;
}
The difference is:
Scenario I
$scope.fromFactory = testFactory.get();
<div ng-controller="GoodbyeCtrl">
<p> {{fromFactory.name}}</p>
</div>
The $scope variable is set to testFactory.get() which is an object reference. On each digest cycle the watcher fetches the value of the property name using the object reference. The DOM gets updated with changes to that property.
Scenario II
$scope.fromFactory = testFactory.get().name;
<div ng-controller="GoodbyeCtrl">
<p>{{fromFactory}}</p>
</div>
The $scope variable is set to testFactory.get().name which is a primative. On each digest cycle, the primative value doesn't change.
The important difference is that when a reference value is passed to a function, and a function modifies its contents, that change is seen by the caller and any other functions that have references to the object.
I have a recent article section where i need to validate whether image is exist or not on server.
I try some tutorial it validate properly but it does not return any value to my ng-if directive.
Here is my recent article section:-
<div ng-controller="RecentCtrl">
<div class="col-md-3" ng-repeat="items in data.data" data-ng-class="{'last': ($index+1)%4 == 0}" bh-bookmark="items" bh-redirect>
<div class="forHoverInner">
<span class="inner">
<span class="defaultThumbnail">
<span ng-if="test(app.getEncodedUrl(items.bookmark_preview_image))" style="background-image: url('{{app.getEncodedUrl(items.bookmark_preview_image)}}'); width: 272px; height: 272px; " class="thumb" variant="2"></span></span></span> </div>
</div></div>
Here is my recent article controller:-
app.controller('RecentCtrl', function($scope, $http, $rootScope, RecentArticleFactory,$q) {
$scope.test = function(url) {
RecentArticleFactory.isImage(url).then(function(result) {
return result;
});
};
})
Here is recent aricle factory code:-
app.factory("RecentArticleFactory", ["$http", "$q", function ($http, $q) {
return {
isImage: function(src) {
var deferred = $q.defer();
var image = new Image();
image.onerror = function() {
deferred.resolve(false);
};
image.onload = function() {
deferred.resolve(true);
};
image.src = src;
return deferred.promise;
},
}
})
But
ng-if="test(app.getEncodedUrl(items.bookmark_preview_image))" does not return any value
Any Idea?
Thats because it is async due to deferred. Try calling the test function and binding the result value to a field in scope.
First, trigger the test function via $watch:
$scope.$watch("data.data", function() {
for(var i = 0; i < $scope.data.data.length; i++) {
var items = $scope.data.data[i];
$scope.test(items);
}
})
Then change your test function as follows:
$scope.test = function(items) {
items.isImageAvailable= false;
RecentArticleFactory.isImage(items.bookmark_preview_image).then(function(result) {
items.isImageAvailable= result;
});
};
})
Finally, you can use this in your view as:
<span ng-if="items.isImageAvailable" ...></span>
Of course you also need to call app.getEncodedUrl in between. But as I could not see, where app is defined, I omitted this. But the conversion is nevertheless necessary.
I've been developing an e-commerce website and I am stuck at a point. I am using Stripe payment and all is working fine except data biding after token creation. Here is my controller
app.controller('shoppingCartController', ['$scope', '$http', '$sce', 'stripe', '$window', function ($scope, $http, $sce, stripe, $window) {
$window.Stripe.setPublishableKey('pk_test_saiYYlyCNgO2yZq6Mu******');
$scope.createToken = function () {
var expire = $scope.master[0].expire.split('/');
if ($scope.userDetail.$valid === true) {
$window.Stripe.card.createToken({
number: $scope.master[0].card,
cvc: $scope.master[0].cvv,
exp_month: expire[0],
exp_year: expire[1],
}, $scope.makepayment);
}
}
$scope.makepayment = function (status, response) {
if (response.error) {
$scope.handleStripeCallback(response);
} else {
// response contains id and card, which contains additional card details
var data = {token: response.id, data: $scope.cartData};
$http.post('make_payment', data).success(function (data) {
if (data.status) {
$scope.stripePaymentMessage = data.message;
$scope.stripePaymentMessageClass = "success";
} else {
$scope.stripePaymentMessage = data.message;
$scope.stripePaymentMessageClass = "danger";
}
})
}
}
$scope.handleStripeCallback = function (response) {
//alert(response.error.message);
$scope.stripChargeRequest = true;
$scope.stripePaymentMessage = response.error.message;
$scope.stripePaymentMessageClass = "danger";
}
}]);
In my view I am trying to handle error or success message with this code
<div ng-show="stripChargeRequest ">
<div class="alert alert-{{stripePaymentMessageClass}}" role="alert" >
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
{{stripePaymentMessage}}
</div>
</div>
My question is: stripChargeRequest angular variable contain true / false with normal scope binding but when I am trying to create a token by calling $scope.createToken() it not works. I guess I am forgetting something in calling callback function $scope.makepayment(). Fortunately it is working in controller's scope. I can see error after stripe request in controller but it is not showing in view. Please suggest me the proper way of doing that. Thanks in advance.
The stripe callbacks are outside of angular so you need to use $apply to tell angular whenever you update the scope so it can run a digest to update the view
Example:
$scope.handleStripeCallback = function (response) {
//alert(response.error.message);
$scope.stripChargeRequest = true;
$scope.stripePaymentMessage = response.error.message;
$scope.stripePaymentMessageClass = "danger";
$scope.$apply(); // tell angular to update view
}