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

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.

Related

Setting up dynamic $http.get request (Angular)

I want to know how to dynamically change an $http call so that the $http request URL differs based on the element that is clicked in an ng-repeat list. But I'm stuck.
Currently, I have an ng-repeat set up on my index page:
<div ng-repeat="team in nt.getStandings">
<h2>{{team.team_name}}</h2>
<p>Team ID = {{team.team_id}}</p>
</div>
The variable getStandings is taken from an API via an $http call. Like so:
In StandingsService
return $http.get(
'http://api.com/standings/1005?Authorization=xxxx'
)
.success(function(data) {
return data;
})
.error(function(err) {
return err;
});
And then StandingsService is attached to the getStandings variable in my controller.
"1005" is a property which calls a specific array, in this case a particular sporting competition, from an array of competitions.
So, on my index page I'm using ng-repeat to list all teams within that competition.
As you can see on the html above, I have linked each team so that it dynamically generates a URL which appends the team_id to the end, which using $routeParams I define as the variable whichTeam.
Team Details Page
<h1>Dynamic Team ID = {{whichTeam}}</h1>
This works fine, the team ID is generated dynamically according the team that is clicked.
Just like 'StandingsService' above, I have another service called 'TeamService' which makes an $http request to pull team data. Currently though it is set up statically to make a call to one individual team - I want to make the service take in the whichTeam variable so that the call changes depending on which team was clicked.
This is the static team $http request (I've broken the URL apart and concatenated to make it clearer):
return $http.get(
'http://api.com/team/' + '16110' + '?Authorization=xxxx'
)
I want the 16110 part, which refers to ONE team, to be a the whichTeam variable, allowing it to pull in the correct individual team data, but I don't know how to write this (or indeed if it's possible).
I hope I've been clear - happy to clarify further if needed. Thanks in advance.
Make a factory:
app.factory("DataService", ["$http", function($http) {
return {
getTeamDetailsById: function(teamId) {
return $http.get('path/to/api' + teamId + '?Auth=xxxx')
}
};
}]);
Use it in a controller:
app.controller("MainCtrl", ["$scope", "DataService", function($scope, DataService) {
$scope.teamDetails = {};
$scope.getTeamDetailsById = function(event, teamId) {
//prevent click navigation
event.preventDefault();
//call factory service
DataService.getTeamDetailsById(teamId).then(function(response) {
//success callback
$scope.teamDetails = response.data;
}, function(response) {
//an error has occurred
});
}
}]);
In the ng-repeat element:
<div ng-repeat="team in teams">
<a href ng-click="getTeamDetailsById($event, team.team_id)">{{team.team_name}}</a>
</div>
The above assumes you have only one state and are storing in only one controller. If you want to use different states usving $stateProvider, then you'd have to use parameters, by making use of ui-sref and passing in team Id.
If indeed you are using $states and parameters, do this:
<a href ng-click="goToState($event, team.team_id)">{{ team.team_name }}</a>
$scope.goToState = function(e, teamId) {
$state.go("teamDetailsState", { "teamId": teamId });
}

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);
}
}
});

Undefined resolved data while accessing nested view

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;
}

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';
}
);

How to execute AngularJS controller function on page load?

Currently I have an Angular.js page that allows searching and displays results. User clicks on a search result, then clicks back button. I want the search results to be displayed again but I can't work out how to trigger the search to execute. Here's the detail:
My Angular.js page is a search page, with a search field and a search
button. The user can manually type in a query and press a button and
and ajax query is fired and the results are displayed. I update the URL with the search term. That all works fine.
User clicks on a result of the search and is taken to a different page - that works fine too.
User clicks back button, and goes back to my angular search page, and the correct URL is displayed, including the search term. All works fine.
I have bound the search field value to the search term in the URL, so it contains the expected search term. All works fine.
How do I get the search function to execute again without the user having to press the "search button"? If it was jquery then I would execute a function in the documentready function. I can't see the Angular.js equivalent.
On the one hand as #Mark-Rajcok said you can just get away with private inner function:
// at the bottom of your controller
var init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
// and fire it after definition
init();
Also you can take a look at ng-init directive. Implementation will be much like:
// register controller in html
<div data-ng-controller="myCtrl" data-ng-init="init()"></div>
// in controller
$scope.init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
But take care about it as angular documentation implies (since v1.2) to NOT use ng-init for that. However imo it depends on architecture of your app.
I used ng-init when I wanted to pass a value from back-end into angular app:
<div data-ng-controller="myCtrl" data-ng-init="init('%some_backend_value%')"></div>
Try this?
$scope.$on('$viewContentLoaded', function() {
//call it here
});
I could never get $viewContentLoaded to work for me, and ng-init should really only be used in an ng-repeat (according to the documentation), and also calling a function directly in a controller can cause errors if the code relies on an element that hasn't been defined yet.
This is what I do and it works for me:
$scope.$on('$routeChangeSuccess', function () {
// do something
});
Unless you're using ui-router. Then it's:
$scope.$on('$stateChangeSuccess', function () {
// do something
});
angular.element(document).ready(function () {
// your code here
});
Dimitri's/Mark's solution didn't work for me but using the $timeout function seems to work well to ensure your code only runs after the markup is rendered.
# Your controller, including $timeout
var $scope.init = function(){
//your code
}
$timeout($scope.init)
Hope it helps.
You can do this if you want to watch the viewContentLoaded DOM object to change and then do something. using $scope.$on works too but differently especially when you have one page mode on your routing.
$scope.$watch('$viewContentLoaded', function(){
// do something
});
You can use angular's $window object:
$window.onload = function(e) {
//your magic here
}
Another alternative:
var myInit = function () {
//...
};
angular.element(document).ready(myInit);
(via https://stackoverflow.com/a/30258904/148412)
Yet another alternative if you have a controller just specific to that page:
(function(){
//code to run
}());
When using $routeProvider you can resolve on .state and bootstrap your service. This is to say, you are going to load Controller and View, only after resolve your Service:
ui-routes
.state('nn', {
url: "/nn",
templateUrl: "views/home/n.html",
controller: 'nnCtrl',
resolve: {
initialised: function (ourBootstrapService, $q) {
var deferred = $q.defer();
ourBootstrapService.init().then(function(initialised) {
deferred.resolve(initialised);
});
return deferred.promise;
}
}
})
Service
function ourBootstrapService() {
function init(){
// this is what we need
}
}
Found Dmitry Evseev answer quite useful.
Case 1 : Using angularJs alone:
To execute a method on page load, you can use ng-init in the view and declare init method in controller, having said that use of heavier function is not recommended, as per the angular Docs on ng-init:
This directive can be abused to add unnecessary amounts of logic into your templates. There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, as seen in the demo below; and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope.
HTML:
<div ng-controller="searchController()">
<!-- renaming view code here, including the search box and the buttons -->
</div>
Controller:
app.controller('SearchCtrl', function(){
var doSearch = function(keyword){
//Search code here
}
doSearch($routeParams.searchKeyword);
})
Warning : Do not use this controller for another view meant for a different intention as it will cause the search method be executed there too.
Case 2 : Using Ionic:
The above code will work, just make sure the view cache is disabled in the route.js as:
route.js
.state('app', {
url : '/search',
cache : false, //disable caching of the view here
templateUrl : 'templates/search.html' ,
controller : 'SearchCtrl'
})
Hope this helps
I had the same problem and only this solution worked for me (it runs a function after a complete DOM has been loaded). I use this for scroll to anchor after page has been loaded:
angular.element(window.document.body).ready(function () {
// Your function that runs after all DOM is loaded
});
You can save the search results in a common service which can use from anywhere and doesn't clear when navigate to another page, and then you can set the search results with the saved data for the click of back button
function search(searchTerm) {
// retrieve the data here;
RetrievedData = CallService();
CommonFunctionalityService.saveSerachResults(RetrievedData);
}
For your backbutton
function Backbutton() {
RetrievedData = CommonFunctionalityService.retrieveResults();
}
call initial methods inside self initialize function.
(function initController() {
// do your initialize here
})();

Resources