Angular: is it possible to share data between scopes? - javascript

my question is if i can share scopes, for example
i have one function
$scope.init = function(){
$http({
url: url_for('account/getinfo'),
method: "POST",
data: fields
}).success(function (data, status, headers, config) {
$scope.locations = data.stores;
$scope.currentLocation = data.stores.filter(function(store){
return store.mpos_id == $scope.mposid;
});
if ($scope.currentLocation.length > 0) {
$scope.currentLocation = $scope.currentLocation[0];
}
}).error(function (data, status, headers, config) {
});
};
the $scope.currentLocation is an Object!
can i use this obj data in other function?
tried with angular.copy and extend, no success
$scope.getInfo = function(){
$scope.currentLocationData = angular.copy($scope.currentLocation);
}

Services in Angular are singletons, so you could store this data in a service and inject it wherever you need it. It's already considered best practice to separate your data requests into services, so you'd be creating a service anyways.
Here's an example:
First separate your data retrieval logic into a service.
angular.module('app').service('accountDataService', AccountDataService);
function AccountDataService($http){
var service = this;
service.getInfo = function(fields){
return $http.post(url_for('account/getinfo'), fields)
.then(function(response){ return response.data.stores; });
}
}
Then create an account service to share the retrieved data between controllers/components:
angular.module('app').service('accountService', AccountService);
function AccountService(accountDataService){
var service = this;
service.currentLocation = {};
service.locations = [];
service.init = function(fields, mposid){
accountDataService.getInfo(fields).then(function(stores){
service.locations = stores;
var currentLocations = stores.filter(function(store){
return store.mpos_id == mposid;
});
if (currentLocations.length > 0) {
service.currentLocation = currentLocations[0];
}
});
}
}
Now all you have to do in your controllers is inject the accountService.
angular.module('app').run(function(accountService){
accountService.init({ /* your fields */ }, '[your mposid]');
});
angular.module('app').controller('myController', MyController);
function MyController($scope, accountService){
$scope.currentLocation = accountService.currentLocation;
}
Running the init function inside run(..) is just for example. You'd preferably run this in a route resolve function or something like that due to the async nature of the init function.
Bonus note
Avoid using the $scope to pass variables to the view. Use the controllerAs syntax instead. You can check the documentation for more info on how it works, or I suggest you to check out the angular 1.x style guide from John Papa.

Definitely you can copy like that i will work. you can check in below example.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>
<div ng-app ="app" ng-controller ="ctrl">
{{currentLocationData}}
</div>
<script>
angular.module('app', []).controller('ctrl', function($scope){
$scope.currentLocation = 'testing angular copy';
$scope.currentLocationData = angular.copy($scope.currentLocation);
})
</script>
</body>
</html>

If you are looking to share data between two controllers / directives, then you should use a service and inject it in both the controllers / directives.
If you are looking to just create a new copy of / clone the object, then you can use JSON.parse(JSON.stringify($scope.currentLocation))
Hope this helps!

You can access that object in another scope if and only if that scope is a child not isolated of the previous one. In this case your child scope will inherit all the properties of the parent scope too.
So if that code is the scope of a controller, and you have another controller as child of that one, the child one can access that property.
Although it works, a lot of times that's not the best practice to develop things using AngularJS. It will force a dependency among the two controllers and if you will change your code, you could lose the inheritance and break your code.
In these cases the best solution is to define a service which holds the value of the currentLocation. Then inject this service wherever you need to access this value.
I hope it makes sense

Related

Unable to add $scope to service in angularjs? [duplicate]

I have a Service:
angular.module('cfd')
.service('StudentService', [ '$http',
function ($http) {
// get some data via the $http
var path = 'data/people/students.json';
var students = $http.get(path).then(function (resp) {
return resp.data;
});
//save method create a new student if not already exists
//else update the existing object
this.save = function (student) {
if (student.id == null) {
//if this is new student, add it in students array
$scope.students.push(student);
} else {
//for existing student, find this student using id
//and update it.
for (i in students) {
if (students[i].id == student.id) {
students[i] = student;
}
}
}
};
But when I call save(), I don't have access to the $scope, and get ReferenceError: $scope is not defined. So the logical step (for me), is to provide save() with the $scope, and thus I must also provide/inject it to the service. So if I do that like so:
.service('StudentService', [ '$http', '$scope',
function ($http, $scope) {
I get the following error:
Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope <-
StudentService
The link in the error (wow that is neat!) lets me know it is injector related, and might have to do with order of declaration of the js files. I have tried reordering them in the index.html, but I think it is something more simple, such as the way I am injecting them.
Using Angular-UI and Angular-UI-Router
The $scope that you see being injected into controllers is not some service (like the rest of the injectable stuff), but is a Scope object. Many scope objects can be created (usually prototypically inheriting from a parent scope). The root of all scopes is the $rootScope and you can create a new child-scope using the $new() method of any scope (including the $rootScope).
The purpose of a Scope is to "glue together" the presentation and the business logic of your app. It does not make much sense to pass a $scope into a service.
Services are singleton objects used (among other things) to share data (e.g. among several controllers) and generally encapsulate reusable pieces of code (since they can be injected and offer their "services" in any part of your app that needs them: controllers, directives, filters, other services etc).
I am sure, various approaches would work for you. One is this:
Since the StudentService is in charge of dealing with student data, you can have the StudentService keep an array of students and let it "share" it with whoever might be interested (e.g. your $scope). This makes even more sense, if there are other views/controllers/filters/services that need to have access to that info (if there aren't any right now, don't be surprised if they start popping up soon).
Every time a new student is added (using the service's save() method), the service's own array of students will be updated and every other object sharing that array will get automatically updated as well.
Based on the approach described above, your code could look like this:
angular.
module('cfd', []).
factory('StudentService', ['$http', '$q', function ($http, $q) {
var path = 'data/people/students.json';
var students = [];
// In the real app, instead of just updating the students array
// (which will be probably already done from the controller)
// this method should send the student data to the server and
// wait for a response.
// This method returns a promise to emulate what would happen
// when actually communicating with the server.
var save = function (student) {
if (student.id === null) {
students.push(student);
} else {
for (var i = 0; i < students.length; i++) {
if (students[i].id === student.id) {
students[i] = student;
break;
}
}
}
return $q.resolve(student);
};
// Populate the students array with students from the server.
$http.get(path).then(function (response) {
response.data.forEach(function (student) {
students.push(student);
});
});
return {
students: students,
save: save
};
}]).
controller('someCtrl', ['$scope', 'StudentService',
function ($scope, StudentService) {
$scope.students = StudentService.students;
$scope.saveStudent = function (student) {
// Do some $scope-specific stuff...
// Do the actual saving using the StudentService.
// Once the operation is completed, the $scope's `students`
// array will be automatically updated, since it references
// the StudentService's `students` array.
StudentService.save(student).then(function () {
// Do some more $scope-specific stuff,
// e.g. show a notification.
}, function (err) {
// Handle the error.
});
};
}
]);
One thing you should be careful about when using this approach is to never re-assign the service's array, because then any other components (e.g. scopes) will be still referencing the original array and your app will break.
E.g. to clear the array in StudentService:
/* DON'T DO THAT */
var clear = function () { students = []; }
/* DO THIS INSTEAD */
var clear = function () { students.splice(0, students.length); }
See, also, this short demo.
LITTLE UPDATE:
A few words to avoid the confusion that may arise while talking about using a service, but not creating it with the service() function.
Quoting the docs on $provide:
An Angular service is a singleton object created by a service factory. These service factories are functions which, in turn, are created by a service provider. The service providers are constructor functions. When instantiated they must contain a property called $get, which holds the service factory function.
[...]
...the $provide service has additional helper methods to register services without specifying a provider:
provider(provider) - registers a service provider with the $injector
constant(obj) - registers a value/object that can be accessed by providers and services.
value(obj) - registers a value/object that can only be accessed by services, not providers.
factory(fn) - registers a service factory function, fn, that will be wrapped in a service provider object, whose $get property will contain the given factory function.
service(class) - registers a constructor function, class that will be wrapped in a service provider object, whose $get property will instantiate a new object using the given constructor function.
Basically, what it says is that every Angular service is registered using $provide.provider(), but there are "shortcut" methods for simpler services (two of which are service() and factory()).
It all "boils down" to a service, so it doesn't make much difference which method you use (as long as the requirements for your service can be covered by that method).
BTW, provider vs service vs factory is one of the most confusing concepts for Angular new-comers, but fortunately there are plenty of resources (here on SO) to make things easier. (Just search around.)
(I hope that clears it up - let me know if it doesn't.)
Instead of trying to modify the $scope within the service, you can implement a $watch within your controller to watch a property on your service for changes and then update a property on the $scope. Here is an example you might try in a controller:
angular.module('cfd')
.controller('MyController', ['$scope', 'StudentService', function ($scope, StudentService) {
$scope.students = null;
(function () {
$scope.$watch(function () {
return StudentService.students;
}, function (newVal, oldVal) {
if ( newValue !== oldValue ) {
$scope.students = newVal;
}
});
}());
}]);
One thing to note is that within your service, in order for the students property to be visible, it needs to be on the Service object or this like so:
this.students = $http.get(path).then(function (resp) {
return resp.data;
});
Well (a long one) ... if you insist to have $scope access inside a service, you can:
Create a getter/setter service
ngapp.factory('Scopes', function (){
var mem = {};
return {
store: function (key, value) { mem[key] = value; },
get: function (key) { return mem[key]; }
};
});
Inject it and store the controller scope in it
ngapp.controller('myCtrl', ['$scope', 'Scopes', function($scope, Scopes) {
Scopes.store('myCtrl', $scope);
}]);
Now, get the scope inside another service
ngapp.factory('getRoute', ['Scopes', '$http', function(Scopes, $http){
// there you are
var $scope = Scopes.get('myCtrl');
}]);
Services are singletons, and it is not logical for a scope to be injected in service (which is case indeed, you cannot inject scope in service). You can pass scope as a parameter, but that is also a bad design choice, because you would have scope being edited in multiple places, making it hard for debugging. Code for dealing with scope variables should go in controller, and service calls go to the service.
You could make your service completely unaware of the scope, but in your controller allow the scope to be updated asynchronously.
The problem you're having is because you're unaware that http calls are made asynchronously, which means you don't get a value immediately as you might. For instance,
var students = $http.get(path).then(function (resp) {
return resp.data;
}); // then() returns a promise object, not resp.data
There's a simple way to get around this and it's to supply a callback function.
.service('StudentService', [ '$http',
function ($http) {
// get some data via the $http
var path = '/students';
//save method create a new student if not already exists
//else update the existing object
this.save = function (student, doneCallback) {
$http.post(
path,
{
params: {
student: student
}
}
)
.then(function (resp) {
doneCallback(resp.data); // when the async http call is done, execute the callback
});
}
.controller('StudentSaveController', ['$scope', 'StudentService', function ($scope, StudentService) {
$scope.saveUser = function (user) {
StudentService.save(user, function (data) {
$scope.message = data; // I'm assuming data is a string error returned from your REST API
})
}
}]);
The form:
<div class="form-message">{{message}}</div>
<div ng-controller="StudentSaveController">
<form novalidate class="simple-form">
Name: <input type="text" ng-model="user.name" /><br />
E-mail: <input type="email" ng-model="user.email" /><br />
Gender: <input type="radio" ng-model="user.gender" value="male" />male
<input type="radio" ng-model="user.gender" value="female" />female<br />
<input type="button" ng-click="reset()" value="Reset" />
<input type="submit" ng-click="saveUser(user)" value="Save" />
</form>
</div>
This removed some of your business logic for brevity and I haven't actually tested the code, but something like this would work. The main concept is passing a callback from the controller to the service which gets called later in the future. If you're familiar with NodeJS this is the same concept.
Got into the same predicament. I ended up with the following. So here I am not injecting the scope object into the factory, but setting the $scope in the controller itself using the concept of promise returned by $http service.
(function () {
getDataFactory = function ($http)
{
return {
callWebApi: function (reqData)
{
var dataTemp = {
Page: 1, Take: 10,
PropName: 'Id', SortOrder: 'Asc'
};
return $http({
method: 'GET',
url: '/api/PatientCategoryApi/PatCat',
params: dataTemp, // Parameters to pass to external service
headers: { 'Content-Type': 'application/Json' }
})
}
}
}
patientCategoryController = function ($scope, getDataFactory) {
alert('Hare');
var promise = getDataFactory.callWebApi('someDataToPass');
promise.then(
function successCallback(response) {
alert(JSON.stringify(response.data));
// Set this response data to scope to use it in UI
$scope.gridOptions.data = response.data.Collection;
}, function errorCallback(response) {
alert('Some problem while fetching data!!');
});
}
patientCategoryController.$inject = ['$scope', 'getDataFactory'];
getDataFactory.$inject = ['$http'];
angular.module('demoApp', []);
angular.module('demoApp').controller('patientCategoryController', patientCategoryController);
angular.module('demoApp').factory('getDataFactory', getDataFactory);
}());
Code for dealing with scope variables should go in controller, and service calls go to the service.
You can inject $rootScope for the purpose of using $rootScope.$broadcast and $rootScope.$on.
Otherwise avoid injecting $rootScope. See
Common Pitfalls: $rootScope exists, but it can be used for evil.

AngularJs: Calling Service/Provider from app.config

I would like to call my service from within app.config.
When searching for this, I found this question with a solution that i tried to follow (not the accepted answer, but the solution below with the title "Set up your service as a custom AngularJS Provider")
However with that solution and with the suggested alternative as well i run into problems when i am trying to call the service from within my app.config (The service does not seem to be called at all). I am new to angular and javascript and don't really know how to debug this properly (i am using firebug). I hope you can help me with some pointers and possibly a solution.
Current Code (trying to implement the "possible alternative" from the linked question:
angular.module('myApp', [
'ngRoute',
])
.config(['$routeProvider', '$locationProvider', '$provide', function($routeProvider, $locationProvider, $provide) {
$provide.service('RoutingService',function($routeParams){
var name = $routeParams.name;
if (name == '') {name = 'testfile'}
var routeDef = {};
routeDef.templateUrl = 'pages/' + name + '/' + name + '.html';
routeDef.controller = name + 'Ctrl';
return routeDef;
})
//Here i would like to get a hold of the returned routeDef object defined above.
$routeProvider.when('/name:', {
templateUrl: RoutingService.templateUrl,
controller: RoutingService.controller
});
My previous attempt was to declare the Service like this via a provider:
var ServiceModule = angular.module('RoutingServiceModule', []);
ServiceModule.provider('routingService', function routingServiceProvider(){
this.$get = [function RoutingServiceFactory(){
return new RoutingService();
}]
});
function RoutingService(){
this.getUrlAndControllerFromRouteParams = function($routeParams){
var name = $routeParams.name;
var routeDef = {};
routeDef.templateUrl = 'pages/' + name + '/' + name + '.html';
routeDef.controller = name + 'Ctrl';
return routeDef;
}
}
and tried to call it like i would usually call a service in a controller (after adding the RoutingServiceModel to the dependencies of myAppof course). In both cases the templateUrl and controller are not set when i navigate to my views, so I guess i am missing some dependencies, or am not calling the service correctly.
Any ideas?
during the config phase, only providers can be injected (with the
exception of the services in the AUTO module--$provide and $injector).
Please check this working demo: http://jsfiddle.net/nxbL66p2/
angular.module('Joy',[])
.config(function($provide) {
$provide.provider('greeting', function() {
this.$get = function() {
return function(name) {
alert("Hello, " + name);
};
};
});
})
.controller('MyCtrl', ['$scope', 'greeting', function ($scope, greeting) {
$scope.greet = function () {
greeting('Joy');
};
}]);
Simple HTML:
<div ng-app="Joy">
<div ng-controller="MyCtrl">
<div ng-click="greet()">Click to greet.</div>
</div>
</div>
Reference: https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
Angular's terminology is a bit of a mess, because 'service' and 'provider' may designate different things, depending on the context. According to the statement in the manual,
An Angular service is a singleton object created by a service factory.
These service factories are functions which, in turn, are created by a
service provider. The service providers are constructor functions.
When instantiated they must contain a property called $get, which
holds the service factory function.
You can't inject services in config (except constant), only service providers, and your RoutingService is inapplicable there.
Technically, you can get a service there with
RoutingServiceProvider.$get();
But it will make a separate instance (rather than singleton object), and the usage of $routeParams makes no sense at the moment when it is being called in config.
You can have templateUrl as function (see the manual), while dynamic controller names aren't suitable for route configuration, define them within templates with ng-controller instead.

Angular: $rootScope variable vs event

I'm dealing with an app that manages users login. Like in many apps, i want to change the header when the user logs in.
I've a main file (index.html) which uses ng-include to include the header.html
I found two solutions (i'm new to angular, so both may be wrong):
1) use a $rootScope.broadcast()
So when the user logs in I broadcast (the auth.js, it's inside a factory) a message that is intercepted by the controller in the header.
the auth.js
$rootScope.$broadcast('logged',user);
the controller.js
$scope.$on('logged', function(evnt, message){
$scope.user = message;
});
the header.html
<div class="header" ng-controller="GcUserCtrl as gcUserCtrl">
...
<li><a ng-show="user" href="#">User: {{user.name}}</a></li>
2) set a $rootScope variable
As far as I understood $rootScope is the root of all the scope (the naming is quite smart) and all the $scope have access to it.
the auth.js
$rootScope.user=user;
the heaeder.html (no controller is needed here)
<div class="header">
...
<li><a ng-show="user" href="#">User: {{user.name}}</a></li>
Now, what's the correct way to handle it?
the first seems a bit more expensive since the broadcast may have to do many checks.
the second .. well, I'm not a fan of global variables..
EDIT
3) use service
after the comment of alex I add this options, even if I'm not able to make it working. (here the plunkr)
it does not work without events
index.html
...
<ng-include src="'header.html'"></ng-include>
...
header.html
as for the number 1)
controller.js
.controller('GcUserCtrl', ['$scope','my.auth','$log', function ($scope, auth, $log) {
$scope.user = auth.currentUser();
}]);
my.auth.js
.factory('my.auth', ['$rootScope', '$log', function ($rootScope, $log, localStorageService) {
var currentUser = undefined;
return {
login: function (user) {
currentUser = user;
...
},
...
currentUser: function () {
return currentUser;
}
};
}]);
The problem here is that the controller is called only the first time and nothing happens after the login.
As I stated earlier you will want to use a Service which will store the user's information. Attach user information to this service where ever you are authenticating the user. If you have questions about the best way to authenticate that would be a seperate question but you may want to look into using a Login Factory that does the actual authentication (and any authorization). You can then inject the login Service into that factory. I have created a Plunker here as a reference.
var app = angular.module('myApp', []);
app.service('SessionService', function () {
this.attachUser = function(userId, fName){
this.userId = userId;
this.fName = fName;
}
});
app.controller('MainCtrl', function($scope, SessionService){
// You will want to invoke attachUser some other way (perhaps on authentication), this is for test purposes only
SessionService.attachUser(1234, 'John');
$scope.userName = SessionService.fName;
});
The code above is an example of your Service. This will act as a Session handler and store important information about the user. The controller MainCtrl can then invoke properties in the SessionService using dependency injection. The part I mentioned at the beginning of this post, SessionService.attachUser(userId, fName) would most likely live in a login factory.
The reason this is the best choice is because it decouples your application. It puts the session (which is really what you are storing in global variables) in a place that is designated to store that data. It makes it maintainable. You do not need to find every occurrence of $rootScope, for instance.
EDIT:
New plunker uses rootScope broadcast/on to capture changes
Events are the preferred way to communicate that action needs to be taken by something else. That an action occurred that something else might be interested in action against. It also reduces scope pollution as you mentioned.
The comment about using a service in this case is only partially accurate. All of the login logic could, and should, be put into a single service specific to logging and logging out. That service would then broadcast the event when a login occurs.
module.service('LoginHelper', function($rootScope) {
this.loginUser = function(username, password) {
// on success
$rootScope.broadcast('loggedIn', logginUserData)
}
this.logout = function() {
// on success
$rootScope.broadcast('loggedOut')
}
})
The logged in data should be stored and accessible by the service.
Alternatively, $emit could be used on $rootScope. You would then only be able to watch for the 'loggedIn' event on the $rootScope by there would be marginally less overhead.
Avoid watches
An event would be the appropriate way to go for this kind of requirement, like how alex has pointed out. A plunk demonstrating an example: http://plnkr.co/edit/v6OXjOXZzF9McMvtn6hG?p=preview
But for this particular scenario, I don't think the "angular way" is the "way". Given the nature of how $broadcast and/or $emit works (i.e. the default way events work in angular) I would avoid them...(Read the docs to understand why). In short, these mechanisms are meant to trigger listeners (attached to some scope) up/down the scope heirarchy. You don't really need all that. (Ref code for $emit)
I'd normally rely on other event propagation mechanisms (considering this pattern of requirement).
app.controller('MainCtrl', function($scope, SessionService, $document){
// You will want to invoke attachUser some other way (perhaps on authentication), this is for test purposes only
$scope.isLoggedIn = false;
$document.bind('$loggedin', function(){
$scope.isLoggedIn = true;
$scope.user = SessionService.fName;
});
$scope.logout = function() {
SessionService.attachUser(null, null);
$scope.isLoggedIn = false;
};
});
app.controller('LoginCtrl', function($scope, SessionService, $document){
$scope.doLogin = function() {
console.log('doLogin');
SessionService.attachUser(1234, $scope.username);
var doc = $document[0];
var evt = new Event('$loggedin');
doc.dispatchEvent(evt);
};
});
Plunk
Of course, when you are done with that view, always cleanup. Handle the $destroy event on that controller's scope and unbind the event handler...
$scope.$on('$destroy', function() {
$document.unbind('$loggedin');
};
Refer MDN for more on how to trigger events using DOM.
Update: [24 Sep]
Here is a small directive setup which demonstrates the point:
app.directive('ngNest', function($parse, $compile, $timeout){
var end = false;
var level = 0;
var fnPostLink = function(scope, element, attrs) {
//console.log('start:', ++fnPostLink.count);
var lvl = attrs.level;
if(!lvl) {
throw 'Level not specified';
}
var create = document.createElement.bind(document);
var level = parseInt(lvl);
var count = 0;
var div = create('div');
div.setAttribute('ng-controller', 'DummyCtrl');
var cls = function() {
return 'margin ' + (count % 2 ? 'even' : 'odd');
//return 'margin even';
};
div.setAttribute('class', cls());
var node = div;
while(count++ < level - 1) {
var child = create('div');
child.setAttribute('ng-controller', 'DummyCtrl');
child.setAttribute('class', cls());
node.appendChild(child);
node = child;
}
node.setAttribute('ng-controller', 'FinalCtrl');
node.innerHTML = 'foo';
var $new = $compile(div)(scope);
var el = element;
el.append($new);
};
fnPostLink.count = 0;
var fnPreLink = function(scope, element, attrs) {
//console.log('prelink');
};
var api = {
link: {
post: fnPostLink,
pre: fnPreLink
},
template: '<div></div>',
scope: {},
restrict: 'E',
replace: true
};
return api;
});
It simply nests divs attaching a controllers to it. I am attaching these two controllers:
app.controller('DummyCtrl', function($scope){
});
app.controller('FinalCtrl', function($scope, $document){
$scope.$on('$myEvt', function(){
console.log('$myEvt', $scope.$id, new Date().getTime());
});
$document.bind('$myEvt', function(){
console.log('$myEvt', $scope.$id, new Date().getTime());
});
});
FinalCtrl is added to the tail; DummyCtrl is added to the rest.
In the html template I do something like:
<ng-nest level="10"></ng-nest>
There is also in the html file a nested markup which is manually put there...
Entire code may be found here: https://gist.github.com/deostroll/a9a2de04d3913f021f13
Here are the results I've obtained running from my browser:
Live reload enabled.
$broadcast 1443074421928
$myEvt 14 1443074421929
$myEvt 19 1443074421930
DOM 1443074426405
$myEvt 14 1443074426405
$myEvt 19 1443074426405
You can notice the difference in the ticks when I've done $broadcast. I have done a $broadcast on $rootScope; hence angular walks down the scope tree depth-first and triggers those listeners attached the respective scopes, and, in that order...The stuff in $emit & $broadcast source code also validates this fact.

Angular - Update scope when change in Factory data

I'm using a factory to poll a particular web service. This web service is used to update data any the factory. I initiate this factory in the main controller, and populate a scope variable through a factory function. The variable initializes correctly, and I get the right data on the screen, but I'm struggling on getting the data to bind automatically.
Edit for additional notes:
The reason this code is in a Factory is that I plan on using the factory data across multiple views.
Here is what I have so far:
App.factory('metrics', function($http, $q, $timeout){
var service;
var users = [{laps:[]}];
var updateMetrics = function(){
//updates the users array in the factory
};
service.load = function (){
var deferred = $q.defer();
$http.get('http://www.example.com/api/random').success(function(data) {
var temp_array = data.split("\n");
updateMetrics(0, temp_array);
deferred.resolve({status: 'good'});
$timeout(service.load,3000);
});
return deferred.promise;
};
service.lastLapInfo = function(){
var lastlap = [];
for (var i=0; i<users.length;i++)
{
var lap = users[i].laps[users[i].laps.length-1];
lastlap.push(lap);
}
return lastlap;
};
return service;
});
App.controller('mainController', function($scope, metrics) {
metrics.load().then(function(response){
$scope.$watch(function () { return metrics.lastLapInfo() }, function (newVal, oldVal) {
if (newVal !=oldVal)
{
$scope.users=metrics.lastLapInfo();
}
});
});
});
When I try the above, I get an error saying '10 $digest() iterations reached'. I don't see how that's possible, asI'm not calling the watch function multiple times.
Any suggestions (or other means to accomplish what I'm trying to do?)
If you're not 100% set on using $watch, a pattern that I prefer is to bind new instances of (not references to) modules to the current scope and keep the controllers strictly as components used for wiring together the project's views and models. This excludes the use of $watch, even for coordinating data across modules. I prefer to use $rootScope's $broadcast, $emit and $on methods within modules/factories (after passing in $rootScope as a service, which may or may not work for all situations, though it has for all that I've come across) rather than the comparatively sluggish $watch or $watchCollection methods. Using the latter makes me feel dirty inside... But I digress.
Would something like the following work in your situation?
App.factory('metrics', function($http, $q, $timeout){
var service;
service.users = [{laps:[]}];
service.updateMetrics = function(){
// updates the users array in the current instance of `metrics`
// ex:
// this.users = updatedMetrics;
// don't do:
// service.users = updatedMetrics;
};
service.load = function (){
var deferred = $q.defer();
$http.get('http://www.example.com/api/random').success(function(data) {
var temp_array = data.split("\n");
this.updateMetrics(0, temp_array);
deferred.resolve({status: 'good'});
$timeout(service.load,3000);
}.bind(this));
return deferred.promise;
};
service.lastLapInfo = function(){
var lastlap = [];
for (var i=0; i<this.users.length;i++)
{
var lap = this.users[i].laps[this.users[i].laps.length-1];
lastlap.push(lap);
}
return lastlap;
};
return service;
});
App.controller('mainController', function($scope, metrics) {
$scope.metrics = angular.copy(metrics);
$scope.metrics.load();
});
By setting $scope.metrics = angular.copy(metrics), we are creating a new instance of metrics, rather than setting $scope.metrics as a reference to metrics ($scope.metrics = metrics). This has several benefits, including that you can now use multiple instances of the same module in the controller (ie $scope.foo = angular.copy(foo); $scope.bar = angular.copy(foo); since the objects bound to $scope are completely new objects, rather than references to the same module.
Another benefit is that the instance of metrics attached to $scope can be used to call methods on metrics which can allow any changes to metrics to automatically be applied to your controller's views. I frequently faced odd issues when trying to get this to work when not using angular.copy or $.extend, seemingly because changes to the referenced module attached to $scope were not always being registered.

inheritance with angularjs .service()

I have a ServiceA() in module A and variable item is used in html.
angular.module("ModuleA").service("ServiceA", function () {
var item=[];
this.get(){
item.push("A");
}
});
Controller.js
angular.module("ModuleX").controller("Ctrl", function (ServiceA) {
$scope.service=ServiceA;
});
HTML:
<h1>{{service.item}}
</h1>
i am trying to achieve inheritance using angularjs .service(),I want to make serviceA() in moduleA as base service and create a service serviceB() in moduleB and this should inherit base service(serviceA()) and update variable 'item' in serviceA().
Controller code and html code remains same.
Is it possible? Is this a good approach? Can we achieve inheritance/abstraction using angularjs .service()?
angular.module("ModuleB").service("serviceB", function (serviceA) {
serviceA.item="B";
});
Whenever you define a module, you should pass a empty array or
dependencies as the second argument. i.e. angular.module("ModuleA",
[]) or angular.module("ModuleA", ['someDependent']).
The service is in one module whereas the controller is in the other module. So, you need to create a relation between them angular.module("ModuleX", ["ModuleA"]).
You are not returning anything from the service and you have written the method in wrong way.
Plnkr
Working Code
angular.module("ModuleA", []).service("ServiceA", function () {
var item=[];
this.get = function(){
item.push("A");
return item;
}
});
angular.module("ModuleX", ["ModuleA"]).controller("Ctrl", function ($scope, ServiceA) {
$scope.service = ServiceA.get();
console.log($scope);
});

Categories

Resources