I'm new to AngularJS and currently using Google Firebase for my real-time app. I have created a factory "Friends" which checks my Firebase database in real-time with the following code:
angular.module('App')
.factory('friendsFactory', function(){
var friends = [];
var friendshipRef = firebase.database().ref('friendships');
friendshipRef.on('value', function(snapshot){
//etc.. updates friends array
});
return{
getFriends: function(){
return friends;
},
//etc
}
In my controller I set my $scope.friends variable to my factory class .getFriends() method. This works well and my page displays all friends correctly.
$scope.friends = friendsFactory.getFriends();
However, as soon as my factory is updated, these changes do not reflect on my scope variable and view. I've tried using $apply, $watch etc. but I cannot figure out how I can make sure my scope and view are updated as soon as the factory is. Right now, every time I navigate to a different page and back, only then the changes are shown. They do not update in real-time. Can anyone help me accomplish this? Thanks a lot!
You should be doing this using angularfire which does the three way binding between firebase database, your model and view.
1.Include angularfire in your project.
2.Update your factory according to this and you don't need to do anything. Whenever data is updated on friendships node in firebase database, it will be notified to angularfire and angularfire will update the local variables and call $scope.$apply and thus, view will also be updated.
// factory
angular.module('App', ['firebase'])
.factory('friendsFactory', function($firebaseArray){
var friendshipRef = firebase.database().ref('friendships');
return {
friends: $firebaseArray(friendshipRef)
}
});
// controller
$scope.friends = friendsFactory.friends;
Because, you get list only once, at start.
It is necessary to make $scope.$apply in on section, but it is not right to do in the factory.
Try to use angularfire
Fast solution, will work:
angular.module('App')
.factory('friendsFactory', function(){
var friends = [];
var friendshipRef = firebase.database().ref('friendships');
friendshipRef.on('value', function(snapshot){
$timeout(function () {
//etc.. updates friends array
});
});
return{
friends: friends,
//etc
}
And in controller:
$scope.friends = friendsFactory.friends;
Related
I have multiple controllers on a small app I'm writing, and I have successfully shared a 'selected' variable between the controllers like so.
app.service('selectedEmployee', function () {
var selected = null;
return
{
getSelected: function() {
return selected;
},
postSelected: function(employee) {
selected = employee;
}
};
});
I have a side nav bar with a list of employees. When I click on an employee I call the postSelected function then the getSelected to set $scope.selected.
$scope.selectEmployee = function(employee) {
//Calling Service function postSelected
selectedEmployee.postSelected(employee);
$scope.selected = selectedEmployee.getSelected();
if ($mdSidenav('left').isOpen()) {
$mdSidenav('left').close();
}
}
I have a third controller for my main content area, and this is where I don't understand what to do. I want information from the selected employee to be displayed, but angular is compiling the whole page before the first employee has a chance to get set as selected, and subsequent selections of an employee aren't reloading the main content page (because I haven't told them to I think). Here's my main content controller:
app.controller('mainContentController', ['$scope','selectedEmployee',
function ($scope, selectedEmployee) {
$scope.selected = selectedEmployee.getSelected();
console.log($scope.selected);
}
]);
My main content view is very simple right now
<h2>{{selected.firstName}}{{selected.lastName}}</h2>
My question is how I can tell one controller to effectively update its partial view so that when I select an employee it displays information.
GitLab repo
Don't rely on messy broadcasts if your goal is simply to display & modify the data in the controller's template.
Your controllers do NOT need to "know" when the Service or Factory has updated in order to use it in the template as Angular will handle this for you, as you access the data via dot notation. This is the important concept which you should read more about.
This Fiddle shows both ways of accessing the data, and how using the container object in the template causes Angular to re-check the same actual object on changes - instead of the primitive string value stored in the controller:
http://jsfiddle.net/a01f39Lw/2/
Template:
<div ng-controller="Ctrl1 as c1">
<input ng-model="c1.Bands.favorite" placeholder="Favorite band?">
</div>
<div ng-controller="Ctrl2 as c2">
<input ng-model="c2.Bands.favorite" placeholder="Favorite band?">
</div>
JS:
var app = angular.module("app", []);
app.factory('Bands', function($http) {
return {
favorite: ''
};
});
app.controller('Ctrl1', function Ctrl1(Bands){
this.Bands = Bands;
});
app.controller('Ctrl2', function Ctrl2(Bands){
this.Bands = Bands;
});
First of all lets start by good practices, then solve your problem here...
Good Practices
At least by my knowledge, i dont intend to use services the way you do... you see, services are more like objects. so if i were to convert your service to the way i normally use it would produce the following:
app.service('selectedEmployee', [selectedEmployeeService])
function selectedEmployeeService(){
this.selected = null;
this.getSelected = function(){
return this.selected;
}
this.postSelected = function(emp){
this.selected = emp;
}
}
You see there i put the function seperately, and also made the service an actual object.. i would reccomend you format your controller function argument like this... If you want to disuss/see good practices go here. Anways enough about the good practices now to the real problem.
Solving the problem
Ok The Andrew actually figured this out!! The problem was:that he need to broadcast his message using $rootScope:
$rootScope.$broadcast('selected:updated', $scope.selected);
And then you have to check when $scope.selected is updated.. kinda like $scope.$watch...
$scope.$on('selected:updated', function(event, data) {
$scope.selected = data;
})
After that it autmoatically updates and works! Hope this helped!
PS: Did not know he anwsered already...
So after much research and a lot of really great help from Dsafds, I was able to use $rootScope.$broadcast to notify my partial view of a change to a variable.
If you broadcast from the rootScope it will reach every child controller and you don't have to set a $watch on the service variable.
$scope.selectEmployee = function(employee) {
selectedEmployee.postSelected(employee);
$scope.selected = selectedEmployee.getSelected();
$rootScope.$broadcast('selected:updated', $scope.selected);
if ($mdSidenav('left').isOpen()) {
$mdSidenav('left').close();
}
}
And in the controller of the main content area
function ($scope) {
$scope.$on('selected:updated', function(event, data) {
$scope.selected = data;
})
}
I don't think you have to pass the data directly, you could also just as easily call selectedEmployee.getSelected()
$rootScope also has to be included in the Parent controller and the broadcasting controller.
I have a books service to search for book.
Most of the time the service, give for example only 20 books.
I want the service to be able to change properties of the books on the screen, after the controller recieve the data.
For example:
I have a controller that show list of 20 books (from a search query, and limit properties).
I want that the service will be able to change the books that the controller got, after the controller got the data (realtime change)
controller($scope,bookService){
$scope.data=bookService.getList(query,20)
}
service(function(){
var dataBindedToController=[]
return{
getList:function(query,limit){
dataBindedToController.push([{name:'book1'},{name:'book2'}])
return dataBindedToController[dataBindedToController.length-1]
}
}
})
In the example above every time controller ask for list of books I add the returned data to the service by reference. After that for example if I do in the service: dataBindedToController[0][2].name='Moshe', it will automatically update the controller. the Controller $scope.data === dataBindedToController[0]
Now the question is: When the controller have destroyed, how the service can now this, and remove the bindedData from it's array?
I want to keep the controller ASAP (as simple as possible).
Another example:
A working JSFiddle, that use the bind technique to update a counter in a service, after the controller got the data:
https://jsfiddle.net/tLLtn45j/
var app=angular.module('app',[])
.controller('a',function($scope,service){
$scope.data=service
})
.service('service',function($interval){
var data={counter:3}
$interval(function(){data.counter++},500)
return data
})
The question is: how the service can now when to stop update the counter, when the controller have been destroyed
You can pass the scope like this
controller($scope,bookService){
$scope.data=bookService.getList($scope, query,20)
}
then save the $scope in your service, then attach the event listener there. I'm not sure though if it's a good practice to pass the $scope to the service, and I think it's not
you can listen to $destroy event
$scope.$on('$destroy', function() {
bookService.destroy($scope.$id);
})
you might want to index the dataBindedToController with $scope.$id so you will know what to remove
I am using a jsonn object to load in data between objects. currently using a factory to return the object and bind it between the controllers. Right now I am making a copy like so :
var LevelsHere = $http.get("my.json")
.success(function(data){
var dataCopy = angular.copy(data);
return dataCopy;
});
return {
all: function() {
return LevelsHere;
}
};
This works fine, but I have a button that I want to call this function and refresh it so It gets a clean copy from the my.json (so any changes are reverted).
Just to clarify, in each controller I call it in into a scope within the controller like so
UserService.all().then(function(data){
$scope.storeHere= data.data;
});
I am thinking maybe something like a $rootscope might be the way to go because I am sharing between controllers. So - have the root scope (which is a copy of the json) be shared between controllers. Then when I press my refresh button it would refresh that $rootscope with a fresh copy of my.json so changes would revert back.
Maybe I could use the method I am trying now? I tried the having the refresh button call the $get again but it wasnt binded to both places so it was only refreshing in one controller.
To quickly review - I have json I'm bringing in and using in 2 controllers with a factory calling it. I want to be able to refresh that so it refreshes in both places.
Here is my attempt at the refresh :
$scope.cancelProcedure = function() {
//refresh data
UserService.all().then(function(data){
$scope.levels = data.data;
};
The problem with this is it calls the current data, and doesn't refresh with a new call. I'm not sure how to make it refresh in both places. Thanks!!
To give you an answer I assume the following:
There's one resource you want to get my.json and update from time to time.
You want to access and update the data from two (or more) controllers
You don't want to pollute your $rootScope
In that case the ideal solution would be to store the method to get/update the data in the factory as well as the current data. In each controller, where you need that data, you simply inject the factory and assign it to the $scopeof that controller.
Here's one example:
angular.factory('dataFactory', ['$http', function ($http) {
var dataFactory={};
dataFactory.currentData = null;
dataFactory.update = function () {
return $http.get("my.json")
.success(function(data){
dataFactory.currentData = data;
return data;
});
};
return dataFactory;
}]);
angular.controller('firstCtrl', ['$scope', 'dataFactory', function ($scope, dataFactory) {
$scope.data = dataFactory;
}]);
angular.controller('secondCtrl', ['$scope', 'dataFactory', function ($scope, dataFactory) {
$scope.data = dataFactory;
}]);
In your HTML you can then use e.g. ng-bind="data.currentData" and ng-click="data.update()".
Further thoughts: If you don't want to put the factory in your controllers $scope you might even consider to further break down your logic and create one or two directives that are based on that factory. If that makes sense is not easy to tell with the given information, however.
I've been working on a Chromium extension which sends data from a content script to another extension page which runs an Angular app. Since I separated my app into several controllers relying on a data service, I've run into a bug where sometimes my app's templates will all display correctly but other times it will not. In this simplified example, my app might display the data value on the page on a run and then not display it after refreshing the page and running the same code.
In the case when the data is not displayed, I can still inspect the DataService object at runtime and find the data value to be instantiated correctly.
app.js
angular.module('angularApp', [])
.controller('AppController', ['$scope', 'DataService',
function($scope, dataService) {
$scope.dataService = dataService;
}]);
service.js
var angularApp = angular.module('angularApp');
angularApp.service('DataService', function() {
var data = [];
chrome.runtime.onMessage.addListener(function(message, sender) {
var messageData = $.extend([], message.data);
// populate data array based on messageData
});
};
view.html
<html ng-app="angularApp">
<body ng-controller="AppController">
{{dataService.data}}
</body>
</html>
This seems to be an issue with the asynchronous arrival of the message from the content script, but I'm not sure of the appropriate way to solve this within my Angular service. An ideal answer would explain what is going on to cause my bug and the best practice to build my service and controllers to work as expected every time.
Your controller and view are fine. Your service should work if you do this:
angular.module('angularApp').service('DataService',['$timeout',
function($timeout){
var self = this;
this.data = [];
var tmp;
function handleMessage(){
//tmp == message.data
//populate self.data however..
}
chrome.runtime.onMessage.addListener(function(message,sender){
tmp = message.data;
$timeout(handleMessage);
});
}]);
The reason it isn't working currently is because the event handler isn't executing in angular's event loop. By doing the data manipulation in a $timeout it will force angular to update the view with the data that has changed.
Using $timeout essentially has the same effect as $scope.$apply() in this scenario
I'm new to AngularJS and I would like to understand how to properly separate the model from the controller. Till now I've always worked with the models inside the controllers. For instance:
angular.module("app").controller("customerController", ["Customer", "$scope", "$routeParams",
function (Customer, $scope, $routeParams){
$scope.customer = Customer.find({ID:$routeParams.ID});
}]);
This function retrieves a customer from the database and exposes that customer to the view. But I would like to go further: for example I could have the necessity to ecapsulate something or create some useful functions to abstract from the row data contained in the database. Something like:
customer.getName = function(){
//return customer_name + customer_surname
};
customer.save = function(){
//save the customer in the database after some modifies
};
I want to create a model for the Customer and reuse that model in lots of controllers. Maybe I could then create a List for the customers with methods to retrieve all customers from the database or something else.
In conclusion I would like to have a model that reflects a database entity (like the customer above) with properties and methods to interact with. And maybe a factory that creates a Customer or a list of Customers. How can I achieve a task like this in AngularJS? I would like to receive some advices for this issue from you. A simple example will be very useful or a theoretical answer that helps me to undestand the right method to approch issues like these in Angular. Thanks and good luck with your work.
Angular JS enables you to have automatic view updates when a model change or an event occur.
TAHTS IT!
it does so by using $watches which are a kind of Global Scope java script objects and stay in primary memory through out the life cycle of the angular js web app.
1.Please consider the size of data before putting anything onto the $scope because each data object you attach to it does +1 to $watch. As you are reading from a database you might have 100+ rows with >4 columns and trust me it will eat up client side processing.Pls do consider the size of your dataset and read about angular related performance issues for huge data set
2.to have models for your database entity i would suggest having plain javascript classes i.e. dont put everything on $scope (it will avoid un necessay watches! ) http://www.phpied.com/3-ways-to-define-a-javascript-class/
3.You wish to fire up events when the user changes the values. For this best i would suggest that if you are using ng-repeat to render the data in your array then use $index to get the row number where the change was done and pass this in ng-click i.e. and use actionIdentifier to distinguish in the kinds of events you want
ng-click="someFunc($index,actionIdentifier)"
You need to create a factory/service to do do the job, check jsfiddle
html:
<div ng-app="users-app">
<h2>Users</h2>
<div ng-view ></div>
<script type="text/ng-template" id="list.html">
<p>Users: {{(user || {}).name || 'not created'}}</p>
<button ng-click='getUser()'>Get</button>
<button ng-click='saveUser(user)'>Save</button>
</script>
</div>
js:
angular.module('users-app', ['ngRoute'])
.factory('Users', function() {
function User (user) {
angular.extend(this, user);
}
User.prototype.save = function () {
alert("saved " + this.name);
}
return {
get: function() {
return new User({name:'newUser'});
}
}
})
.config(function($routeProvider) {
$routeProvider
.when('/', {controller:'ListCtrl',templateUrl:'list.html'});
})
.controller('ListCtrl', function($scope, Users) {
$scope.getUser = function() {
$scope.user = Users.get();
}
$scope.saveUser = function(u) {
u.save();
}
})
Hope that help,
Ron