I have a simple SPA with two views: a list view and a detail view. I use a service called StateService to pass data between the two controllers.
I am trying to handle the case where the user refreshes the browser page--when this happens, the StateService gets reinitialized and the detail view can no longer work. I want to detect when this happens and return the user to the list view.
Here is a simplified version of my State Service. The idea is that I would set isInitialized to true when I switch to the detail view so that I can detect when the service has not been properly initialized.
var StateService = function () {
var isInitialized = false;
};
This is what I have tried in the first few lines of my controller. The StateService is being successfully injected into the controller.
//always returns [Object], on refresh or navigating from list page
alert(StateService);
// this next line always returns undefined. Should be false since I am initializing
// the value to false?
alert(StateService.isInitialized);
//One of the many combinations I have tried . . .
if (!StateService.isInitialized | StateService.isInitialized == false) {
$location.path('/');
}
I don't know if this is a gap in my understanding of javascript or angular, but any thoughts on how I can get the above code to work, or better ideas on what to do when a user refreshes the page?
Edit
Using console.log as recommended by nycynik I see the following:
c {} [StateService]
undefined [StateService.isInitialized]
So it seems that StateService itself is just an empty object when this code gets hit. I get the same results from my other controller (the one that handles the list view).
As noted in the comments, the service seems to otherwise work as expected.
I think you have a problem with scoping. variables in javascript have function scope.
isInitialized is scoped only to your StateService Function, so you can't get at it outside of your StateService Function.
not sure exactly how you're getting this thing into your controller, but maybe these help:
if you're using an angular's module.service() to use StateService as a constructor to inject a (new StateService) into your controller then you need to set isInitialized on the instance
var StateService = function () {
this.isInitialized = false;
};
This way (new StateService).isInitialized === false
If you are just using module.factory() or something else that doesn't use new, then you need to put your isInitialized value somewhere else you can actually get at it.
var StateService = function () {
};
StateService.isInitialized = false
Hope that helps.
Related
Hi Guys i'm stuck with a small issue in angular 5, I'm trying to call a common session check method which is imported from a common ts file, i'm using the session check method on load of the page and on click of logout button to redirect the user to login page. but on load of the page it works fine, but on click of a button it gives an error of undefined, Please help thanks in advance.
Dashboard
Imported
this inside function() { ... } definition does not refer to your component, but to that function context. Use arrow function so this context will remain bound to your component:
logout() {
session.signOut().then(() => {
this.s.checkIfSignedIn();
});
}
Also you should remove the var keyword when declaring class variable u.
Bonus advices:
care about your editor/linter warnings (to suppress errors when accessing global objects like gapi, declare that first like this: declare const gapi: any;)
care about code indenting
try to make your variable names descriptive, do not use names like a or u, again so you do not get lost in the code so easily (btw you have utils variable there too, maybe you should use that instead of defining new one called u...)
You are declaring it with var and calling it with this. It's not a property of the component, that's why it's not working.
You have a different scope inside the promise.
logout() {
let self = this;
var session = gapi.auth2.getAuthInstance();
session.signOut().then( function() {
localStorage.clear();
self.s.checkIfSignedIn();
});
}
I have looked up several posts with similar issue but was unable to find a solution for my case.
I have a search page that sends a request to the backend an populates the page with search results (each result is an object). I display a concise view of each object and, upon a mouse click on a specific object, the user should be redirected to a page that shows a more detailed view for that object.
On my JS side, I have one controller that handles the $http.post call and retrieves the objects from the backend to display on the first page. I use a different controller for the second page to try and get the relevant object from the first controller (through angular's .service ), but for some reason I get an empty object on the second page. The service works with a getter and a setter. The service is able to set the object just fine, through the first controller, and I am able to print it. However, when redirecting to the second page, while using the second controller's getter, the object gets deleted and shows as empty for some reason.
Here is the relevant code. The service:
app.service('shareService', function(){
var savedData = {}
function set(data) {
savedData = data
console.log(savedData);
}
function get() {
console.log(savedData);
return savedData;
}
return {
set: set,
get: get
}
});
The search (setter) contoller:
app.controller('SearchCtrl', function(shareService, $scope, $http) {
$scope.sendSearch = function() {
$http.post("http://localhost:9080/MedNetApp/rest/mednet/searchCollections", json).success(function (response) {
$scope.collections = response.searchResults;
shareService.set($scope.collections);
});
};
});
The second (getter) controller:
app.controller('CollectionsCtrl', function(shareService, $scope){
$scope.collections = shareService.get();
})
Not sure if this is relevant, but here is also the html part where I set up a temporary test button to redirect to the second page:
<button id=mixing type = "button" class="btn btn-primary-aligned"
data-ng-click = "go('second-page.html')">temp</button>
So, at the end, savedDatashows as empty object when printing it the second time through the get() function. Any idea why this is not working? or a better way to send data to a new page?
EDIT - I should mention that I basically relied on this solution:
AngularJS - Passing data between pages
So, after further research, I came across this solution:
Sharing Data between pages in AngularJS returning empty
I used sessionStorage in my service, as described in the link above, and it fixed the problem.
Use localStorage or sessionStorage
var myJson ={};
localStorage.set("your_Data",myJson);
// to get the value from another page
var returnJson = localStorage.get("Your_Data");
I'm not sure if i have completely wrapped my head around this idea - but I'll try my best to clearly describe what I am trying to do here.
I have a factory that changes and parses a URL for me, so I can pass params into a controller for use (that were stored in the url). This is sort of so I can save a state for the user and they can share it via copy'ing of a URL (send it to their friends or bookmark it or w/e).
I am trying to set up a factory (or service) that listens for locationChangeSuccess - so that if the user mofies the url and presses enter, it will refresh the scopes in the controllers. So here is what I have:
.factory("urlFactory", function($location, freshUrl, StateString){
//request to change new url
requestObj.requestState = function(moduleName, stateName, startVar){
}
//request item from url, via your module and state name
requestObj.parseState = function(moduleName, stateName){
}
I dropped the center out (if it is needed im happy to link), but those just get and set the url for me.
So in the controllers I do something like
$scope.mod2m3 = urlFactory.parseState("module2", "mod3");
$scope.mod2m4 = urlFactory.parseState("module2", "mod4");
So when they land on the page, they pull their state. This works great. However, now i'm trying to solve some edge case scenarios where maybe the user modifies the url.
So I can latch onto that even pretty easily with
.factory("urlWatcher", function($location, $scope){
var urlWatcher = {};
$scope.$on('$locationChangeSuccess', function(event) {
console.log("Asdsa");
});
return urlWatcher
});
However, where I am struggling is trying to determine a way where when this fires, it would connect the new value to the scope in the controller. It was suggested to me that a callback of some sort in the parse (set) function, but I am struggling with how to approach that. It would be super cool if I could set a way for this factory/service to re send the new value when it changes to the right place. Callback sounds good, however I don't know how to config this correct.
The easiest route would be to just do an
$scope.$on('$locationChangeSuccess', function(event) {
console.log("Asdsa");
});
In each controller and manually bind to each scope, but I am trying to make this as modular as possible (and thats also a ton of watchers on the locationchangesuccess). would be fantastic if I could figuire out a clean way to set the service/factory to listen once, and on change find the right module/controller and change the value.
I can't seem to think a clear route, so I would be very greatful for any insight to this issue. Thank you very much for reading!
If what you want is a publish/subscribe architecture, where publications are global and subscriptions have the same lifecycles as Angular scopes... then Angular events are what you're looking for. There's no point setting up an ad hoc communication system with callbacks and whatnut, that would just be partially reinventing events.
However, if you want to make the semantics more obvious / add flexibility, you can listen once to $locationChangeSuccess in a service and broadcast a custom event.
$rootScope.$on("$locationChangeSuccess", function (event) {
$rootScope.$broadcast('myCustomeEvent', {message: "Guys, time to refresh!"});
});
Then listen to this event in each of the scopes where it is relevant.
$scope.$on('myCustomeEvent', function (event) {
console.log("Asdsa");
});
If setting up the listening gets repetitive, by all means, factor it out in a function, which you can for example put in a service:
myApp.factory('someFactory', [function () {
return {
listenToLogAsdsa: function (scope) {
scope.$on('myCustomeEvent', function (event) {
console.log("Asdsa");
});
}
};
}]);
Then all you have to write in your controller is:
someFactory.listenToLogAsdsa($scope);
You can assign a variable in the scope to an object in the factory, that way it's bound to a reference instead of a value. Then, in your HTML you bind the reference to the DOM. urlFactory.parseState() should then save the result to said object, and return the key where it was saved.
For example:
In urlFactory:
requestObj.parseState = function(moduleName, stateName){
var key = moduleName+stateName;
this.urlContainer[key] = "www.example.com";
return key;
}
In the controller:
$scope.urls = urlFactory.urlContainer;
$scope.mod2m3 = urlFactory.parseState("module2", "mod3");
In your HTML:
{{urls[mod2m3]}}
This way, "urls" is bound to a reference, which angular watches for changes, and whenever you change urls[mod2m3], it will affect the DOM.
You can also just react to changes in the scope variables by watching them:
$scope.$watch('urls', function() {
//do something
});
NOTE: Since this is an object, you might need to use $watchCollection instead of $watch.
I'm writing some small exercises to teach myself AngularJS and I'm trying to write some simple user Authorisation tasks. I have a form to collect/input a username and password, these are then sent to a rest service using $http and CORS (as my REST service is running on a different port), they are checked and if there is a match I return a UUID and create a token and I $broadcast a loggedIn value to true that is on the $rootScope, something like this.
// this is in a service I call 'authService'
this.login = function (user) {
return $http({method: 'POST', url: 'http://127.0.0.1:3000/login', data: user})
.then(function (response) {
// set up a local storage token
storageService.setLocalStorage('token', response.data[0].uuid);
// broadCast is loggedIn - we have a match
$rootScope.loggedInUser = true; // this is set to false at the .run() of the app
$rootScope.$broadcast('LoggedIn');
return 1;
}, function () {
// return http code later
return 0;
});
};
this.getLoggedIn = function () {
return $rootScope.loggedInUser;
};
Now in a separate menu view I have the following condition (the authService is added as a dependancy on the menu controller):
<div id="logIn" ng-show="!authService.getLoggedIn()">...</div>
Now when I load the app for the first time the condition is correct, however I want this condition to update should a user log in correctly (so the div) isn't shown. In the menu controller I have the following code, none of it seems to do anything?
$scope.$on('LoggedIn', function () {
authService.getLoggedIn(); // doesn't update the view?
console.log($rootScope.loggedInUser); // returns true
console.log(authService.getLoggedIn()); // returns true
});
$scope.$watch('loggedInUser', function () {
console.log('loggedInUser has changed ' + $rootScope.loggedInUser);
// This runs once when we set $rootScope.loggedInUser in the .run() of the app, output is: 'loggedInUser has changed false'
// then when we have successfully logged in again, output is 'loggedInUser has changed true'
});
Okay, so the condition on the <div> in my menu view doesn't update when I changed the $rootScope.loggedInUser, I'm doing something wrong in my approach, can someone give me some advice or correct my approach to this. Thanks
You don't have to do anything special in Angular to update view when some prop is updated, provided it is in the correct scope. I provided a Plunkr for you which demonstrates that you don't have to do anything special to refresh the view.
http://plnkr.co/edit/B6kUwJdA4lRKkjAItSFO?p=preview
You don't have to do watches, you don't have to do anything. That is the power of Angular. Also, it is weird that you set stuff in rootscope. My advice for you is to look at my example and revise/restructure your code. Also, having stuff like this:
ng-show="!authService.getLoggedIn()"
Is not the recommended way of doing things. You can have a controller, in which you say:
$scope.userLoggedIn = autService.getLoggedIn();
and then in your view:
ng-show="userLoggedIn"
You can also take a look at this plunkr:
http://plnkr.co/edit/aRhS0h7BgpJJeRNvnosQ?p=preview
Using AngularJS 1.0.4
One of our Angular apps is dependent on a resource being loaded before anything else can be loaded. We do this from a service that gets initialized in app.run() and then broadcast an event that everything else listens for to start loading.
In the controllers we also need to have access to the resulting resource. So I then have the following in each one:
$scope.parent = null;
if(!svc.parent) {
$scope.on('parentLoaded', function() {
$scope.parent = svc.parent;
});
} else {
$scope.parent = svc.parent;
}
Each of the controllers is tied to a view and can be called in any order. So it's not guaranteed that the resource is loaded when the controller gets called, although it can be if another controller was called before hand. The load event only gets trigger the first time the service is initialized when the app first loads.
Is there a better way to this?
It seems kind of redundant & not clean.
I would just use a promise. You would have something like:
var deferred = $q.defer();
$http.get('/application').then(function(res) {
deferred.resolve(res);
});
function fetch() {
return deffered.promise;
}
To load your initial resource, we'll call the resource "application" for example. Then, to load your next portion, you can do:
application.fetch().then(function(svc) {
//res is whatever is returned from our $http.get, earlier
$scope.parent = svc.parent
//do whatever required your resource here
});
Instead of doing this:
$scope.parent = null;
Create an empty array instead (or object depending on what resource is).
$scope.parent = [];
This way angular object watchers will react to changes of the array. For more advanced issues can use promises when loading data