Do not understand the controllers in Angular JS - javascript

This is a question I am not used to ask, but I feel like this is the only way to understand what I am struggling with. As shown below, there are two functions app.controller() and app.factory(), to me both of them seems to be equal even though if I delete one part the function does not perform its intended task.
Link to plunker: click here (Relevant file is script.js)
What is the difference between these two functions, why cant I have only one of them? I know there must be a simple explanation.
app.controller('MainCtrl', ['$scope', 'ItemsService', function ($scope, ItemsService) {
$scope.newItem = { PlateNumber: '', Description: '', Price: 0 };
$scope.currentItem = null;
$scope.items = ItemsService.getItems();
$scope.addItem = function () {
ItemsService.addItem(angular.copy($scope.newItem));
$scope.newItem = { PlateNumber: '', Description: '', Price: 0 };
};
$scope.updateItem = function (id) {
ItemsService.updateItem(id);
};
$scope.removeItem = function (id) {
ItemsService.removeItem(id);
};
}]);
vs
app.factory('ItemsService', ['$firebase', 'FIREBASE_URI', function ($firebase, FIREBASE_URI) {
var ref = new Firebase(FIREBASE_URI);
var items = $firebase(ref);
var getItems = function () {
return items;
};
var addItem = function (item) {
items.$add(item);
};
var updateItem = function (id) {
items.$save(id);
};
var removeItem = function (id) {
items.$remove(id);
};
return {
getItems: getItems,
addItem: addItem,
updateItem: updateItem,
removeItem: removeItem
}
}]);

Controllers are only instantiated when you need one, (you use ng-controller or controllerAs). So every time you switch to a different route or page, Angular instantiates a controller (it can be the same one that's cleaned up, for example if you refresh the page.)
Angular providers, of which there are a few main kinds - Factory being one of them, are a way to keep data around for the lifetime of the app or even used to pass data between different controllers. The scope of your question is more like: Provider vs Controller, not Factory(which is one type) vs Controller.
In your example above, you have ONE controller, what if you have many? How can you pass data or utility functions to many controllers without writing the same code over and over again? With Providers for one!
Here are some good links for you to check out:
Angular Provider Docs
Blog post by Tyler McGinnis explaining the above further
JSFiddle with Example of a Factory vs a Service

Related

Passing data between controller via service won't work

I'm trying all the approaches passing data between controllers using service, factory or broadcast. None of them works for me. I follow the exact solution online, but still unfortunate. I placed service inside my app.js.
App.JS
myApp.service('customService', [function () {
this.list = [];
this.setObject = function (o) {
this.list.push(o);
},
this.getObject = function () {
return this.list;
}
}]);
Controller #1
myApp.controller('Controller1', function ($scope, customService) {
customService.setObject({..});
$window.open("/controller2", '_blank');
}
Controller #2
myApp.controller('Controller2', function ($scope, customService) {
console.log(customService.getObject()); // Returns []
}
Problem
It returns [] on controller 2 from controller 1, instead of object data.
You should modify your service for storing a object under some specific key, and then retrieve it later given that key. You can define these keys whatever you like. I defined them in the same service so I can reuse them through all controllers. Something like this
Service
myApp.service('customService', [function () {
this.keys = {"foo": "foo", "bar": "bar"};
this.list = {};
this.setObject = function (obj, key) {
this.list[ley] = obj;
},
this.getObject = function (key) {
return this.list[key];
}
}]);
Controller #1
myApp.controller('Controller1', function ($scope, customService) {
customService.setObject({"propX": "propX"}, customService.foo);
//$window.open("/controller2", '_blank');
/* I encourage you to use something like ngRoute here for navigating
* so, you should do something like $location.path('/controller2');
*/
}
Controller #2
myApp.controller('Controller2', function ($scope, customService) {
console.log(customService.getObject(customService.foo));
}
Are your controllers in the same page ?
Angular.js only works and keeps data on a single page. If your page reloads
(as you seem to indicate when you say "express.js loads the next
page", then it reinitialized everything.
You should either:
look at how to use Angular.js routing
(http://docs.angularjs.org/tutorial/step_07) so that you stay on the
same page. use something to persist your data, such as localstorage.
Find out more: http://diveintohtml5.info/storage.html
ref : Using angular service to share data between controllers
have you used routing? if u use then your code should be work.

How to DRY up $scope-manipulating code across controllers in angularJS

Pardon if this question is a total blow-off... Just getting warmed-up into the world angularJS.
I have these two controllers: seekerController and wizardController...
Inside the wizardController, I have a chat Scope object, and I have implemented a bunch of functions that are manipulating this chat Scope object.
Going back to the other controller now, ( seekerController ), I discover that I need to have basically a direct replica of this chat Scope object and all the other functions manipulating it as I have inside wizardController
The obvious way is just to copy all these into my other controller, and my work is done under a minute, but then I'll have a lot of repeated stuffs everywhere...
So: I'm looking for a way where I can have this(the code) in a single place, but still be able to have access to this chat Scope object from both controllers, as well as all the other functions working seamlessly.
Update - add code samples:
//seekerController
angular.module('cg.seeker', [])
.controller('SeekerController', ['$scope', 'seekerService', 'timeService', 'chatService', '$stateParams', 'toastr',
function ($scope, seekerService, timeService, chatService, $stateParams, toastr) {
...
// THE CHAT BUSINESS
$scope.chat = { close: true };
chatService.unreadCount(function(count){
$scope.chat.unreadCount = count;
$scope.$apply();
});
chatService.listDialogs( function (dialogList) {
$scope.chat.dialogList = dialogList.items;
$scope.$apply();
} );
$scope.endChat = function () {
$scope.chat.close = true;
}
$scope.chatBox = function (dialogId, occupants_ids) {
$scope.chat.opponentId = getOpponentId(occupants_ids);
chatService.getMessages( dialogId, function (messageList) {
$scope.chat.messages = messageList.items;
$scope.chat.close = false;
$scope.$apply();
});
}
var getOpponentId = function (opponentId) {
if(typeof(opponentId) != 'object') {
return opponentId;
} else {
return opponentId.filter(function(x) { return x != $scope.seeker.chat_user.chat_id_string; })[0];
}
}
$scope.sendMsg = function (opponentId) {
var msg = {
type: 'chat',
body: $scope.chat.msg,
extension: {
save_to_history: 1,
}
};
chatService.sendMsg(opponentId, msg);
$scope.chat.msg = '';
}
...
I now have an exact replica of the above code in a second controller WizardController. Exactly same, with no changes... and even a third controller have some of these, though not all.
The next level of abstraction to angularjs controllers are
Factory
Service
Provider
You could use a service called maybe chatService which could contain the common code. You can inject the service into any controller which needs the common functionality and invoke the methods on the Service.
Do note that you could use any of the above three options even though I have mentioned just Service in the above statement.
EDIT 1:
You could move the common parts of the code from Controller to Service.
For example:- You could move the construction of msg object from controller to chatService. You controller would be simply -
$scope.sendMsg = function (opponentId) {
chatService.sendMsg(opponentId);
$scope.chat.msg = '';
}
And your chatService would be doing the hard-work.
$chatService.sendMsg = function (opponentId) {
var msg = {
type: 'chat',
body: $scope.chat.msg,
extension: {
save_to_history: 1,
}
};
sendMsg(opponentId, msg);
}
After simplifying the Controllers you could revisit to see if you could use only one controller instead of 3 as they seem to be doing similar function.

Forcing a component to redraw from a different component in Mithril.js

I have 2 components - addProjectForm and listProjects. They are both nested components inside the root module. Whenever I add a project using the form, I want it to appear in the list straight away.
To achieve this, I had to pass down the controller instance to each component like this:
var RootComponent = {};
rootComponent.controller = function() {
this.example = 'test variable';
}
rootComponent.view = function(ctrl) {
return [
m.component(addProjectForm, ctrl),
m.component(listProjects, ctrl)
];
}
and then the listProjectscomponent for example, looks like this:
var listProjects = {
controller: function(root) {
this.root = root;
},
view: function(ctrl) {
console.log(ctrl.root.example);
}
};
So this way I keep calling methods on the top level, but I don't quite like passing down the controller instance like this. Is there any other way I should be doing it?
I think this is what you're looking for:
Mithril.js: Should two child components talk to each other through their parent's controller?
A newer way of solving this common problem is to use a Flux like architecture developed by Facebook:
https://facebook.github.io/flux/
Writing your own dispatcher is semi-trivial. Here's an example that someone else built alongside Mithril:
https://gist.github.com/MattMcFarland/25fb4f0241530d2f421a
The downside with this approach is it would be somewhat anti-Flux to use m.withAttr, as views aren't supposed to write directly to models in the dispatcher paradigm.
The problem you have is the difference between passing by reference or by value. In JS all primitive types are passed by value. Thats why you can't pass the string directly since it's cloned during pass. You have multiple options here:
You can use m.prop and just pass the variable down to the components, m.props stores the value in function that is always passed by reference.
var RootComponent = {};
rootComponent.controller = function() {
this.example = m.prop('test variable');
}
rootComponent.view = function(ctrl) {
return [
m.component(addProjectForm, ctrl.example),
m.component(listProjects, ctrl.example)
];
}
If the variable is an array, it will be passed by reference anyways.
Second option is to keep the list in the root context and add a callback to the second component.
var RootComponent = {};
rootComponent.controller = function() {
var projects = this.projects = [];
this.addProject = function(project) {
projects.push(project);
}
}
rootComponent.view = function(ctrl) {
return [
m.component(addProjectForm, {
onsubmit: ctrl.addProject
}),
m.component(listProjects, ctrl.projects)
];
}

Is this a good way to keep data in sync between two controllers?

I have got a page layout with two controllers at the same time, one holds the data displayed as kind of side navigation, based on data stored the browsers local storage and at least one other controller, which is bind to a route and view.
I created this little wire frame graphic below which show the page layout:
The second controller is used for manipulating the local stored data and performs actions like adding a new item or deleting an existing one. My goal is to keep the data in sync, if an item got added or deleted by the 'ManageListCrtl' the side navigation using the 'ListCtrl' should get updated immediately.
I archived this by separating the local storage logic into a service which performs a broadcast when the list got manipulated, each controller listens on this event and updates the scope's list.
This works fine, but I'm not sure if there is the best practice.
Here is a stripped down version of my code containing just the necessary parts:
angular.module('StoredListExample', ['LocalObjectStorage'])
.service('StoredList', function ($rootScope, LocalObjectStorage) {
this.add = function (url, title) {
// local storage add logic
$rootScope.$broadcast('StoredList', list);
};
this.delete = function (id) {
// local storage delete logic
$rootScope.$broadcast('StoredList', list);
};
this.get = function () {
// local storage get logic
};
});
angular.module('StoredListExample')
.controller('ListCtrl', function ($scope, StoredList) {
$scope.list = StoredList.get();
$scope.$on('StoredList', function (event, data) {
$scope.list = data;
});
});
angular.module('StoredListExample')
.controller('ManageListCtrl', function ($scope, $location, StoredList) {
$scope.list = StoredList.get();
$scope.add = function () {
StoredList.add($scope.url, $scope.title);
$location.path('/manage');
};
$scope.delete = function (id) {
StoredList.delete(id);
};
$scope.$on('StoredList', function (event, data) {
$scope.list = data;
});
});
I don't see anything wrong with doing it this way. Your other option of course is to just inject $rootScope into both controllers and pub/sub between them with a $rootScope.$broadcast and a $rootScope.$on.
angular.module('StoredListExample')
.controller('ListCtrl', function ($scope, $rootScope) {
$scope.list = [];
$rootScope.$on('StoredList', function (event, data) {
$scope.list = data;
});
});
angular.module('StoredListExample')
.controller('ManageListCtrl', function ($scope, $rootScope, $location) {
$scope.list = [];
$scope.add = function () {
//psuedo, not sure if this is what you'd be doing...
$scope.list.push({ url: $scope.url, title: $scope.title});
$scope.storedListUpdated();
$location.path('/manage');
};
$scope.delete = function (id) {
var index = $scope.list.indexOf(id);
$scope.list.splice(index, 1);
$scope.storedListUpdated();
};
$scope.storedListUpdated = function () {
$rootScope.$broadcast('StoredList', $scope.list);
};
});
Additionally, you can achieve this in a messy but fun way by having a common parent controller. Whereby you would $emit up a 'StoredListUpdated' event from 'ManageListCtrl' to the parent controller, then the parent controller would $broadcast the same event down to the 'ListCtrl'. This would allow you to avoid using $rootScope, but it would get pretty messy in terms of readability as you add more events in this way.
It is always a better practice to use a common service that is a singleton for sharing the data between the 2 controllers - just make sure you use only references and not creating a local object in one of the controllers that should actually be in the service

Maintain model of scope when changing between views in AngularJS

I am learning AngularJS. Let's say I have /view1 using My1Ctrl, and /view2 using My2Ctrl; that can be navigated to using tabs where each view has its own simple, but different form.
How would I make sure that the values entered in the form of view1 are not reset, when a user leaves and then returns to view1 ?
What I mean is, how can the second visit to view1 keep the exact same state of the model as I left it ?
I took a bit of time to work out what is the best way of doing this. I also wanted to keep the state, when the user leaves the page and then presses the back button, to get back to the old page; and not just put all my data into the rootscope.
The final result is to have a service for each controller. In the controller, you just have functions and variables that you dont care about, if they are cleared.
The service for the controller is injected by dependency injection. As services are singletons, their data is not destroyed like the data in the controller.
In the service, I have a model. the model ONLY has data - no functions -. That way it can be converted back and forth from JSON to persist it. I used the html5 localstorage for persistence.
Lastly i used window.onbeforeunload and $rootScope.$broadcast('saveState'); to let all the services know that they should save their state, and $rootScope.$broadcast('restoreState') to let them know to restore their state ( used for when the user leaves the page and presses the back button to return to the page respectively).
Example service called userService for my userController :
app.factory('userService', ['$rootScope', function ($rootScope) {
var service = {
model: {
name: '',
email: ''
},
SaveState: function () {
sessionStorage.userService = angular.toJson(service.model);
},
RestoreState: function () {
service.model = angular.fromJson(sessionStorage.userService);
}
}
$rootScope.$on("savestate", service.SaveState);
$rootScope.$on("restorestate", service.RestoreState);
return service;
}]);
userController example
function userCtrl($scope, userService) {
$scope.user = userService;
}
The view then uses binding like this
<h1>{{user.model.name}}</h1>
And in the app module, within the run function i handle the broadcasting of the saveState and restoreState
$rootScope.$on("$routeChangeStart", function (event, next, current) {
if (sessionStorage.restorestate == "true") {
$rootScope.$broadcast('restorestate'); //let everything know we need to restore state
sessionStorage.restorestate = false;
}
});
//let everthing know that we need to save state now.
window.onbeforeunload = function (event) {
$rootScope.$broadcast('savestate');
};
As i mentioned this took a while to come to this point. It is a very clean way of doing it, but it is a fair bit of engineering to do something that i would suspect is a very common issue when developing in Angular.
I would love to see easier, but as clean ways to handle keeping state across controllers, including when the user leaves and returns to the page.
A bit late for an answer but just updated fiddle with some best practice
jsfiddle
var myApp = angular.module('myApp',[]);
myApp.factory('UserService', function() {
var userService = {};
userService.name = "HI Atul";
userService.ChangeName = function (value) {
userService.name = value;
};
return userService;
});
function MyCtrl($scope, UserService) {
$scope.name = UserService.name;
$scope.updatedname="";
$scope.changeName=function(data){
$scope.updateServiceName(data);
}
$scope.updateServiceName = function(name){
UserService.ChangeName(name);
$scope.name = UserService.name;
}
}
$rootScope is a big global variable, which is fine for one-off things, or small apps.
Use a service if you want to encapsulate your model and/or behavior (and possibly reuse it elsewhere). In addition to the google group post the OP mentioned, see also https://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion.
Angular doesn't really provide what you are looking for out of the box. What i would do to accomplish what you're after is use the following add ons
UI Router & UI Router Extras
These two will provide you with state based routing and sticky states, you can tab between states and all information will be saved as the scope "stays alive" so to speak.
Check the documentation on both as it's pretty straight forward, ui router extras also has a good demonstration of how sticky states works.
I had the same problem, This is what I did:
I have a SPA with multiple views in the same page (without ajax), so this is the code of the module:
var app = angular.module('otisApp', ['chieffancypants.loadingBar', 'ngRoute']);
app.config(['$routeProvider', function($routeProvider){
$routeProvider.when('/:page', {
templateUrl: function(page){return page.page + '.html';},
controller:'otisCtrl'
})
.otherwise({redirectTo:'/otis'});
}]);
I have only one controller for all views, but, the problem is the same as the question, the controller always refresh data, in order to avoid this behavior I did what people suggest above and I created a service for that purpose, then pass it to the controller as follows:
app.factory('otisService', function($http){
var service = {
answers:[],
...
}
return service;
});
app.controller('otisCtrl', ['$scope', '$window', 'otisService', '$routeParams',
function($scope, $window, otisService, $routeParams){
$scope.message = "Hello from page: " + $routeParams.page;
$scope.update = function(answer){
otisService.answers.push(answers);
};
...
}]);
Now I can call the update function from any of my views, pass values and update my model, I haven't no needed to use html5 apis for persistence data (this is in my case, maybe in other cases would be necessary to use html5 apis like localstorage and other stuff).
An alternative to services is to use the value store.
In the base of my app I added this
var agentApp = angular.module('rbAgent', ['ui.router', 'rbApp.tryGoal', 'rbApp.tryGoal.service', 'ui.bootstrap']);
agentApp.value('agentMemory',
{
contextId: '',
sessionId: ''
}
);
...
And then in my controller I just reference the value store. I don't think it holds thing if the user closes the browser.
angular.module('rbAgent')
.controller('AgentGoalListController', ['agentMemory', '$scope', '$rootScope', 'config', '$state', function(agentMemory, $scope, $rootScope, config, $state){
$scope.config = config;
$scope.contextId = agentMemory.contextId;
...
Solution that will work for multiple scopes and multiple variables within those scopes
This service was based off of Anton's answer, but is more extensible and will work across multiple scopes and allows the selection of multiple scope variables in the same scope. It uses the route path to index each scope, and then the scope variable names to index one level deeper.
Create service with this code:
angular.module('restoreScope', []).factory('restoreScope', ['$rootScope', '$route', function ($rootScope, $route) {
var getOrRegisterScopeVariable = function (scope, name, defaultValue, storedScope) {
if (storedScope[name] == null) {
storedScope[name] = defaultValue;
}
scope[name] = storedScope[name];
}
var service = {
GetOrRegisterScopeVariables: function (names, defaultValues) {
var scope = $route.current.locals.$scope;
var storedBaseScope = angular.fromJson(sessionStorage.restoreScope);
if (storedBaseScope == null) {
storedBaseScope = {};
}
// stored scope is indexed by route name
var storedScope = storedBaseScope[$route.current.$$route.originalPath];
if (storedScope == null) {
storedScope = {};
}
if (typeof names === "string") {
getOrRegisterScopeVariable(scope, names, defaultValues, storedScope);
} else if (Array.isArray(names)) {
angular.forEach(names, function (name, i) {
getOrRegisterScopeVariable(scope, name, defaultValues[i], storedScope);
});
} else {
console.error("First argument to GetOrRegisterScopeVariables is not a string or array");
}
// save stored scope back off
storedBaseScope[$route.current.$$route.originalPath] = storedScope;
sessionStorage.restoreScope = angular.toJson(storedBaseScope);
},
SaveState: function () {
// get current scope
var scope = $route.current.locals.$scope;
var storedBaseScope = angular.fromJson(sessionStorage.restoreScope);
// save off scope based on registered indexes
angular.forEach(storedBaseScope[$route.current.$$route.originalPath], function (item, i) {
storedBaseScope[$route.current.$$route.originalPath][i] = scope[i];
});
sessionStorage.restoreScope = angular.toJson(storedBaseScope);
}
}
$rootScope.$on("savestate", service.SaveState);
return service;
}]);
Add this code to your run function in your app module:
$rootScope.$on('$locationChangeStart', function (event, next, current) {
$rootScope.$broadcast('savestate');
});
window.onbeforeunload = function (event) {
$rootScope.$broadcast('savestate');
};
Inject the restoreScope service into your controller (example below):
function My1Ctrl($scope, restoreScope) {
restoreScope.GetOrRegisterScopeVariables([
// scope variable name(s)
'user',
'anotherUser'
],[
// default value(s)
{ name: 'user name', email: 'user#website.com' },
{ name: 'another user name', email: 'anotherUser#website.com' }
]);
}
The above example will initialize $scope.user to the stored value, otherwise will default to the provided value and save that off. If the page is closed, refreshed, or the route is changed, the current values of all registered scope variables will be saved off, and will be restored the next time the route/page is visited.
You can use $locationChangeStart event to store the previous value in $rootScope or in a service. When you come back, just initialize all previously stored values. Here is a quick demo using $rootScope.
var app = angular.module("myApp", ["ngRoute"]);
app.controller("tab1Ctrl", function($scope, $rootScope) {
if ($rootScope.savedScopes) {
for (key in $rootScope.savedScopes) {
$scope[key] = $rootScope.savedScopes[key];
}
}
$scope.$on('$locationChangeStart', function(event, next, current) {
$rootScope.savedScopes = {
name: $scope.name,
age: $scope.age
};
});
});
app.controller("tab2Ctrl", function($scope) {
$scope.language = "English";
});
app.config(function($routeProvider) {
$routeProvider
.when("/", {
template: "<h2>Tab1 content</h2>Name: <input ng-model='name'/><br/><br/>Age: <input type='number' ng-model='age' /><h4 style='color: red'>Fill the details and click on Tab2</h4>",
controller: "tab1Ctrl"
})
.when("/tab2", {
template: "<h2>Tab2 content</h2> My language: {{language}}<h4 style='color: red'>Now go back to Tab1</h4>",
controller: "tab2Ctrl"
});
});
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular-route.js"></script>
<body ng-app="myApp">
Tab1
Tab2
<div ng-view></div>
</body>
</html>

Categories