Passing values from a service - javascript

Do I need to create a getCurrent to keep bindings (if I have {{current.status}} in my view ex)? Or would current: current be enough?
Would I loose the binding to status if Id do $scope.status = services.status. Meaning status wont be updated in a view if its changed.
Will bindings to someValue be kept? Meaning it will change in my view if its changed in the service if I do $scope.someValue = services.someValue
function someService() {
var current = {
status: ''
};
var someValue = 'hello';
//////////
var service = {
current: current,
getCurrent: getCurrent,
status: current.status,
someValue: someValue
};
return service;
//////////
function getCurrent() {
return current;
}
}

No, you don't need the getCurrent method, current:current should be enough.
2 & 3. No, because you are setting the scope variables to the service's properties ($scope.status = service.status) and those properties are strings, the bindings will NOT be kept. However, if you instead assign the entire service object as the scope variable and use dot notation in your bindings, then they will because you will be updating the object whose reference was injected into the controller (the service). The important things to note are in order to understand why your method does not work, but the alternative does is to understand that
objects are passed by reference
for the above reason, angular recommends that your bindings always use dot notation.
This is what your code would could look like to make it work:
//service
.factory('service', function() {
var current = {
status: 'theStatus'
};
var someValue = 'hello';
var service = {
current: current,
status: current.status,
someValue: someValue
};
return service;
})
// controller
.controller('theCtrl', ['$scope', 'service', function($scope, service) {
$scope.serviceData = service;
}])
// view
<p>{{serviceData.current}}</p>
<p>{{serviceData.status}}</p>
<p>{{serviceData.someValue }}</p>
And here is the sample plunker: http://plnkr.co/edit/n2P07mjwnMVHCl4l7SAj?p=preview . Note that it has 2 examples, the first one shows your method and the second one shows the object method.
EDIT - BIG CAVEAT:
One big caveat to notice is that in your service, if the someValue or the current variables change, your view will NOT be updated. Because we are returning the service object, changes WILL reflect in the service object's someValue, current and status properties, but those WILL NOT cause the original current and someValue variables to also be in sync.

Ok, as you are programming in JavaScript, you're not obliged to use getter/setter as you would in Java for example.
All angular service are singleton, so you can easily share some data. Moreover by creating Factory, you'll be able to return an object, with want you want inside, for example method, that will be invoked. You can make the connection with the factory pattern.
In your case, you can save your service instance into your current $scope.
EDIT
In your factory, you should return your current object. Then, you should use it in your view to retrieve current status. So, you will get an object, not just a fixed value, so it will updated.
Controller
(function(){
function Controller($scope, Service) {
//Register the Service instance into our scope
$scope.service = Service;
//Retrieve current object with status property
$scope.working = Service.current;
//Retrieve VALUE of current object
$scope.not_working = Service.status;
$scope.changeStatus = function() {
Service.changeStatus('another status');
}
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Service
(function(){
function Service($timeout) {
var current = {
status: 'off'
};
var someValue = 'hello';
$timeout(function() {
//Update service status
current.status = 'on';
}, 500);
//////////
var service = {
//Return the current object
current: current,
//Just set the VALUE of current.status
status: current.status,
getCurrent: getCurrent,
someValue: someValue,
changeStatus: changeStatus
};
return service;
function getCurrent() {
return current;
}
function changeStatus(status) {
alert("status changed");
//Modifying status property of current object
current.status = status;
}
}
angular
.module('app')
.factory('Service', Service);
})();
HTML
<body ng-app='app' ng-controller='ctrl'>
Status : {{working.status}}<br>
Status not updating : {{not_working}}<br>
SomeValue : {{service.someValue}}
<br>
<button ng-click="changeStatus()">go</button>
</body>
You can see the Working Plunker

Angular will keep track of changes on $scope with it's implementation of dirty checking. So, when an event occurs within your Angular application and an $apply or $digest is invoked, Angular will iterate through all of the $watch values and update any bound values accordingly.
Without the newer controllerAs syntax, you would want to put any values that you want bound onto the $scope object. Then, any events fired within your Angular application will trigger your updates automatically.
Here is a simple demo binding $scope.status.value to three DOM references:
http://codepen.io/anon/pen/KdKrqe
To answer your questions directly:
No, you don't need to create getters/setters for your bound values due to Angular dirty checking. But, current: current is not enough. That would be reassigning a value outside of Angular to its $scope and depending on the Object type, this will be assigned by either value or reference. Any 'connection' to the original value will be lost when you assign by value (Numbers, Strings, Booleans...)
Yes, you would loose your binding if service.service is assigned by value i.e. You wouldn't be able to update services.status to update the value of $scope.status. If you changed it a bit to: $scope.services = services; Then $scope.services.status would be bound.
If you used the suggestion from above, any changes would be reflected in your original object: $scope.services = services; then, any changes on services. would be reflected in your original object.
I think the two main points here are understanding assignment by value/reference in JS and how Angular implements bound values by dirty checking.

Related

Save form data in view

how can i save data from 1 view to another in angularjs?
i did $rootScope
From what I see, you use 2 different controllers for each view (or one for the view and none for the root view).
The problem is that Angular can't share data between controllers like that.
You either have to use a service/factory, or use the rootscope, but not as you did, rather with broadcast and emit
If I were you I would use a service.
EDIT Here you go, a service for you :
(function() {
'use strict';
angular
.module('YourModuleName')
.factory('CountriesService', CountriesService);
CountriesService.$inject = ['Your', 'dependencies', 'here', 'in', 'string'];
/* #ngInject */
function CountriesService(your, dependencies, here, not, in, string) {
var service = {
setCountries: setCountries,
getCountries: getCountries
};
var vm = this;
vm.countries = []; // Or maybe an object ?
// ... List of other variables you need to store.
return service;
////////////////
function setCountries(listOfCountries) {
vm.countries = listOfCountries;
}
function getCountries() {
return vm.countries;
}
}
})();
This will store your variables. In your controller you add CountriesService as a dependency, to save you use CountriesService.setCountries and to load you use CountriesService.getCountries. Be aware that refreshing the page will delete all the data !
EDIT NUMBER 2
If you're scared of John papa guidelines, here is a simple service you can use in the same file you put your controller :
app.factory('CountryControl', function(your, dependencies) {
var service = {
setCountries: setCountries,
getCountries: getCountries
};
this.countries = []; // Or maybe an object ?
// ... List of other variables you need to store.
return service;
////////////////
function setCountries(listOfCountries) {
this.countries = listOfCountries;
}
function getCountries() {
return this.countries;
}
});
I have an app that does this more or less. A service fixes this nicely AND creates a mechanism such that you can do this anywhere in your app.
First, I would recommend not trying to manage this with scope. Just put an object on your controller (myFormObj), and add the properties you want to it (name, rank, serialnumber, etc).
Then bind the input fields of the form, to the properties in that object (as opposed to scope vars). So your ng-model things would look like myCtl.formObj.name, and so on.
When the user triggers the event that changes the view, save a COPY (angular.copy) of that formObj off to the side, usually in a Service (think FormStateService or something). FormStateService could do nothing more than hold a simple array.
this.forms = { 'TheNameOfYourForm' : theFormObjToSave };
So, when the user triggers that event that leaves the form, you just do this:
formStateSvc.forms [ 'NameOfMyForm' ] = angular.copy ( theFormObj );
When the user comes back to the original view and the controller initializes, you just ask the formStateSvc:
if ( 'NameOfMyForm' in formStateSvc.forms ) {
this.formObj = formStateSvc.forms [ 'NameOfMyForm' ];
}
Voila, your old form state is restored.
More robustly, you could create "addForm, removeForm" methods etc, you could ensure against things like undefined, and you could make the rebind to the former state implicit (when your form's controller inits, just ask it to restore the state if there's any to restore). So your controller would just have:
this.formObj = formStateSvc.rebindOldDataIfItExists ( 'MyFormName' );
You get the idea.
A simple approach is to create a value provider object and publish it on scope:
//Create value provider object
app.value("FormObj", {});
app.controller("myController", function($scope, FormObj) {
//Publish on scope
$scope.FormObj = FormObj;
});
Then have the ng-model directives use that object:
Name <input ng-model="FormObj.name"><br>
Rank <input ng-model="FormObj.rank"><br>
SerialNum <input ng-model="FormObj.ssnum"><br>
The value object is a singleton which persists for the life of the application. Changes to the contents of the object will be retained and available to other controllers and will survive changes to the view.
The DEMO on PLNKR

check if model changed since controller loaded

Is there an angular way of checking if a model has changed since it's initial state when the controller was loaded ?
I can of course create a variable to store the initial state and compare against it, but is there a more elegant way ?
function myController (Service) {
var vm = this;
vm.model = Service.getValue(); // Service sets inital value at controller load
vm.method = function(){
// do something here only if vm.model changed form inital State
}
}
You can use $watch on the scope
$scope.$watch('vm.model', function (newVal, oldVal) {
If (newVal) doSomething();
}, true);
The optional Boolean for the second parameter allows you to do a deep watch of the variable properties.
Make sure that you inject $scope into your controller too
you should use $watch to tracking all change of variable.
View more detail on here: https://docs.angularjs.org/api/ng/type/$rootScope.Scope

2 ways of adding an object to $scope, only one works

I have a user-service taking care of the authentication.
In the html I have:
<div ng-show="user.authenticated">Hi {{user.username}}!</div>
The controller sets up the scope like this:
$scope.user = userService;
This works great! If I am reloading the html-page, the div is hidden for a short while until the already logged in user is authenticated.
But if I try to set up the user-object on $scope in the controller like this:
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
If I reload the page, then it does not work any more, the div is never shown, even if the user already is logged in, like in the above example. How this is not working?
Edit: I will add the controller (or at least, the part of the controller that is interesting here):
angular.module('app.controllers')
.controller('NavCtrl', ['$scope','$rootScope','$location','userService','alertService',
function($scope,$rootScope,$location,userService,alertService) {
// $scope.user = userService; // This works (but is now commented out)
// The following does not work if the user reloads this page
// The view is only updated with username when (after a few milliseconds)
// userService has talked with the server and gotten the user-details...
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
}]);
It is important to reload the html after login, or else the userService will already be set up with the user-details. So when the view is set up (after page-reload), there is no user-info available in the userService, this is available only a short while after reload....
Edit 2: The reason I was trying the second variant is because I have a page with an object with various properties and the username is only one of those properties needed. This works fine until the user possibly reloads the page.
Edit 3: I have altered 2ooom's fiddle to make it more my case, see http://jsfiddle.net/utphjuzy/1/
This as to do with how Angular's binding mechanism works (and how binding works in general).
When you do something like $scope.user = userService;, you are actually binding the property user to the Object userService. That means that both properties will point to the same in-memory mutable object reference. If a property of that object changes, then both "pointers" will notice that change (in the case of the $scope.user, that will force a re-render).
However, if you use it like this:
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
You are creating a completely new user Object and you are assigning its properties not by reference but by value because JavaScript strings, numbers and booleans are immutable and therefore cannot be assigned by reference. Ultimately this means Angular will not be able to track changes on those properties.
One way of doing what you need is to use a model Object on the service that holds any data that needs to be bind. Like shown in this Plunker.
You are setting values which will never be updated. Your controller only runs once so this:
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
gets set to whatever values they had when the controller was created. Since I'm guessing your user service gets the details using AJAX, it will never update the user object on your scope.
Why this works?
$scope.user = userService;
here userService object is directly assigned to scope, so all updates on userService object after assigning it to scope variable will reflect into scope. for e.g
var userService = {};
$scope.user = userService;
alert($scope.user.username);
angular.extend(userService, {"username":"User"});
alert($scope.user.username);
So in above example $scope.user.username will have value User in the last statement
Why this doesn't work?
$scope.user = {
username: userService.username,
authenticated: userService.authenticated
};
here values are extracted from userService and assigned to scope, so future changes to userService will not be reflected into scope. That's why $scope.user.username will be undefined
Created a jsfiddle for explaining the same
Solution
In my opinion you should initialise / re-initialize the $scope.user on success event of userService call.

ui-select2 inside directive isn't updating controller model

I have a directive that takes in a collection and builds out a dropdown.
.directive("lookupdropdown", function () {
return {
restrict: 'E',
scope: {
collectionset: '=',
collectionchoice: '='
},
replace: true,
template: '<select class="input-large" ui-select2 ng-model="collectionchoice" data-placeholder="">' +
' <option ng-repeat="collection in repeatedCollection" value="{{collection.id}}">{{collection.description}}</option>' +
'</select>',
controller: ["$scope", function ($scope) {
$scope.repeatedCollection = new Array(); //declare our ng-repeat for the template
$scope.$watch('collectionset', function () {
if ($scope.collectionset.length > 0) {
angular.forEach($scope.collectionset, function (value, key) { //need to 'copy' these objects to our repeated collection array so we can template it out
$scope.repeatedCollection.push({ id: value[Object.keys(value)[0]], description: value[Object.keys(value)[1]] });
});
}
});
$scope.$watch('collectionchoice', function (newValue, oldValue) {
debugger;
$scope.collectionchoice;
});
} ]
}
});
This works fine. It builds out the drop down no problem. When I change the dropdown value, the second watch function gets called and I can see that it sets the value of collection choice to what I want. However, the collectionchoice that I have put into the directive doesn't bind to the new choice.
<lookupDropdown collectionset="SecurityLevels" collectionchoice="AddedSecurityLevel"></lookupDropdown>
That is the HTML markup.
This is the javascript:
$scope.SecurityLevels = new Array();
$scope.GetSecurityLevelData = function () {
genericResource.setupResource('/SecurityLevel/:action/:id', { action: "#action", id: "#id" });
genericResource.getResourecsList({ action: "GetAllSecurityLevels" }).then(function (data) {
$scope.AddedSecurityLevel = data[0].SCRTY_LVL_CD;
$scope.SecurityLevels = data;
//have to get security levels first, then we can manipulate the rest of the page
genericResource.setupResource('/UserRole/:action/:id', { action: "#action", id: "#id" });
$scope.GetUserRoles(1, "");
});
}
$scope.GetSecurityLevelData();
Then when I go to post my new user role, I set the user role field like this:
NewUserRole.SCRTY_LVL_CD = $scope.AddedSecurityLevel;
but this remains to be the first item EVEN though I have updated the dropdown, which according the watch function, it has changed to the correct value. What am I missing here?
You faced this issue because of the prototypical nature inheritance in Javascript. Let me try and explain. Everything is an object in Javascript and once you create an object, it inherits all the Object.Prototype(s), which eventually leads to the ultimate object i.e. Object. That is why we are able to .toString() every object in javascript (even functions) because they are all inherited from Object.
This particular issue on directives arises due to the misunderstanding of the $scope in Angular JS. $scope is not the model but it is a container of the models. See below for the correct and incorrect way of defining models on the $scope:
...
$scope.Username = "khan#gmail.com"; //Incorrect approach
$scope.Password = "thisisapassword";//Incorrect approach
...
$scope.Credentials = {
Username: "khan#gmail.com", //Correct approach
Password: "thisisapassword" //Correct approach
}
...
The two declarations make a lot of difference. When your directive updated its scope (isolated scope of directive), it actually over-rid the reference completely with the new value rather then updating the actual reference to the parent scope hence it disconnected the scope of the directive and the controller.
Your approach is as follows:
<lookupDropdown collectionset="SecurityLevels" collectionchoice="$parent.AddedSecurityLevel"></lookupDropdown>
The problem with this approach is that although it works, but it not the recommended solution and here is why. What if your directive is placed inside another directive with another isolated scope between scope of your directive and the actual controller, in that case you would have to do $parent.$parent.AddedSecurityLevel and this could go on forever. Hence NOT a recommended solution.
Conclusion:
Always make sure there is a object which defines the model on the scope and whenever you make use of isolate scopes or use ng directives which make use of isolate scopes i.e. ng-model just see if there is a dot(.) somewhere, if it is missing, you are probably doing things wrong.
The issue here was that my directive was being transcluded into another directive. Making the scope im passing in a child of the directive it was in. So something like $parent -> $child -> $child. This of course was making changes to the third layer and second layer. But the first layer had no idea what was going on. This fixed it:
<lookupDropdown collectionset="SecurityLevels" collectionchoice="$parent.AddedSecurityLevel"></lookupDropdown>

Sharing a variable between controllers through a service

Here is my plunker:
From what I understand, since the Service Variable being shared is an object, the object that gets loaded to the service by controller 1 should be plainly seen by controller2 without the need for $watches or listeners or anything. Am I wrong? How can I get this to work?
I have fixed your plunk: http://plnkr.co/edit/JNBmsjzdj6SHOSK4kPNh.
Your service has an object which you put into a model on your $scope ($scope.item). So far so good. However, you then update your service object with a new object reference ($scope.thisObject) so that $scope.item and myService.myObject are now referencing to completely different objects.
You should only update object properties. See the plunk for details.
So instead of writing:
app.factory('myService',function(){
var service = {
myObject:{},
changeProperty: function(newProperty){
this.myObject = newProperty;
}
};
return service;
});
You should use:
app.factory('myService',function(){
var service = {
myObject:{},
changeProperty: function(newProperty){
this.myObject.text = newProperty.text;
}
};
return service;
});
Hope that helps.

Categories

Resources