I am learning angularJS, went through few tutorials and sort of know my why around. It seems that the page never refreshes, therefore a value created in one view should be available in another view, right? I am testing this in a shop scenario. If we are at the main view, and we click on "add to cart" that should trigger a function in the background and add the item in an array. Then when we go to the cart view, we can see the item listed there. But this does not work.
I have a cart controller:
angular.module('shoppingCartApp')
.controller('CartCtrl', function ($scope) {
$scope.cart = [
'one item'
];
$scope.pushing = function(item){
this.cart.push(item);
};
});
In the main view (which doesn't have access to this controller) I have.
<div ng-controller="CartCtrl">
add to chart
</div>
And on the cart view I display the cart object
<div ng-repeat="item in cart">
{{item}}
</div>
We only see the one item. I have also added the ng-click attribute to this page as well, just to test, and it does work, however, if we go home and come back, the item is gone.
From the idea that the page never reloads, should the pushed items stay in the array? here is the simple example in action
Thanks
Controllers are not singletons, so when you change the view the $scope gets destroyed and a new controller will be initialized. If you want persistent data across different views then you want to look at using a service to store it, since they are singletons.
If you create something like
angular.module('app')
.service('cartService', [function() {
var cart = [];
var add = function(item) {
cart.push(item);
};
var get = function() {
return cart;
};
return {
add: add,
get: get
};
}]);
Then you can add that as a dependency in your controllers and use that for backing your data rather than using the $scope.
angular.module('app')
.controller('Ctrl', ['cartService', function(cartService) {
$scope.cart = cartService.get();
$scope.pushing = function(item) {
cartService.add(item);
};
}]);
Related
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;
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.
really new to Angular here and I have a problem that I need some help with. Basically, I need to have a selection of items that user can click on. When an item is clicked, the page needs to show some of the properties that the item has like it's description, etc. The first part is not a problem, but I'm having trouble with the second part, which is displaying the data. So here is what I have:
On the front end, I have an angular ng-click chooseItem(item) function that takes the clicked item as its paramater:
<div ng-repeat="item in items" class="col-xs-2 col-md-1">
<div ng-click="chooseItem(item)" class="thumbnail">
<img src="/images/items/{{item.name}}.png"/>
</div>
</div>
This is then passed on to the items factory through items.getChosenItemData(item) function. Since the real item data is stored in Mongo and not the factory, this function queries the db to retrieve the item data. This retrieved data is stored into the chosenItem object, which is then passed back to the controller as $scope.chosenItem.
app.factory('items', ['$http', function($http){
var objects = {
items: [
// ... more items before these
{name: "Pencil"},
{name: "Pen"}
/* I use these item objects as keys to the items themselves.
The ng-repeat iterates through all of the names for each item
which allows me to display static images for each item to the page.
There aren't many items, about 100, but they have tons of json information
so to hardcode it all into here is not an option
A way to do this without any hardcoding would be nice! */
// more items after these
],
// this is used to store a currently clicked item's values
chosenItem: null
}
objects.getChosenItemData = function(name){
return $http.get('/items/' + name).success(function(data){
// console.log(data);
angular.copy(data, objects.chosenItem);
console.log("Chosen Item: ", objects.chosenItem);
})
}
return objects
}]);
app.controller('MainCtrl', [
'$scope',
'items',
function($scope, items){
$scope.items = items.items;
$scope.chosenItem = null;
$scope.chooseItem = function(item){
items.getChosenItemData(item.name);
$scope.chosenItem = items.chosenItem; //chosen item object attribute in factory
console.log("$scope item: ", $scope.chosenItem);
}
}
});
This almost all works. I can query the data of the clicked item successfully, but returning it is another story. Upon first click, the value of $scope.chosenItem is null. Then upon second click, it stores the value of the click item. This also causes the problem where if I click on n amount of items, the value stored is always the value of the n-1 item, not the current item. I need it to store the value of the clicked item on the first click, not the second.
I have a feeling I need to add a callback somewhere in here to make it work, but I'm new to Angular/JS so I'm not sure where it should even go.
Thanks for any help! Also any tips or leads on Angular design patterns would be much appreciated, since I have the feeling that this is a terrible implementation of something that seems rather simple.
I suggest you to expose the service directly:
$scope.serviceItem = items;
and then you can call it in the view like that:
{{serviceItem.chosenItem}}
It will be always updated to the latest clicked value.
I hope it helps.
I have following 2 html pages
1. home.html
<div data-ng-controller="userComments">
<Will display all the comments>
</div>
2. comments.html
<div data-ng-controller="userComments">
<Have a comment box and submit button.
Submit button calls submit() function on ng-click>
</div>
where comments.html is pop-up which is initiated from the home page.
And controller
.controller('userComment',['$scope', function($scope){
$scope.title = 'User Comment';
$scope.comments = <db call>
$scope.cmt = '';
$scope.submit = function(){
console.log("comment just entered", $scope.cmt);
$scope.comments = $scope.comments.concat($scope.cmt);
console.log("Updated Comments", $scope.comments);
};
}])
New comments need to be updated automatically in the home.html as well. What should i do to accomplish that?
Thanks
Update:
when the comments are added in the comment.html page, ng-click triggers submit function, $scope.comments gets updated with the new comment, but what should i do to get the updated comments in the home.html too?
When you use the same controller on different views, different instances of the controller are created. You'll need a factory or service to store and share data between views.
So in your case, you'll want a comments factory, something like
myApp.factory('commentsService', function() {
return {
comments: []
};
});
Then in your controller:
.controller('userComment',['$scope', 'commentsService', function($scope, commentsService){
$scope.title = 'User Comment';
$scope.comments = commentsService.comments;
$scope.cmt = '';
$scope.submit = function(){
console.log("comment just entered", $scope.cmt);
$scope.comments = $scope.comments.concat($scope.cmt);
// store the comments for use across views
commentsService.comments = $scope.comments;
console.log("Updated Comments", $scope.comments);
};
}])
You can build out the comments service to also make the db call, as that is an angular best practice (don't fetch external data from controllers, do it from factory/service). You'd build a method called getComments() or something, then call that from the controller.
See:
https://docs.angularjs.org/guide/services
Angularjs - Updating multiple instances of the same controller
Angularjs provides two-way binding so inserting
<div data-ng-controller="userComments">
{{comments}}
</div>
would update comments.
To have same data in entire application( that one defined by ng-app directive ), define a service:
You can inject service to whatever controller
Service data is the same in entire application.
Create service using service method of module.
var app = angular.module('myApp',[]).service('myService', function() {
this.comments = [];
});
Injecting service to controller:
.controller('MyController',['myService',function(myService){
this.addComments = function(data){
myService.comments.push(data);
}
this.getComments = function(){
return myService.comments;
};
}]);
This would keep data same across aplication, and you can also inject this service to another controllers.
Invoke later controller, which uses service:
<div ng-controller="MyController as mc">
{{mc.getComments()}}
</div>
and in another view, set:
<div ng-controller="MyController as mc">
<input type="text" ng-model="myComm"/>
<button type="submit" ng-click="mc.addComment(myComm)" value="Add comment"></button>
</div>
It sets service with new comment. myComm is variable.
ng-model is set with input text, user entered, and ng-click attribute executes on user click.
As final word, there are services provided with angular.
There is $http for network calls, $timeout for invoking things after specific time. You can use them for specific operations and also you can have your own services.
You could also use the events bundled in AngularJS in order to communicate the two instances. So every time the comments array has changes, you can trigger an custom event that the other controller listens and then update the comments array of the other controller.
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