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.
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.
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.
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
From what I've read, it seems using $rootScope.$broadcast is not advisable unless absolutely necessary. I'm using it in a service to notify a controller that a variable has changed. Is this incorrect? Is there a better way to do it? Should I be using watch instead (even though the variable only changes on user interaction) ?
the service:
function Buildservice($rootScope) {
var vm = this;
vm.box= [];
var service = {
addItem: addItem,
};
return service;
// Add item to the box
// Called from a directive controller
function addItem(item) {
vm.box.push(item);
broadcastUpdate();
}
function broadcastUpdate() {
$rootScope.$broadcast('updateMe');
}
// In the controller to be notified:
// Listener for box updates
$scope.$on('updateMe', function() {
// update variable binded to this controller
});
// and from a separate directive controller:
function directiveController($scope, buildservice) {
function addToBox(item){
buildservice.addItem(item);
}
So this works just fine for me, but I can't figure out if this is the way I should be doing it. Appreciate the help!
If you are in same module, why don't you use $scope instead of $rootScope?
You can use a callback function to notify the controller something has changed. You supply the service a function from the controller, and invoke that particular function whenever your variable has been changed. You could also notify multiple controllers if needed.
I have created a small example:
HMTL:
<div ng-controller="CtrlA as A">
{{A.label}}
<input type="text" ng-model="A.input" />
<button ng-click="A.set()">set</button>
</div>
<div ng-controller="CtrlB as B">
{{B.label}}
<input type="text" ng-model="B.input" />
<button ng-click="B.set()">set</button>
</div>
JS
var app = angular.module('plunker', []);
app.controller('CtrlA', function(AService) {
var vm = this;
vm.label = AService.get();
vm.notify = function() {
vm.label = AService.get();
}
vm.set = function() {
AService.set(vm.input)
}
AService.register(vm.notify);
});
app.controller('CtrlB', function(AService) {
var vm = this;
vm.label = AService.get();
vm.notify = function() {
vm.label = AService.get();
}
vm.set = function() {
AService.set(vm.input)
}
AService.register(vm.notify);
});
app.factory("AService", function() {
var myVar = "Observer";
var observers = [];
return {
get: function() {
return myVar;
},
set: function(name) {
console.log(name);
myVar = name;
this.notify();
},
register: function(fn) {
observers.push(fn);
},
notify: function() {
for( i = 0; i < observers.length; i++) {
observers[i]();
}
}
}
})
You will see upon executing this that the controllers get notified when the internal variable has been changed. (Notice: I haven't filtered the original sender from the list) (Plnkr)
I have a controllers with name PostController and CommentController
PostController having the function showPost
CommentController having the function showComment
i want to execute the both the functions when button is clicked
<button href="#" ng-click="showPost();showComment()" class="btn">show</button>
how can i do that one without specifying the nested controller concept
You can achieve this by using service/factory. Here is simple example how it should work:
JS
var myApp = angular.module('myApp', []);
function CommentController($scope, myStorage) {
$scope.myStorage = myStorage;
$scope.showMe = function(){
myStorage.setIndex(3);
}
}
function PostController($scope, myStorage) {
$scope.myStorage = myStorage;
}
myApp.factory('myStorage', function () {
var currentBusinessIndex = 0;
return {
getIndex: function () {
return currentBusinessIndex;
},
setIndex: function (index) {
currentBusinessIndex = index;
}
}
});
HTML
<div ng-controller="CommentController">
<button ng-click="showMe();">press me</button>
<pre>{{myStorage.getIndex()}}</pre>
</div>
<div ng-controller="PostController">
<pre>{{myStorage.getIndex()}}</pre>
</div>
Demo Fiddle
In addition, you can watch the service data by using $watch, to trigger on any data change