Getting routing to work with hashes in AngularJS - javascript

There is a partially working example of my issue at http://nwlink.com/~geoffrey/routing/default.html
var routingInterface = angular.module("routingInterface", ['ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider.state("first",
{
url: "",
template: '<h1>{{title}}</h1><timer-control action="second" wait="10" ></timer-control>' +
'<p>This page is supposed to change to another in ten seconds</p>',
controller: function($scope) {
$scope.title = "Welcome to Routing";
}
});
$stateProvider.state("second",
{
url: "",
template: '<h1>{{title}}<h2><p>This page is supposed to be displayed after the routing works</p>',
controller: function($scope) {
$scope.title = "Second page for routing";
}
});
});
State "first" always comes up no matter which hash is displayed. I thought I was copying syntax from an example correctly, but I'm not getting something right.
There's a directive on the first view:
routingController.directive('timerControl',
[
'$timeout', '$state', '$location',
function($timeout, $state, $location) {
return {
scope: {
action: "#",
wait: "#"
},
link: function($scope) {
if (!$scope.wait) {
$scope.wait = 30;
}
var tOut = $timeout(function() {
$timeout.cancel(tOut);
//this doesn't work:
// $location.hash($scope.action);
// does work but doesn't put a hash on the url
// like I expected
$state.go($scope.action);
},
Number($scope.wait) * 1000);
},
template: '<span class="ng-hide"></span>'
};
}
]);
This does work put a new hash on the url to navigate to the new view, but it angular adds a second hash mark. But even if I type it in correctly in the url, something (the browser? or angularJS?) is adding a "/" to whatever I type. And I never ever display anything other than the first view.
I need a way to have views be unique but still expressed on the url somehow. If I have to use parameters that would also be ok. I expect that there will have to be parameters for some of the views.

I was actually very close to the right answer.
var routingInterface = angular.module("routingInterface", ['ui.router'])
.config(function ($stateProvider, $urlRouterProvider) {
//this was the first omission
//it allows the first page to render even without the hash
$urlRouterProvider.otherwise("first");
$stateProvider.state("first",
{
url: "/first", // this is what puts the hash on the Url
// and responds to the hash
template: '<h1>{{title}}</h1><timer-control action="second" wait="10" ></timer-control>' +
'<p>This page is supposed to change to another in ten seconds</p>',
controller: function ($scope) {
$scope.title = "Welcome to Routing";
}
});
$stateProvider.state("second",
{
url: "/second", //this is what puts the hash on the Url and
//and responds to the hash
template: '<h1>{{title}}</h1><p>This page is supposed to be displayed after the routing works</p>',
controller: function ($scope) {
$scope.title = "Second page for routing";
}
});
});

Related

AngularJS strange query behavior with UI Router

Have a small search app using Elasticsearch and AngularJS. I'm using $state.go() in my search() on my ng-submit button. searchTerms is my ng-model on the input.
I have 2 controllers, homeCtrl and searchCtrl. homeCtrl is for homepage which is just a search box and provides autocomplete functionality. searchCtrl is for results page and has same search box and provides for results area.
When I have $state.go() like this:
$state.go('search', {q: searchTerms});
It solved a problem I was having, which was
http://localhost:8000/app/search?q=searchTerms
INSTEAD of
http://localhost:8000/app/search?q=userTypedInput
So now the url functions correctly, but the search results do not display...?
When I have
$state.go('search', {q: 'searchTerms'});
The search results display BUT the url does not function as desired.
So basically what I'm asking is how can I get my url to function as desired which is
http://localhost:8000/app/search?q=userTypedInput
AND still have the search results display?
UPDATE
These are my states for ui router
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home/home.html', controller: 'homeCtrl'})
.state('search', {
url: '/search?q',
views: {
'' : { templateUrl: 'search/search.html', controller: 'searchCtrl' }
//add more views here when necessary
}
});
searchCtrl
I have this at the top
'use strict';
angular.module("searchApp.search", ['ngAnimate'])
.controller('searchCtrl', ['$scope', '$sce', '$stateParams', '$state', 'searchService', function($scope, $sce, $stateParams, $state, searchService) {
$scope.searchTerms = $stateParams.searchTerms || null;
and this is search(), which contains the $state.go()
$scope.search = function() {
resetResults();
var searchTerms = $scope.searchTerms;
if (searchTerms) {
$scope.results.searchTerms = searchTerms;
} else {
return;
}
getResults();//calls searchService which does the searching
$state.go('search', {q: searchTerms});
};
var getResults = function() {
$scope.isSearching = true;
searchService.search($scope.results.searchTerms,$scope.currentPage,...
I'm trying to get the url to be
http://localhost:8000/app/search?q=userTypedInput
and have the results display as well.
In your state config you use the parameter name of q to pass the search query. This is the name, you also need to use when accessing the parameter from $stateParams in your searchCtrl. In the code shown above, your query will always be null, because you access the parameter named searchQuery which doesn't exist.

$state.go() not routing to other state, #/null in url

Using angular ui-router I'm trying to use $state.go() to change to the blogEdit state after creating a new entry with blogCreate to continue editing after saving. When I click to save and trigger addPost() method, it doesnt redirect correctly and I see /#/null as the route in the address bar instead of the expected /blog/post/:postId/edit.
blogModule.controller('PostCreateController', ['$scope', '$state', '$stateParams', 'PostResource',
function ($scope, $state, $stateParams, PostResource) {
$scope.post = new PostResource();
$scope.addPost = function () {
$scope.post.$save(function () {
$state.go('blogEdit', {postId: $stateParams.postId}); // THIS SHOULD REDIRECT TO CONTINUE EDITING POST
});
}
}
]);
blogModule.controller('PostEditController', ['$scope', '$stateParams', 'PostResource',
function ($scope, $stateParams, PostResource) {
$scope.post = PostResource.get({postId: $stateParams.postId});
$scope.updatePost = function () {
$scope.post.$update({postId: $stateParams.postId});
}
}
]);
State route configuration:
var app = angular.module('app', [
'ui.router',
'blogModule'
]);
app.config(['$stateProvider', function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('blog', {
url: '/blog',
templateUrl: 'app/blog/view/blog-list.html',
controller: 'PostListController'
})
.state('blogView', {
url: '/blog/post/{postId:[0-9]}',
templateUrl: 'app/blog/view/blog-detail.html',
controller: 'PostViewController'
})
.state('blogCreate', {
url: '/blog/post/new',
templateUrl: 'app/blog/view/blog-create.html',
controller: 'PostCreateController'
})
.state('blogEdit', {
url: '/blog/post/{postId:[0-9]}/edit',
templateUrl: 'app/blog/view/blog-edit.html',
controller: 'PostEditController'
});
}]);
It seems to do this regardless of what state I try to change to.
I suppose you are saving your post on backend. When you perform save (PUT) operation your backend should return you some response. The response should be like HTTP 201 Entity created and there should be location attribute set (f.e. http://example.com/blog/post/1). Then you can get the id from location header like this:
$scope.post.$save(function (createdPost, headers) {
var postId = headers.location.split("/").pop();
$state.go('blogEdit', {postId: postId});
});
Another way is to just ignore headers and return json response from your backend. F.e. {"postId": 1, "title": "New post", ...}. Then you can do something like:
$scope.post.$save(function (createdPost) {
$state.go('blogEdit', {postId: createdPost.postId});
});
The most important is to know API of your backend (what "it returns").

Angular UI-Router. Dynamic Template URL

I got a small situation I am creating a front end portion of an application using Angular and ui-router for the routing.
Te Database is holding the routes for the links. The url would look like (AdminMsgReview?init=1) which initiates the building of the frame.
I am trying to put that frame into a single page application (hence angular), I tried hard coding the url and i know it works and injects the frame into the view.
When i try to build it dynamically, I get the issue where the templateUrl is loading first before the resolve is finished getting the url.
This is the route.js
$urlRouterProvider.otherwise('/login');
$stateProvider
.state('login', {
url: '/login',
templateUrl: function(){
return 'AdminMsgReview?init=1'
}
})
.state('app', {
url: '/app/:id/:fnid',
resolve: {
fnUrl: function(Toolbar) {
return Toolbar.getToolbar();
}
},
controller: ['$rootScope', '$stateParams','fnUrl', function($rootScope, $stateParams, fnUrl){
var mid = Number($stateParams.id) -1 ;
var fnid = $stateParams.fnid;
$rootScope.newUrl = fnUrl;
$rootScope.url = JSON.stringify($rootScope.newUrl[mid].Function[fnid].URL);
alert("Url should be = " + $rootScope.url);
}],
templateUrl: function($rootScope){
console.log($rootScope.url);
alert($rootScope.url);
return $rootScope.url;
}
})
This is my factory for getting the data:
var json = "rwd/resources/data/toolbar.json";
return{
getToolbar : function(){
return $q.all([
$http.get(json)
]).then(function(results){
var toolbar = [];
angular.forEach(results, function(result){
toolbar = result.data;
});
return toolbar.toolbar;
});
}
}

Search Engine in AngularJS

i make straight search engine. Get data from Google Api. My first view is home page and here is only form search and list results. My two view contain player.
My route provider:
$routeProvider.
when('/home', {
templateUrl: 'view/home.html',
controller: 'ZippyListCtrl'
}).
when('/mp3/:wwwId/:zippyId', {
templateUrl: 'view/mp3.html',
controller: 'Mp3DetailCtrl'
}).
otherwise({
redirectTo: '/home'
});
}])
My controllers:
zippycatControllers.controller('ZippyListCtrl', ['$scope', '$http', function($scope, $http) {
$scope.dane = {};
$scope.ladowanie = 0;
var url = "http://ajax.googleapis.com/ajax/services/search/web?callback=JSON_CALLBACK";
$scope.search = function() {
if ($scope.dane) {
$scope.ladowanie = 1;
$http({
url: url,
params: {q: $scope.dane.q, "start": 0, v: "1.0", rsz: "large", "pws": 0},
method: 'JSONP'
}).success(function (responseData) {
//console.log(responseData);
$scope.ladowanie = 0;
$scope.message = responseData;
})
}
}}]);
and:
zippycatControllers.controller('Mp3DetailCtrl', ['$scope', '$routeParams', '$sce', function($scope, $routeParams, $sce) {
$scope.player = $sce.trustAsHtml(
'code player');}]);
My question is: how search on two view?
For different actions on different pages, you can create a factory whose purpose is to get the data and perform a callback function passed to it.
.factory('getResults', ['$http', function($http) {
return function(requestObj, callBackFunc){
var url = "http://ajax.googleapis.com/ajax/services/search/web?callback=JSON_CALLBACK";
$http({
url: url,
params: {q: requestObj.q, "start": 0, v: "1.0", rsz: "large", "pws": 0},//setting to be done based on requestObj params,
method: 'JSONP'
}).success(function (responseData) {
callBackFunc(responseData);
})
}
}])
Now when you call it from the home controller, you can use:
getResults($scope.dane, function(resonseData){
$scope.messageObj.ladowanie = 0;
$scope.messageObj.message = responseData;
});
while in other view you can use:
getResults($scope.dane, function(resonseData){
$scope.messageObj.ladowanie = 0;
$scope.messageObj.message = responseData;
//redirection to home page.
});
Here messageObj should be in the main controller(means attached to html element containing the views , it is usually body level controller) so that the it is available in both the view and both the views are just changing the attributes so the main Object and not the copy is getting changed. In your code, $scope.message is different for different controllers as it creates a seperate copy for each controller.
Now when you get redirected to the home page, you have a search result list already so you can just populate that there.
Hope this helps...

Exposing the current state name with ui router

I'm trying to implement a language switcher where if a user clicks on "de" from any given page on an "en" side - it takes them to that page of the "de" side. If I console.dir the $state parameter, it exposes the values I'd want with the "current" property of the given $state. If I try to console.dir the $state.current to focus on the values I want, it only gives the parent state property (my current views are nested).
My current thinking is, I'm on url/en/content, and dynamically I can then have my lang navigation dynamically load the appropriate destination points into some kind of data attribute, pick those up with a custom directive where I'd initiate a "go to" and set my preferedLanguage value per angular-translate.
The key issue at the moment is exposing that $state name - again, when simply browsing $state the current object gives the values I'd want, but $current.state directly only gives the parent state.
If anyone has a better suggestion of how to do this (in a angular way - no custom cookie junk) I'm happy to take suggestions.
Thanks!
Update! CODE SAMPLES:
Object reference of my states:
var urlStates = {
en: {
home: {
name: 'home',
url: '/en',
templateUrl: 'templates/'+lang+'/home.html',
abstract: 'true'
},
home_highlights: {
name:'home.highlights',
url: '',
templateUrl: 'templates/'+lang+'/home.highlights.html'
},
home_social:
{
name: 'home.social',
url: '/social',
templateUrl: 'templates/'+lang+'/home.social.html'
},
home_map:
{
name: 'home.map',
url: '/map',
templateUrl: 'templates/'+lang+'/home.map.html'
}
};
My States:
$stateProvider
.state(urlStates.en.home)
.state(urlStates.en.home_highlights)
.state(urlStates.en.home_social)
.state(urlStates.en.home_map);
$locationProvider.html5Mode(true);
})
Controller:
.controller('LandingPage', function($translate, $state){
this.state = $state;
this.greeting = "Hello";
});
And Lastly, the output I see in the dom:
With this.state = $state;
{
"params": {},
"current": {
"name": "home.highlights",
"url": "",
"templateUrl": "templates/en/home.highlights.html" },
"transition": null
}
With this.state = $state.current
{
"name": "",
"url": "^",
"views": null,
"abstract": true
}
this is how I do it
JAVASCRIPT:
var module = angular.module('yourModuleName', ['ui.router']);
module.run( ['$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]);
HTML:
<pre id="uiRouterInfo">
$state = {{$state.current.name}}
$stateParams = {{$stateParams}}
$state full url = {{ $state.$current.url.source }}
</pre>
EXAMPLE
http://plnkr.co/edit/LGMZnj?p=preview
Answering your question in this format is quite challenging.
On the other hand you ask about navigation and then about current $state acting all weird.
For the first I'd say it's too broad question and for the second I'd say... well, you are doing something wrong or missing the obvious :)
Take the following controller:
app.controller('MainCtrl', function($scope, $state) {
$scope.state = $state;
});
Where app is configured as:
app.config(function($stateProvider) {
$stateProvider
.state('main', {
url: '/main',
templateUrl: 'main.html',
controller: 'MainCtrl'
})
.state('main.thiscontent', {
url: '/thiscontent',
templateUrl: 'this.html',
controller: 'ThisCtrl'
})
.state('main.thatcontent', {
url: '/thatcontent',
templateUrl: 'that.html'
});
});
Then simple HTML template having
<div>
{{ state | json }}
</div>
Would "print out" e.g. the following
{
"params": {},
"current": {
"url": "/thatcontent",
"templateUrl": "that.html",
"name": "main.thatcontent"
},
"transition": null
}
I put up a small example showing this, using ui.router and pascalprecht.translate for the menus. I hope you find it useful and figure out what is it you are doing wrong.
Plunker here http://plnkr.co/edit/XIW4ZE
Screencap
In my current project the solution looks like this:
I created an abstract Language State
$stateProvider.state('language', {
abstract: true,
url: '/:language',
template: '<div ui-view class="lang-{{language}}"></div>'
});
Every state in the project has to depend on this state
$stateProvider.state('language.dashboard', {
url: '/dashboard'
//....
});
The language switch buttons calls a custom function:
<a ng-click="footer.setLanguage('de')">de</a>
And the corresponding function looks like this (inside a controller of course):
this.setLanguage = function(lang) {
FooterLog.log('switch to language', lang);
$state.go($state.current, { language: lang }, {
location: true,
reload: true,
inherit: true
}).then(function() {
FooterLog.log('transition successfull');
});
};
This works, but there is a nicer solution just changing a value in the state params from html:
<a ui-sref="{ language: 'de' }">de</a>
Unfortunately this does not work, see https://github.com/angular-ui/ui-router/issues/1031
Use Timeout
$timeout(function () { console.log($state.current, 'this is working fine'); }, 100);
See - https://github.com/angular-ui/ui-router/issues/1627
I wrapped around $state around $timeout and it worked for me.
For example,
(function() {
'use strict';
angular
.module('app')
.controller('BodyController', BodyController);
BodyController.$inject = ['$state', '$timeout'];
/* #ngInject */
function BodyController($state, $timeout) {
$timeout(function(){
console.log($state.current);
});
}
})();
Its just because of the load time angular takes to give you the current state.
If you try to get the current state by using $timeout function then it will give you correct result in $state.current.name
$timeout(function(){
$rootScope.currState = $state.current.name;
})

Categories