Undefined resolved data while accessing nested view - angularjs

I try to integrate Firebase on a simple AngularJS project based on UI-Router sample.
This seems to works pretty well using navigation flow... but actually a got an undefined variable while accessing directly the nested view with for example the direct url : #/contacts/42
You can test and see the source code on the following plunker :
http://plnkr.co/edit/1CnwHl9rsOifzWoSYg8V?p=preview
As you can see in firebug I got the following error :
Error: contacts[i] is undefined
Contacts is a variable of the factory 'ContactDB' which is promised from firebase backend and resolved data of the state 'contacts'.
It seems $scope.contacts is corrupted. Only the first item is set...
I really don't understand the reason of this issue... ?!
It would be really GREAT if somebody can help me.
Thanks in advance to have a look.

I am also using ui-router and firebase and having issues trying to get the number of contacts to display on state route change. I am using numChildren() in my contacts controller.
.controller( 'contactsCtrl', function contactsCtrl( $scope, filterFilter, orderByPriorityFilter, contacts, FBURL ) {
var contactsRef = new Firebase(FBURL+'/contacts');
$scope.entryLimit = 50;
$scope.search = $scope.lastSearch.search;
contactsRef.on('value', function(snap) {
$scope.contacts = contacts;//contacts is a resolved $firebase Object
$scope.contactsCount = snap.numChildren();
});
$scope.$watch('search', function(oldTerm, newTerm) {
$scope.filtered = filterFilter(orderByPriorityFilter($scope.contacts), oldTerm);
$scope.contactsCount = $scope.filtered.length;
$scope.lastSearch.search = oldTerm;
//sets contacts list page to 1 on new searches
if (oldTerm != newTerm) {
$scope.contactListPos.page = 1;
}
});
});
This works fine if you load the /contacts route from the browser bar or refresh the page. But if I change states from say my /home state which has a link to go to /contacts, $scope.contactsCount will display 0. I also have a $watch in the search input, only when I do a search does the contactsCount update. contactsCount will also update correctly when I go into a contacts.detail child state and then back to the contacts state.
I was able to get it working by editing the $watch callback using if/else statement:
if (newTerm === oldTerm) { //first run
return;
} else {
$scope.contactsCount = $scope.filtered.length;
}

Related

I can't navigate to state ui router from address bar

I have a problem navigating to a state from the address bar or when i refresh the current state.
i have the following code:
Controller:
$scope.selectCategory = function(category) {
$location.path('/posts/' +category.category);
$scope.posts=[];
Post.query({category: category.id}).$promise.then(function(posts){
$scope.category = $stateParams.category;
$scope.posts = posts;
$scope.sortBy = "-updated";
$scope.letterLimit = 100;
$scope.postDisplayed = 6;
function setCurrentPost(post) {
$scope.currentPost = post;
}
$scope.setCurrentPost = setCurrentPost;
});
}
so in the code above i'm binding my posts to scope category, when a user click a particular category, he will be directed to a page which render the posts of that specific category.
I'm calling the url in state router as follows:
app.js:
.state('posts.category', {
url: '/:category',
templateUrl: 'static/partials/post/postlist.html',
})
then the user has to click a specific category to be directed to that url. I used ng-click to achieve that in my html:
<img ng-click="selectCategory(category)" ng-src="[[category.image]]" err-src="../../../static/img/imageholder.png" alt="img01"/>
I'm using Resource query to call posts from api as follows.
services:
.factory('Post', ['$resource', function($resource){
return $resource('/api/posts/:category', {category:'#category'});
}])
finally in the page rendering the posts, i'm using ng-repeat to loop through the posts as follows:
<div ng-repeat="post in posts | limitTo:postDisplayed | filter: searchText | orderBy: sortBy" >
So far evrything works fine. When the user clicks a specific category it is directed to the correct page and posts are rendered.
My problem if the user refresh the page or try to access the page by entering url in the address bar, or i sent the url link to someone and clicks on it, the page rendered is blank, which means the scope function selectCategory is not called at all. In other words my function selectCategory is only called through ng-click, otherwise is not called at all through state router. Any idea how to fix this issue? Thanks in advance.
From what I understood from your code, you don't have code that is responsible for loading category data when the URL in question is accessed directly.
For the sake of keeping things separate and not complicate your code you should really add a controller for your posts.category state. Modify your state like this:
.state('posts.category', {
url: '/:category',
templateUrl: 'static/partials/post/postlist.html',
controller: 'PostsCategoryController'
})
Then, create a PostsCategoryController which will be used for your posts.category state. Something like this
yourModule.controller('PostsCategoryController', PostsCategoryController);
PostsCategoryController.$inject = ['$scope', '$stateParams']; //add additional dependencies you need.
function PostsCategoryController($scope, $stateParams){
//code for handling posts category data and initialization
}
Then, load and initialize data in your PostsCategoryController. You can do that by taking the code from selectCategory method and modify it a bit, like this:
function PostsCategoryController($scope, $stateParams){
function selectAndInitializeCategory(){
//your Post.query code for getting the data (you can use $stateParams to get category)
}
selectAndInitializeCategory();
}
Please note that you will have to reference your PostsCategoryController in your static/partials/post/postlist.html template.

Change Firebase Array Ref based on Localstorage

i am stuck on the following problem:
i have a select element were the user picks an option.
this option is saved to the localstorage.
now i want to use this stored value as path for a firebasearray.
basically it seems to work but i cant get the firebasearray ref to update without hitting reload.
my firebasearray factory:
.factory("MyFireFactory", ["$firebaseArray", "$localStorage",
function($firebaseArray, $localStorage) {
var ref = firebase.database().ref('demodata/' + $localStorage.selectedoption);
return $firebaseArray(ref);
}
])
After reading the angular docs angular docs if found out that
"...All services in Angular are singletons..." wich basically means if i understood it correctly that they only run once.
Wich in my case meant the i had to move my factory code to the controller so that the data from localstorage can be used.
my now working controller:
var ref = firebase.database().ref('demodata/' + $localStorage.selectedoption);
$scope.demoscop = $firebaseArray(ref);

Angularjs Reinitializing active controller

Is there a way to reinitialize a controller that is currently active ?
This would help me with transitioning from page to page without adding much additional workaround code.
For example:
<div ng-controller='Blah as ex'>
{{ex.name}}
</div>
and in the controller Blah's initialization function, the name would be retrieved from a service:
this.name = someService.name;
So for the example above, I'd like to have a button which would reinitialize my Blah controller.
edit: This is only a basic example.
I'm using the browser's 'state' in order to restore 'back' and 'forward' data in my controllers.
There are a few cases so I'm trying to simplify its process (which currently works, but is not as 'pretty')
Would something like this be a possible solution? Using route.reload to reload your page?
myapp.Controller('SampleController', function($location, $route) {
$scope.navTo = function(url) {
if ($location.path() === url) {
$route.reload();
} else {
$location.path(url);
}
}
});

watchPosition not working correctly by using ngroute (Timeout, code 3)

I use navigator.geolocation.watchPosition to find out the current location insight an angularJS application. That works well.
Now i added the ngRoute functionality to the page.
Starting the application redirects the user to #/main where a controller gets the current position an displays this position on an map.
That still works.
This is the controller:
MyMapCtrl.controller('watchPosMap', ['$scope', 'MarkerService', function($scope) {
$scope.ha = true;
$scope.timeout = 20000;
$scope.processNewLocation = function (data) {
[..code to process an display the location ...]
};
$scope.watchMyPositionChangesID =
navigator.geolocation
.watchPosition(
function(data) {
$scope.processNewLocation(data);
},
function(e){
$scope.errorMsg = e;
if (e.code == 2)
{alert('Unable to detect location');}
},
{ enableHighAccuracy: true,timeout: $scope.timeout });
}]);
The Problem is: if the application goes to an different route (like from #/main to #/detail) an than back to the main route, the map is displayed, but the geolocation seams not to work.
The line $scope.processNewLocation(data) is not executed anymore.
While debugging that situation I found out, that $scope.watchMyPositionChangesID is lost after returning to #/main, navigator.geolocation is called again, but results in an timeout. So $scope.processNewLocation(data); is not called again, the watchPositionresults in timeout (Code 3)
http://dev.w3.org/geo/api/spec-source.html#position_error_interface
So no new position-object could successfully acquired.
I have no idea way?!
//Update:
It seams to work, if I remove the "geolocation-watch-objekt" by using navigator.geolocation.clearWatch(id);.
The problem is: how to determine the id outside the watchPosMap-controller? I think fetching the angular-element is not an elegant idea, right? Should I pass the watchID as an parameter to the new route/view/controller?
//Update 2:
I added to the watchPosMap - controller an $routeChangeStart listener in order to remove the watchPosition object, if the route changed.
$scope.$on('$routeChangeStart', function(next, current) {
navigator.geolocation.clearWatch($scope.watchMyPositionChangesID);
});
Is that best practice?

How can I route a URL request to get data, plus run a function to change the CSS in angularjs?

I have a link that is used to load data, and then I want the CSS styling to change. I want both the data to be loaded and the CSS change to happen from one click event.
<section ng-controller="OffrdHeadCntrl">
<section id="menuOff" ng-class="{true: 'OpenMenu', false: 'ZeroWide'}[MenuStatus]">
<b>Menu</b><br>
<nav>
<ul id='OffrdCat'>
<li>Antiques</li>
<li>Appliances</li>
</ul>
</nav>
</section>
.... More HTML in another section here.
</section>
This is the routeProvider:
var ysshApp = angular.module('ysshApp', [
'ngRoute',
'ysshControllers',
'firebase'
]);
// 'ysshServices'
ysshApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/Antiques', {
templateUrl: 'Client_Pages/Offered_Menu.html',
controller: 'CommonController',
customInput: 'zeg0yv7nxle6a5sr2xl-ezs'
}).
when('/Appliances', {
templateUrl: 'Client_Pages/Offered_Menu.html',
controller: 'CommonController',
customInput: 'zh86tu488b8g6maw9wrfk'
})
.otherwise({ redirectTo:'/Tab_Home_1' });
}]);
When the user clicks 'Antiques', a bunch of data loads. The link triggers the URL to change, and that causes the $routeProvider to use a controller that then loads the data. I also have a function that changes the CSS styling:
This is the controller that loads the data:
var ysshControllers = angular.module('ysshControllers', []);
ysshControllers.controller('CommonController',
function($scope, $http, $route) {
$scope.dbKey = $route.current.customInput;
$scope.urlToDb = 'https://' + $scope.dbKey + '.firebaseio.com/.json';
$http.get($scope.urlToDb).success(function(data) {
var values = [];
for (var name in data) {
values.push(data[name]);
}
$scope.UsedItems = values;
});
// Initially order by date and time
$scope.orderProp = 'Time';
}
);
There is the function/controller? that changes the CSS: (I stripped some stuff out)
function OffrdHeadCntrl($scope) {
$scope.OpenCloseMenu = function() {
if ($scope.MenuStatus === true) {
$scope.OutputStatus = 'MuchWide';}
else
{$scope.OutputStatus = 'FullWideVal';}
};
}
I can get either the CSS to change, or the data to load, but not both. I've tried putting the ng-click="OpenCloseMenu()" in all kinds of places, but either the routeProvider will work, or the CSS change will work, but not both.
I've tried adding lines of code to the Common Controller, but that doesn't work. Can I add an event that runs after the data is done loading? I've seen examples that create a service that relates multiple controllers, but it's for binding data, not running a button click event.
I'm trying to change the CSS in order to hide the menu part the page before the data is loaded. I can use the Resolve object property to cause something to be resolved first, before the data is loaded. That would work, but I'm having trouble configuring the controller so it is available. I can get Resolve to work, but I can't get the function to fire. If I add this to the routeProvider, an alert msg will pop up, then once I click OK, the data loads.
resolve: {
// Run code in resolve first
whatever: function() {
alert("it ran");
}
}
This will work for me if I can get the function I need to run, but I haven't figured that out yet.
I successfully got a $routeChangeSuccess to run, but it runs BEFORE the data is loaded.
ysshControllers.controller('OffrdCatChg', function($scope) {
$scope.$on('$routeChangeSuccess', function(){
alert("it ran the success?");
});
});
So, I don't think that is going to help me. I created a new controller, and put the name of the controller into an HTML tag in that page. It runs when the route changes, but it runs before the data is loaded. That would be okay, but some of my other code runs afterwards, and sets the CSS back to the original. There must be a way to test for a particular controller having been run.
Since the data loaded is accompanied by url change, so link may be directly visited, not only from the clicking previous ones, so you should put data-loading in the first place.
As for the CSS changing, it's a state reflection thing, closely related to url changing(If I understand your description right), so you should listen to url change event, in something like a rootController( aside from ng-view), such as:
$scope.$on '$routeChangeSuccess', (e, current, previous) ->
//getting url segments, and updates css as it should be
return
The logic for me is:
click the link ---> router change ---> new controller(data load)
|
|
|--------->routeChangeSuccess event ---> update the css
I was able to find a solution. First of all, I think I had my controller defined wrong. I had a function defined, and it was working, but I don't think it was set up right. I had a function written like this:
function CntrlMyCSS($scope) {
$scope.OpenCloseMenu = function() {
if ($scope.MenuStatus === true) {
$scope.OutputStatus = 'MuchWide';}
else
{$scope.OutputStatus = 'FullWideVal';}
};
}
I didn't have it tied to a module, or defined as a controller. This is the new change.
ysshControllers.controller('HideShowInOffered',
ysshControllers.controller('HideShowInOffered',
function CntrlMyCSS($scope) {
$scope.OpenCloseMenu = function() {
if ($scope.MenuStatus === true) {
$scope.OutputStatus = 'MuchWide';}
else
{$scope.OutputStatus = 'FullWideVal';}
};
}
);
So, I guess that even though I added
ng-controller="CntrlMyCSS"
to a HTML tag, and it worked, it wasn't really a controller? idk. I was using the name of a function as the controller. I know that defining it as a controller seemed to make the stuff inside that controller available to my other controller. I still don't really understand, but basically I just added a line at the top of the function to tie it to the module, and define it as a controller, and change the HTML tag to the name of the controller instead of the name of the function.
The other thing that helped was being able to debug the code by setting break points, stepping through lines, and looking at assigned values. If it wasn't for that, I'd probably still be going down roads to nowhere.
The fix was basically just tweaking what I had set up. I tried $routeChangeSuccess, but by stepping through the code, I saw that it was firing before the data loaded. That didn't work for me, because the controller was setting default CSS values for when the page originally loads. So it would change the CSS, and then set it back to original. Which happens so fast, I wouldn't have known that without the debugger.
$scope.$on('$routeChangeSuccess', function(){
alert("it ran the success?");
});
Trying to get a fix through the routeProvider didn't seem to be of any use either. It was basically an issue of the code being available to be called, and learning the timing the program flow through debugging.
The lesson to the story? Take the time to make sure you have a way to step through your code, and determine what the program flow is. There are probably infinite numbers of bad guesses, and maybe only one solution. The odds are against you unless you can debug.
This is the controller that loads the data, then formats the data, then changes the CSS to close the menu.
ysshControllers.controller('CommonController',
function($scope, $http, $route) {
$scope.dbKey = $route.current.customInput;
$scope.urlToDb = 'https://' + $scope.dbKey + '.firebaseio.com/.json';
$http.get($scope.urlToDb).success(function(data) {
var values = [];
for (var name in data) {
values.push(data[name]);
}
$scope.UsedItems = values;
});
// Initially order by date and time
$scope.orderProp = 'Time';
$scope.MenuStatus = false;
$scope.OutputStatus = 'FullWideVal';
}
);

Resources