I have an object that is exposed to $scope and has properties attached to it.
I access this object in my controller and call a service which does little something.
What I need to add to it is new property to the object(which is going to be an array attached to the object as a property) so that updated object is returned to the controller and I can access the elements of this array using data-expression tag{{ }}.
I would like to know in detail about making such manipulations the right way and possible ways of doing it.
Just add the array to the object.
$scope.myobj = {};
...
// sometime later
var numbers = [1,2,3];
$scope.myobj.numbers = numbers;
EDIT:
To answer your question about scope in a service. Scope is not accessible in a service. Typically you ask your service for something IE data. But your service can do anything, like add 2 numbers, or in your case create an array of something that you need to attach to your object.
module.service('MyService', function() {
this.add = function(number1, number2) {
return number1 + number2;
}
this.createMyArray = function() {
// pass in whatever you need to this service in order
// to create the array you need.
// example of just returning a hard coded array
return [1,2,3];
}
});
Then you can inject your service into your controller which has the scope you want to modify.
app.controller('MyController', function($scope, MyService) {
$scope.add = function(number1, number2) {
// Lets use our service to add these numbers and
// assign result to scope variable
$scope.answer = MyService.add(number1, number2);
}
$scope.myobj = {};
$scope.makeArray = function() {
// lets use our service again to create an array
// this time lets add it to a scope object that already exists.
$scope.myobj.numbers = MyService.createMyArray();
}
});
A lot of times services are used to grab/update things from a server, so you will see a lot of examples that make http get/post/put/delete calls. Then from your controller(s) you can use those services to grab data. But again you are not limited to that, your service can simple just hold some static data or helper functions too.
Related
I currently have three components of the same type. For example, I have a component called 'Lister' and I have three Lister components that calls different apis (e.g. listing teachers, students and courses). However, if a component that loads courses want to access data from the component that renders teachers then in this case, what should I do? I've seen few ways but they are all related to sharing information between components with other types (via $scope and $rootScope).
<lister data-ng-cloak api="/teacher" type="teacher"></lister>
<lister data-ng-cloak api="/student" type="student"></lister>
<lister data-ng-cloak api="/course" type="course"></lister>
This is the current way to use three components. They share common controller and service.
APIs may be different in your case but they have a common controller and service. So, declare three arrays to store teachers, students and courses in the service.
app.service('myService', function{
vm = this;
vm.teachers = [];
vm.students = [];
vm.courses = [];
});
have an api call to get the data.
vm.getApiData = $http.get(url).then(); // url may be to get above three from api
have a function in service to get the lists in to the controller
vm.getData: function(val){
return vm[val]; // value may be teachers, students and courses
}
Call the above function from the controller.
$scope.teachers = myService.getData('teachers');
$scope.students= myService.getData('students');
$scope.courses= myService.getData('courses');
Now, data is ready. Just access it based on your requirement. Just an idea
Create a parent object in controller
var parent={};
Then for all the listers assign value from this parent object so that all your data can be binded to one common object.
eg : parent.course=value;
Then they all be sharing data from one common object
In you controller create one object named as lister having properties for each different component :
$scope.lister = {
"teacherView": "teacher",
"studentView": "student",
"courseView": "course",
}
and when you call the setData method, receive the data to be stored and the key, lets say
componentService.setData(data, lister.teacherView)
and update your getData so you can choose which data you want to get like this :
componentService.getData(lister.teacherView)
You can create a service like this:
angular.module('app').service('componentService', function() {
var d = {};
function setData(data, key){
d[key] = data;
}
function getData(key){
return d[key];
}
})
In my Ionic / Angular framework I update 2 variables inside a service (an $http.get().then() block):
for( var di = day; di <= endOfMonthDate; di++) {
var flavor = days[di - 1];
daysLeftCalendar.push( flavor[1]); // dates right away !
}
var todaysFlavorIndex = -1;
for (var i = 0; i < days.length; i++ ) {
if ((days[i])[0] == day) {
todaysFlavorIndex = (days[i])[1];
todaysFlavorName = flavors[todaysFlavorIndex]; // only updates if you change tabs
}
}
Then I have these accessor methods in my service that get called by my controller:
return {
// both of these are hit after switching to one of the two tabs which both reference these functions
remainingFlavorIndexes: function() {
return daysLeftCalendar
},
getTodaysFlavorName: function() {
return todaysFlavorName
}
};
Then in my only controller I expose these variables like this:
$scope.remainingFlavorIndexes = Calendar.remainingFlavorIndexes(); // this one copies over right away !!
$scope.todaysFlavorName = Calendar.getTodaysFlavorName(); // this one doesn't
Then in my view:
<div> <!-- this one shows up right away -->
{{remainingFlavorIndexes}}
</div>
<div> <!-- these two only show up after switching tabs and returning -->
<img class="scaled-image" src="img/{{todaysFlavorName[2]}}">
</div>
<div style="text-align: center;">
{{todaysFlavorName[1]}}
</div>
How is it that I'm handling these 2 variables exactly the same, but todaysFlavorName is empty (even after the .then call returns)?
And why is it that when I switch tabs and come back they are populated?
Edit:
What is supposed to go into remaining flavor indexes is something like this:
[21,20,13,0,27,12,9,18,1,3,30,29,25,7,6,4,9,18,21,13]
And it works every time.
What is supposed to go into todaysFlavorName is:
[21, "peanut butter", "peanut_butter.jpg", "some meaningless text here"]
And it works only after I switch tabs.
There is a big difference between both. In the case of daysLeftCalendar:
the service has an array
the controller calls the service function that returns a reference to that array
the http callback function pushes elements to this array
So, the controller has a reference to the array that is in the service. Whenever the array is modified in the service, it's also modified in the controller, since the controller and the service both share a reference to the same array.
In the case of todaysFlavorName:
the service defines a variable todaysFlavorName referencing an array
the controller calls the service function that returns a reference to that array
the http callback function doesn't modify this array. It assigns a new array to the variable todaysFlavorName.
So, in the end, the controller has a reference to the original array, whereas the service has a reference to the new array. Which explains why nothing changes in the view: the controller still references the old array.
When you change tab, I assume the controller is reinstantiated, the service function is called again, and the controller gets back the new value from the service.
The fix is quite easy: always get the value to display from the service, instead of caching the value in the controller. Replace
$scope.todaysFlavorName = Calendar.getTodaysFlavorName();
by
$scope.todaysFlavorName = function() {
return Calendar.getTodaysFlavorName();
};
and
{{todaysFlavorName[1]}}
by
{{todaysFlavorName()[1]}}
I got a service that contain some contacts (name,phone). The controller has array that contain a reference to the array from the service so for every change on the array all gets updated.
Service:
app.service('ContactManagerService', function()
{
this.Contacts=[];
...
this.AddContact=function(Contact){...};
this.RemoveContact=function(Contact){...};
...
});
First question: Is this a good approach? Should every controller/directive need to have a direct reference to the original array from the service? I have read a lot about setting up some events from the service to the controllers when the array has been changed, but it sound stupid because the array on the controller will be change anyway (because its the same array) and the ng-repeat will be updated automatically.
Second problem: The service has a method that replace the array to new one:
this.ReplaceContacts=function(NewContacts)
{
this.Contacts=NewContacts;
});
The ng-repeat does not update because the controller still got the old reference to the old array. So a refresh need to be done.
I tried to replace the code to this one so the same array's reference wont change, but when the the code enter the foreach, this.Contacts array is undefined and the code stops. Why ?!
this.ReplaceContacts=function(NewContacts)
{
this.Contacts.splice(0, this.Contacts.length); //remove all contacts
NewContacts.forEach(function (contact, index)
{
this.Contacts.push(contact);//place the new ones
});
});
The controller code:
app.controller("myCtrl",
function ($scope,ContactManagerService)
{
$scope.Contacts = ContactManagerService.Contacts;
$scope.AddContact= function (Contact1) {
ContactManagerService.AddContact(Contact1);
}
$scope.RemoveContact = function (ContactID) {
ContactManagerService.RemoveContact(ContactID);
}
});
I hope everything is clear,
Thanks.
Because the callback function passed to forEach isn't bound to the service instance. So this, inside the callback, is not the service.
My advice: avoid this like the plague. It's an enormous source of bugs in JavaScript.
Use
var self = this;
at the top of the service, and use self instead of this everywhere.
Or bind the callback function to the service instance:
NewContacts.forEach(function (contact, index) {
...
}, this);
You can simply push elements to Contacts using Array.prototype.push()
The push() method adds one or more elements to the end of an array and returns the new length of the array.
this.ReplaceContacts=function(NewContacts){
this.Contacts.splice(0, this.Contacts.length); //remove all contacts
Array.prototype.push(this.Contacts, NewContacts);
});
As mentioned in previous anser, context of this in forEach loop is not what you think it is.
A simplification would be to use Array.prototype.concat():
var self = this;
self.ReplaceContacts = function (NewContacts) {
self.Contacts.splice(0, this.Contacts.length); //remove all contacts
self.Contacts.concat(NewContacts);
});
This is my code that currently works:
angular.module('myApp')
.controller('myCtrl', function (DocumentTypeManagerPdf, DocumentTypeManagerVideo) {
$scope.showPreview = function(document){
var previewModule = eval('DocumentTypeManager' + document.clientModule);
previewModule.show(document);
};
});
but... two things I would avoid:
Eval is evil
I am forced to inject every DocumentTypeManagerXYZ that I'll implement
In there a better solution tu use a Factory dynamically?
I think you should go with a factory pattern.
One service DocumentTypeManagerFactory
With one method like
var myDocumentTypeManager = DocumentTypeManagerFactory.instanciateWithType(document.clientModule);
myDocumentTypeManager.show(document);
Your controller will only inject one service (and the DocumentTypeManagerFactory should inject all)
In your DocumentTypeManagerFactory you should make a switch or if/else to avoid eval.
I think you can use arguments in the function. inJS every function has a variable named arguments which is a array of given parameters.
But I am not sure how your DocumentTypeManagerXYZ objects are structured. So just type debugger; beginning of your controller function and check arguments data by console then you can take a correct action.
the below one is the first one comes to my mind;
var previewModule;
for(var i = 0, len=arguments.lengh; i <len; i++) {
if (arguments[i].constructure.name === 'DocumentTypeManager' + document.clientModule) {
previewModule = arguments[i];
break;
}
}
this will be your basic approach.
as this is an angular application you can user $injector.get("moduleName")
for example;
var previewModule = $injector.get("'DocumentTypeManager' + document.clientModule");
please see $injector
I have a study, which has multiple cases and multiple executionMessages. Also each case has multiple executionSteps. I am able to access the study, the case and even each case's executionSteps. I cannot figure out why I cannot access the complete executionMessages. By that I meant each executionMessage has a type, message which are accessible but any objects inside executionMessage is not accessing. Here it the code
StudyService.studies.get({id: $routeParams.studyIdentifier}).$promise.then(function(study) {
$scope.study = study;
StudyService.executionMessagesForStudy.get({id: study.id}).$promise.then(function(executionMessages){
$scope.study.executionMessages = executionMessages;
});
for(var i=0;i<study.cases.length;i++){
var callback = callbackCreator(i);
StudyService.executionstepsForCase.get({id: $routeParams.studyIdentifier,caseId:study.cases[i].id})
.$promise.then(callback);
}
});
function callbackCreator(i) {
return function(executionSteps) {
$scope.study.cases[i].executionSteps = executionSteps;
}
}
It looks like you are using $resource for your service types (a guess at the .get().$promise pattern you are using), From the docs:
...invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view.
see: https://docs.angularjs.org/api/ngResource/service/$resource
So your code can be simplified like this:
// get() returns an object that will "fill-in" with the attributes when promise is resolved
$scope.study = StudyService.studies.get({id: $routeParams.studyIdentifier})
$scope.study.$promise.then(function(study) {
// again, executionMessages gets a placeholder that will be filled in on the resolved promise
$scope.study.executionMessages = StudyService.executionMessagesForStudy.get({id: study.id});
angular.forEach(study.cases, function(case, idx) {
// again, executionSteps is a placeholder that will be filled in on the resolved promise
var executionSteps = StudyService.executionstepsForCase.get({id: $routeParams.studyIdentifier, caseId: case.id})
// not sure if ordering is important, the service calls can return in any order
// so using push can produce list with elements in any order.
$scope.study.cases.push(executionSteps);
});
});
You still need to use the study.$promise to get the nested data after the first service call resolves.