Change data between controllers with service - angularjs

When I use this it works:`
angular.module('app').service('DataService', function() {
return {theme: "amelia"}
});
But when I use this, there is no update? Can you tell me the difference?
angular.module('app').service('DataService', function() {
return {
theme: function() {
return {theme: "amelia"}
}
};
});
Controller
$scope.settings = DataService.theme();
Jade
select.form-control(ng-model="settings.theme", ng-options="theme for theme in themes")
Is it possible to get the second way working? Because I will share more data then one Object!
Thank you!

The first version of the code calls the function once to instantiate the service. After that, because services are singletons in angular the function isn't called again, but rather the return value (a "static" object) is accessed in every controller that uses the service after that.
The second version, each controller you inject the service into calls the theme function, which instantiates a brand new object each time. You have now effectively mitigated the fact that the service is a singleton. This is why data will not be shared with the second set of code.
If you put a break point on the function call in each case and run your code you should see the first version called once while the second version will be called many times.
"Get It Working"...
You can't really make it work with a function call but if you need to share multiple data objects there isn't any reason not to nest them. You could very easily do something like:
angular.module('app').service('DataService', function() {
return {
dataObjects: [
{"type":"theme", "theme":"amelia"},
{"type":"user", "id":123, "name":"ABC"}
]};
});
In the example I added a second object which is a user object to make shared "dataObjects" array. To find a specific object in the "dataObjects" array, you could loop till you find the correct type ("theme", for example). If necessary, you could even nest one level deeper if you needed the objects to be pristine (without the added type attribute).
Hope that helps!

It should be theme: function().... inside your service. Replace "=" with ":".

Related

Service Method called 5 times from Angular Controller

This one has me confounded. I have looked far and wide and am out of ideas. In my searching, I discovered that one of the common reasons for multiple function calls on load is if you have a controller defined in routes and via the ngController directive. Checked this - I do not use ngController. I also checked my routes - seem in order. There are no $watch functions that could be causing $digest issues. This function is called one time, at the bottom of the function, and the console.log is logged out 5x...EVERY TIME. I have even set a $timer function and it still calls it 5x. Have tried creating a variable to only run if it hasn't been run before but it seems like it's all happening with the getQuotas() method. Any help would be greatly appreciated!
function getQuotas ()
{
console.log('getQuotas'); //This logs out 5x
UserService.getQuotas()
.then(function(res)
{
if (res.data.success)
{
quotaData = res.data.data;
getQuotas_success();
return true;
}
else
{
getQuotas_failure();
return false;
}
}, function (err)
{
getQuotas_failure();
return false;
});
}
getQuotas(); //Function is called here.
Solved it! I'm hopeful this will help others. There was a custom attribute directive on each of 4 input fields on this page. That particular directive was using the same controller as the page itself. So the controller was getting loaded a total of 5 times. Fortunately for me, this directive is now deprecated but I would probably redo it by either creating a directive-level controller and using the 'require' attribute in the directive's return object, pointing to the page-level controller, OR just have the data that needs to get passed between the page-level controller and the directive go through a service.

Angular controller function outside current controller

I have 4 controllers in my Angular 1.x app:
houseController
apartmentController
addressController
contactInfoController
With addressController & contactInfoController I update the address and contact info of a house or an apartment.
I work in the following way:
<div ng-controller="houseController" ng-init="getHouseInformation()>
{{house.contact_info.email}}
{{house.contact_info.mobile_phone_number}}
<a ng-controller="contactInfoController" ng-click="showContactInfoEdit(house.contact_info.id)">Edit</a>
</div>
When the update of the contact information is successfull within the contactInfoController, I want to update the house information getHouseInformation() in the houseController (read: make a new call to the API to get the updated information).
Since I have more than one (and in the future even more) house/apartment/... controllers, I need to have a solid way on how to 'refresh' the scope of these apartments, houses, ... on the fly.
What would be the best solution in my case?
Edit:
My scopes look like this:
// in houseController
$scope.house = {
id : 1,
title : "House title",
contact_info : {
email: '',
mobile_phone_number : ''
}
}
// in apartmentController
$scope.apartment = {
id : 1,
title : "Apartment title",
contact_info : {
email: '',
mobile_phone_number : ''
}
}
// in contactInfoController
$scope.contact_info : {
email: '',
mobile_phone_number : ''
}
So when updating the contact information scope, I'm not directly changing the house scope... Hope this helps.
I disagree with the other advice you got here - directives help you intelligently manipulate the DOM with your data but not necessarily share it between scopes, which is what you're asking about. As I wrote to you on Slack, what you seem to be looking for is a Service which will contain all your data (either declared in it or linked to an external file or API), and then injected into every controller that needs access to that data. That's one of the main uses for services!
For posterity, here's what I wrote to you on Slack:
"...You’re currently creating unrelated objects that don’t communicate - why should they? They’re in different scopes. You’re actually not getting any use out of Angular and could use vanilla Javascript for that:)
The idea is to use persistent data that is ​shared​ across your web app. $scope.anything will never communicate outside its scope unless you ​bind​ it to something outside the scope, like a service. Whether the service draws data from an external API or really just an object model that lies ​outside​ of Angular, (on your server/file structure, for example), or even defined within the service itself doesn’t matter (although the latter is far from a best practice) - but you need something persistent outside of the local scopes that's shared between your controllers/views.
Then there are a few ways to connect the dots. A very common (and probably the best) design pattern is to create a ​service​ (NOT directive! I don’t know why they gave you that advice) that encapsulates that data. So, for example,
myApp.service(‘dataModel', function(){
$scope.houses = {}; //an object containing ALL the houses, each one containing ALL the apartments, each apt.containing ALL the contact info, etc. etc.
});
Then in your controllers you pass the dataModel service, and then declare and link the local scope 'reference' of the same object to it, for example:
myApp.controller(‘buildingsView’, [dataModel, function(dataModel){
//and link to that service’s object locally:
$scope.houses = dataModel.houses;
}]);
Then, once you affect that model in one view/controller, the data in the other views/controllers will magically change too!
And ​that​ is the angular way:)
Hope this makes sense!
(To clarify, there are two issues here: getting your data INTO angular and then SHARING that same object by injecting the same service to your various controllers. That’s sort of the exact idea of services - they’re singletons, which means only one copy of them exists at any time and if they’re referred to more than once, it’ll always be to the same object, which is why manipulating it in one place will change it in another.)"
Put your data within a $scope variable, and make your controllers watch this varaible from scope. When the event is triggered, you can then do what you want.
$scope.houses = [];
$scope.$watch('houses', function(newValue, oldValue) {
// This is triggered when $scope.houses changes directly
// Add true as param to this function if you want to watch properties nested within the houses object
});
If houses is within a controller, use the following:
(in controller)
var self = this;
self.houses = [];
// This tells angular to watch the controller property houses instead of a scope variable
$scope.$watch(function(){return self.houses}, function(newValue, oldValue) {
// This is triggered when $scope.houses changes directly
// Add true as param to this function if you want to watch properties nested within the houses object
});
I suggest to use directive, then it's easier to exchange data. And that is the reason why directive exists. I try to explain how to build your use case with directives. Assume that you have one controller (houseController) and for every sub requirements you make a directive. One for the contact, one for the appartment and one for the address. Then you define your whole object inside houseController. You pass all necessary data as a parameter to the directive and you can access them from the houseController or from inside the directive, because of the two way data binding. In this way you don't have to exchange something or to call update functions. You adjuste just the object.
your html
<div ng-controller="houseController">
<contact data="house.contact"></contact>
<apartment data="house.apartment"></apartment>
...
</div>
house controller
$scope.house = {
apartment:{
floor: 1,
number:34,
...
},
contact:{
id:2,
email:"peter#test.com",
mobile_phone_number:"123456789",
...
},
...
}
contact directive
.directive('contact', function() {
return {
restrict: 'E',
scope: {
data: '='
},
link: function(scope, element, attrs) {
scope.showContactInfoEdit = function(id){
// do your stuff here
scope.data.name = "Peter";
}
},
templateUrl: 'contact.html'
};
});
contact.html
{{data.email}}
{{data.mobile_phone_number}}
<a ng-click="showContactInfoEdit(data.id)">Edit</a>
I made a plunkr: http://plnkr.co/edit/8tRMtgztaXRC3EKyQhcH?p=preview

dynamic header/menu in angularjs

While transitioning an existing angular site, I encountered an annoying problem. The initial symptom was that a certain controller was not running it's initialize function immediately following the login. I logged and I tracked, and eventually I realized it was a design flaw of the page. Essentially, index.html contains a <header>, <ng-view>, and <footer>. There are a couple of ng-if attributes that live in the header that I want to evaluate after the login, but since the view is the only thing that is reloaded, it was not reinitializing the header controller, and thus not updating the ng-if values.
Then I was reminded of ngInclude, which seems like the perfect solution, until I got it hooked up and realize that doesn't work either. It loads the template the first time, and doesn't reinitialize when the view changes. So then I got the bright idea of passing the HeaderController to another controller or service, and controlling this one stubborn boolean value through a proxy of sorts. That also didn't work. Then I tried putting a function and a boolean into another service, and mirroring that property in the header controller, but thus far I have not gotten this working.
I have done plenty of research about multiple views in the index, and so far I hear a lot about this ui-router, but I'm still not convinced that is the way I want to go. It does not seem to be a simple solution. I have not tried putting the ng-include into the templates yet either, because then I feel like that is going back in time to when we had to update 100 pages every time we changed the menu.
I lost a whole day to this. If anyone could tell me how to trigger the evaluation of this one property in my header controller which I would like to live outside the other templates, please let me know!
Ok so you need to know in your HeaderController when the view has reloaded. There's a number of ways of doing this but the easier and maybe the more correct in this particular case is with an event.
So when you are refreshing the view you just do this, let's say you need the new value of ob1 and ob2 variables.
// ViewController
$rootScope.$emit('viewRefresh', {ob1: 'newvalue1', ob2: 'newvalue2'});
And in your HeaderController you need to listen for that event, and set on your $scope the new values for those attrs (if you're not using controller as syntax).
// HeaderController
$rootScope.$on('viewRefresh', function onRefresh(event, data) {
$scope.ob1 = data.ob1;
$scope.ob2 = data.ob2;
})
Another Solution
Sharing a Promise through a Service (using $q)
function HeaderService($q) {
var defer = $q.defer();
return {
getPromise: function() {return defer.promise},
notify: function(data) {defer.notify(data)}
}
}
function HeaderController(HeaderService) {
var vm = this;
HeaderService.getPromise().then(function(data) {
vm.ob1 = data.ob1;
vm.ob2 = data.ob2;
})
}
function ViewController(HeaderService) {
var data = {ob1: 'newvalue1', ob2: 'newvalue2'};
HeaderService.notify(data)
}

Getting hold of loaded javascript objects in Protractor

In our application we load requirejs, which in return loads angularjs, and also other javascript modules. I am wondering if there any way to get hole of these LOADED modules (angularjs, javascript modules) in protractor test. Note, we want the instance that is loaded by the browser when running Protractor, we don't want to create instance by ourselves.
Any suggestion or example?
Thanks in advance.
Nick Tomlin's answer is what you can do if a module returns serializable data structure as a value. You call require and call with the module's value the callback that executeAsyncScript gives you to allow returning asynchronous values. This will work, for instance, if your module returns "foo" or { foo: 'bar' } or structures that are generally serializable.
However, it won't always work. Complex modules cannot be retrieved that way. Roughly speaking you should expect what you send through executeScript and executeAsyncScript and what they return to have the same limitations as JSON.stringify does. One major exception is that Selenium will wrap DOM objects returned from these calls into a structure that allows to identify them on the script side, and that allows passing them back to the browser. (Then again, there are limitations there too. This is why you get stale element exceptions, for instance.)
If you try to retrieve modules that export functions, you'll probably get something but it won't be complete. Try this, for instance:
browser.executeAsyncScript(function () {
arguments[0]({ foo: function () {}});
}).then(function (value) {
console.log(value);
});
The output I get is:
Object { foo: Object {} }
The function has been turned into an empty object.
I do not use angular with require.js, but i'm assuming you could access the require'd angular the same way you would in a module:
var pageAngular = browser.driver.executeAsyncScript(function () {
var callback = arguments[arguments.length - 1];
require(['angular'], function (angular) {
callback(angular);
})
});
The use of executeAsync is necessary here, since AMD modules are loaded asynchronously.
Do note that as #louis noted, the return of executeAsyncScript is going to be a serialized object, not the "live" instance of angular. If you need to interact with angular within the context of your page, you should do so within the callback torequire.
Something like this should do it:
var angular = browser.driver.executeScript("return window.angular;");

AngularJS custom model objects with methods?

When you have a JSON $resource how can you cast the resulting objects into more specific objects once obtained?
For example, right now they come back as an Array of Objects, but I want them to come back as an Array of "Appointment" objects, so that I can have some methods on that Appointment object which would answer questions about that Appointment object. Ex: Does This Appointment have any services associated with it? Is This appointment in the morning or afternoon?
At first I thought transformResponse hook would work from ngResource, but that doesn't appear to work. The return from that is not the actual objects. Seems that with that function you can only modify the actual data prior to the JSON parsing.
Finally, I question if this is even a proper angularJS technique? Or should these helper methods just show up in a controller or some other module and accept the object to work upon? I just think its cleaner to have them wrapped up in the object, but I admit that I'm not very experienced in angularJS.
If you're using a factory and want to add a function you can for example add the function to the prototype of the returned item (DEMO):
app.factory('Appointment', ['$resource', function($resource) {
var Item = $resource('appointments.json',{/*bindings*/},{/*actions*/});
Item.prototype.hasServices = function() {
if(this.services.length > 0) return true;
else return false;
};
Item.prototype.partOfDay = function() {
if(this.time.split(':')[0] > 12) return "afternoon";
else return "morning";
};
return Item;
}]);
And then access it on your resource in the controller:
$scope.appointments = Appointment.query({}, function() {
console.log($scope.appointments[0].partOfDay())
});
Or directly in the view inside for example an ng-repeat:
{{appointment.partOfDay()}}
To answer your last question, I think the above solution is a proper angularjs technique.
As soon as you have functions associated with a specific resource type it's in my opinion the best to directly append them to the respective resource object. Why should you create helper functions in the controller when you have to pass the resource as a parameter and additionaly the functions may be used in multiple controllers or scopes?!

Resources