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

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.

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

Passing values from a service

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.

Update from the Html View triggers update to the Dynamic View where in saving it to the LocalStorage loki js

I have a a angular factory class User which gets instantialted via a dynamic view filling in data from the view to the user variable which is then bound to the view.
I need to update the localstorage when ever there is a change to the userView.Name which is bound to the view input type='text' i.e. any change to the value in the text field directly changes the text in the user instance of the class and then updates the dynamic view which would update the localStorage ?
I have a sample of the code hosted at CodePen
Sample Code illustrating the problem
app.controller('TestController', ['$scope', 'Loki', 'MobileConstants', 'LocalStorageApi', 'User',
function ($scope, Loki, MobileConstants, LocalStorageApi, User, Book) {
LocalStorageApi.initialize();
var userData = LocalStorageApi.UsersView.data()[0];
var user = new User(userData);
$scope.userView = user;
}]);
app.factory('User', ['$rootScope', function ($rootScope) {
function User(userData) {
User.prototype.Name = userData.nameSample;
};
User.prototype = {
Name: null,
}
return User;
}]);
below is how the code would look like illustrating the problem
LokiJS's default storage adapter in a browser is localstorage, so if you have the user saved in a LokiJS collection all you need to do is call db.saveDatabase() and it will automatically update the localstorage.
In other words, you don't need to fiddle with localStorage directly at all, unless you are saving values that are not stored in LokiJS.

Set radio button state based on a value received from REST in AngularJS

What I would like to achieve is set a radio button's state according to the data retrieved from the server in AngularJS.
What makes this more special is that I need to show a div based on a condition (the car has 1 and only 1 assigned to it).
If the car is so-called isSingleUser, the div contains the radio buttons needs to be visible, otherwise we need to hide it.
Finally, after a lot of struggling, I even have the solution to achieve this, but I ran into some interesting obstacles what I want to share with others for reference and I still have some questions regarding the alternative "solutions".
The simplest (and working) solution
We have 3 cars, the Cars service will GET the particular car according to the chosen link.
Only car1 and car2 are isSingleUser so the div should only visible for those cars.
You can see that the radio button's state changes according to the
car.isMonitoringAutoEnablementSet JSON property.
This was my first attempt, but I couldn't see the code working at that time, I used the
value property of the input, wrongly, because the ng-value is better.
More specifically, the value property on input cannot handle boolean values as booleans (and not stringified)
therefore angular couldn't map the boolean value false to "false" and the same goes with the true value.
If I used ng-value, everything is fine.
Note that the plateNumber for each car is visible on the page.
Let's see what I tried afterwards:
Magic with promise
My assumption was that the data for car is not downloaded yet and the template rendered
before the controller received the data and store it to $scope.car
So I added this code for the controller:
$scope.car = Cars.get({carId: $routeParams.carId})
.$promise.then(function(car) {
$scope.automonitoring_state = car.isMonitoringAutoEnablementSet;
$scope.isSingleUser = car.isSingleUser;
});
and modified the view according to the new variables:
<h1 class="title">{{car.plateNumber}}</h1>
<div class='ng-hide' ng-show='isSingleUser'>
<p class='paragraph'>automatic
<form name="myForm" class="toggle">
<input type="radio" name="state" ng-model="automonitoring_state" ng-value="true">on
<input type="radio" name="state" ng-model="automonitoring_state" ng-value="false">off
<button class="button" disabled>save</button>
</form>
</p>
</div>
Note that the plateNumbers are disappeared from the page, clearly showing that some problem
occured while fetching the data for the car variable. Only the new variables had a
value in the scope (automonitoring_state and isSingleUser).
Somehow even if I put a simple console.log() inside the then function on the promise, the
car variable will not hold the data for the car, strange...
This solution also seems like a workaround, because I need to define new variables in the
scope, but if I save the car's state through the service, I need to sync back my new
variables to the $scope.car so this one is definitely a no-go for me.
Resolve with the help of a controller scope function
Then I googled and googled, found some advices on Stackoverflow, and so on.
Hey, there is resolve which is good for passing some variables into controllers before the view is rendered, that's exactly what I want.
Naively, I tried to invoke a controller function from the routeProvider like this :
when('/driver/cars/:carId', {
templateUrl: 'partials/driver_car.html',
controller: 'DriverCarController',
resolve: 'DriverCarController'.resolveCar
})
and of course added the function in question to the controller:
$scope.resolveCar = {
car: ['$routeParams', 'Cars',
function($routeParams, Cars) {
return Cars.get({
carId: $routeParams.carId
}).then(function(car) {
console.log('resolve');
return car;
}, function() {
return [];
});
}
]
};
The result is: nothing, no console.log at all, proving that the function was not invoked.
Another working solution with passing the variable from resolve
This trial is a slightly modified version of the solution above.
The Cars service is only used from the $routeProvider, if the promise returns the value, it is saved to the car variable.
.when('/driver/cars/:carId', {
templateUrl: 'partials/driver_car.html',
controller: 'DriverCarController',
resolve: {
car : function (Cars,$route) {
return Cars.get({carId: $route.current.params.carId})
.$promise.then(function (response) {
return response;
});
}
}
})
The only thing to change in the controller is to add 'car' to the injection list and save
the parameter to the $scope.
controllers.controller('DriverCarController', ['$scope', '$routeParams','car', 'SecurityEvents',
function($scope, $routeParams, car, SecurityEvents) {
$scope.car = car;
console.log(car);
}
]);
Please see my public plunkers, you can try out each solution I described above: plunkers
Any help is appreciated about judging between the most viable approach, providing some information about the intended use cases of each alternative.
Thanks!
Number 2 is the most similar to what I have used.
To fix the bug, just change the code in the controller like this:
Cars.get({
carId: $routeParams.carId
}).$promise.then(function(car) {
$scope.automonitoring_state = car.isMonitoringAutoEnablementSet;
$scope.isSingleUser = car.isSingleUser;
$scope.car = car;
});
The biggest problem that I see with calling the service from the resolve is that it tightly couples the controller with the app router, which means your controller is less re-useable.

Clear $scope on logout in Angular js

In my controller I am storing data as $scope.$parent.dossierSummaries = data;
but after log out and login the application $scope.$parent.dossierSummaries retains the same old data.
I am doing this on log out
.success( function( response, status ) {
if ( response.status > 0 ) {
var u = $rootScope.user.username;
$cookieStore.remove('myapp');
$rootScope.user = { username: '', role: 0 };
success(u);
}
else {
error(response.messages);
}
})
.error( function( response, status ) {
error(['There was an error logging you out.']);
});
in angularJS, you shouldn't set the variable directly to a controller but you should retrieve it from a service instead. So whenever you load a controller you should write a init() function to get value of that model. So everytime you will have the correct data from server.
Code example and docs : http://docs.angularjs.org/guide/dev_guide.services.creating_services
Another approach to manually tracking and cleaning things up would be to broadcast a 'logout' event on the rootScope (or other custom event). Then listen for the event either in your controller or in your service to clean up the data.
Broadcast:
$rootScope.broadcast('logout');
Watching for an event (in a service for example):
$rootScope.on('logout',function(){
dossiers = [];
});
I don't think there is any effective way to achieve it. Any object (controller, directive,filter or as a matter of fact any js object) can hold reference to another object (in your case user), and one cannot determine easily who all are holding reference.
The reference would only get release if you do it either explicitly or when the object holder the reference is destroyed.
What you can try is
$rootScope.user.username='';
$rootScope.role=0;
Assuming some object are tracking this specific object the data would be cleared now.
if you don't mind a slight screen flickering on logout, you can refresh the page using a method like this:
$window.location.replace($window.location.toString().split('#')[0]);
this would clear out all the $scope and $rootScope variables, and in my case has solved the issue.
If you want to clear the $scope, I think you can use it's constructor method or proto (prototype), which is used in constructor. I believe you can do that to reset the scope to initial state. If someone knows any more on this, feel free to comment.

Categories

Resources