I have an app which creates several surveys with random survey ids. As the ids are generated in the backend they are set in the controller. I read the documentation on that, however I do not really understand how to set the routeparams in order to always reach the page /survey/:surveryID.
Here is my code so far:
App Config:
...
.when('/survey/:surveyId', {
templateUrl: 'views/survey.html',
controller: 'SurveyCtrl',
controllerAs: 'survey'
})
Controller:
function SurveyCtrl($scope, RequestService, _, $location, $routeParams) {
$scope.go = function () {
$location.path('/#/survey/' + Math.random());
};
}
View with the link to /survey/:surveyId:
<div>
<md-button ng-click="go()">Enter Survey</md-button>
</div>
I know that this is not the right way and it is not even working. Can someone tell me how to dynamically create these params and set them in the controller so I can access the link with a button and then when clicked reach the survey/:surveyId page?
To get your work done,
$location.path('/survey/' + Math.random());
You can use search method to pass params as well,
$location.path('/myURL/').search({param: 'value'});
$location methods are chainable.
this produce :
/myURL/?param=value
You could also use the updateParams method for that:
$route.updateParams({'surveryID': Math.random()});
And access the data using $routeParams.surveryID
Related
I want to build a multi step wizard with ajax calls in between:
I currently use ui.router for views of the wizard steps which works fine.
On the first page the users enters some data e.g. playerid.
On the second page i want to display some data pulled from the server corresponding to that playerid.
How should i structure that? Because i read that controllers should only write to the model, but i need to read playerid the user entered to make the ajax call..?
Here is a Plunk how i do it right now:
http://plnkr.co/edit/4ZEdYHUqovn2YfkUpp2y?p=info
I personally would have done it this way (plunker):
The routing :
$stateProvider
.state('view1', {
url: "/view1",
templateUrl: "view1.html",
controller:"WizardCtrlStep1"
})
.state('view2', {
url: "/view2",
templateUrl: "view2.html",
controller:"WizardCtrlStep2",
params:{
playerId:null
},
resolve:{
player:function($http, $stateParams){
//you can use the player id here
console.log($stateParams.playerId);
return $http.get("test.json");
}
}
})
I really really like to have a single controller per state. It avoid thing to get messy.
I also use a resolve to do the ajax call before the step2 view loading.
Here is the controller of the 2nd step
//I inject the "player" resolved value
controller('WizardCtrlStep2', function($scope, player) {
$scope.name = 'World';
//to access the result of the $http call use .data
$scope.player = player.data;
})
And finally the HTML
<input type="text" ng-model="main.playerId">
<button ui-sref="view2({playerId:main.playerId})">proceed</button>
Here i give ui-sref a param for "playerId" that will be used in the resolve function.
Hope it was clear, if you have any question feel free to ask.
I have a main page with a nav, and each nav option takes you to another route. It all looks like a single page app, but each "page" has it's own route and controller.
My problem is that I want to put a search box in the navbar. When someone uses the searchbox, I want to take the user to the "search" route and then display the results. I'm having a lot of trouble figuring out these two issues:
Where do I store this "searchbox" logic? E.g. when someone searches, they choose the type of search from a dropdown, then the search query in the inputbox. I have special logic to automatically choose which dropdown value based on the value typed in the inputbox.
How do I redirect to the
"search" route and display the results based on the input from the
previous page?
It's probably clear I'm a newby to Angular. I'm happy to work out the details, but I'm mainly looking to understand how to structure the solution to this problem. Thanks in advance for your help.
What I love about Angular the most is the amount of options you can apply.
Your goal can be reached either by using a service. A service is a singleton class which you can request from controllers. Being a singleton what ever value you store in the service is available to all controllers. You can than either $watch for value change, use $broadcast to notify data change or use $routeParams to send data with route change.
A service is built as follows :
The following assume you have a global module var named 'app'
app.service('myService', function(){
var myValue;
this.getMyValue = function(){
return myValue;
};
this.setMyValue = function(value){
myValue = value;
};
});
Then you request a service from a controller like you request an angular service such as $scope.
app.controller('myController', ['$scope', 'myServce', function($scope, myService){
$scope.myValue = myService.getMyValue();
//Example watch
$scope.$watch('myValue',function(){
//Search criteria changed!!
}, true);
}]);
Angular is terrific..have fun coding
Basically you would want an own state for your search page, so this is where we begin (I expect you to use the ui-router and not Angulars built in router):
.state('search', {
url: "/search",
templateUrl: "pages/search.html",
controller: 'SearchController as ctrl',
params: { searchString: {} }
})
As you can see, I've defined an additional parameter for the search string that is not part of the URL. Of course, if you like, you could change that and move the parameter to the URL instead:
.state('search', {
url: "/search/:searchString",
templateUrl: "pages/search.html",
controller: 'SearchController as ctrl'
})
The actual search input is pretty straight forward as well, because it's only HTML:
<input type="text" ng-model="searchString" on-key-enter="ctrl.goSearch(searchString)">
The function for the state change has to be placed in the controller for the primary template (e.g. the controller of your navigation bar if the search is located there):
var vm = this;
vm.goSearch = goSearch;
function goSearch(searchString) {
$state.go('main.search', { searchString: searchString });
}
Of interest is also the on-key-enter directive that I've added:
angular.module('your.module')
.directive('onKeyEnter', OnKeyEnter);
function OnKeyEnter() {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.onKeyEnter);
});
event.preventDefault();
}
});
};
}
On pressing the enter-key, it will call the function you supply as attribute value. Of course you could also use a button with ng-click instead of this directive, but I think it simply looks better.
Last, but not least, you need a Search Controller and a HTML template for your search page, which I won't give to you, as it is up to you what you display here. For the controller, you only need to know how you can access the search string:
angular.module('your.module')
.controller('SearchController', SearchController);
SearchController.$inject = ['$scope', '$stateParams'];
function SearchController($scope, $stateParams) {
$scope.searchString = $stateParams.searchString;
/* DO THE SEARCH LOGIC, e.g. database lookup */
}
Hope this helps to find the proper way. :)
In my angular project, when changing the path with $location.path('/foobar') the destination view is displayed but the data aren't reloaded (typically after saving an item and going back to the list, the list is not updated).
I tried to add $route.reload() or $scope.apply(), but nothing change.
I don't know what's wrong or missing to make this work.
UPDATE
$location.url() doesnt' work either
I'm using angular 1.2.26
UPDATE 2 - ANSWER
Ok, after a lot of comments and answers, I think it's time to end this.
I didn't think it would have been a so complicated question.
So, my conclusion, giving all you said is :
Giving simple example of #yvesmancera, the default behavior of the controller is to reload itself
In a complex controller with a resource factory and some REST calls, any save or update action should also manually update the list reference, or trigger a full reload of the list
All of you gave me some good advices, so thank you.
Use $window.location.href. to reload the page. I just check on $location document:
Page reload navigation
The $location service allows you to change only the URL; it does not allow you to reload the page. When you need to change the URL and reload the page or navigate to a different page, please use a lower level API, $window.location.href.
Example:
$window.location.href = "/your/path/here";
I had the same problem just yesterday, if you try to navigate to the same path you're already in, angular won't try to reload the view and controller. What fixed it for me is appending a "/" at the end of each route in $routeProvider, e.g:
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
})
.when('/About/', {
templateUrl: 'views/about.html',
controller: 'AboutCtrl'
})
.when('/Contact/', {
templateUrl: 'views/contact.html',
controller: 'ContactCtrl'
})
Edit
Here is a working plunkr with angular 1.2.26
http://plnkr.co/edit/jkGKKCp0djN6Jvy2fIRd?p=preview
Pseudo Code:-
app.controller('myController', ['$scope', '$location','$http', 'ItemListService'
function($scope, $location, $http, ItemListService){
$scope.data = function(){
ItemListService.getAllItems(); //get all the items;
};
$scope.saveMethod = function(item){
$scope.data = ItemListService.save(item); //this is the refresh part, return data through save method. Pull the latest data and bind it to the scope.
$location.path('/fooView'); //dont think you even need this if you are entering data in a modal sorta thing, which on the same view.
}
}]);
You service should look like,
app.service('ItemListService', function(){
this.getAllItems = function(){
//get the items from itemList
//return all the items
}
this.save = function(item){
//save the item in itemList
//**return all items again, call getAllItems here too.
}
});
Hope this helps!!
You can switch https://github.com/angular-ui/ui-router it has method $state.reload() which can re-initialize whole controller.
If you dont want to switch ther is problem that controller is still living but you can implement after save
$rootScope.$broadcast('data:updated', $scope.data);
then wrap method of loading data in controller to function and then you can push new data to existing list / or make ajax reload
$rootScope.$on('data:updated',function(listener,data) {
$scope.data.push(data);
});
$rootScope.$on('data:updated',function()
{
callAjax.then(function(data) {
$scope.data = data;
}
});
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$on
Try $scope.dataModel.$save(); $location.url('/foobar'); Another reason might solve the problem is: when you redirect to /foobar, the controller of foobar should have a AJAX call to your server to load the new data. And you should use angular factory to make your AJAX calls. If it is still not working, can you give more information about the version of the angular you are using, as well as your backend framework and database.
$location.path("/login");
$timeout(() => $scope.$apply(), 1000);
works for me
Given the following code:
$routeProvider.when('/movies/:type', {
title: 'Movies',
templateUrl: 'pages/movies/movies.html',
controller: 'MoviesCtrl'
});
How can I access the :type param from inside the when function? I want to do something like so:
$routeProvider.when('/movies/:type', {
title: 'Movies' + ' - ' + :type,
templateUrl: 'pages/movies/movies.html',
controller: 'MoviesCtrl'
});
That value in title must be dinamically generated.
Thanks in adv.
I'm not sure why you are extending the route (config) object, but you are able to access routeParams from within your controller. That is also the recommended way.
The $routeParams service allows you to retrieve the current set of route parameters.
angular.module('MyModule').controller('MoviesCtrl',function($scope, $routeParams) {
$scope.currentMovieType = 'Filmes-' + $routeParams.type;
});
Let's say your route is something like that /movies/scifi. In this case $scope.currentMovieType becomes scifi and you can use {{currentMovieType}} in your view to populate this value. You can find detailed informations in the documentation.
Note that the $routeParams are only updated after a route change completes successfully. This means that you cannot rely on $routeParams being correct in route resolve functions. Instead you can use $route.current.params to access the new route's parameters.
It is not really possible, because the route config object is not as dynamic as you think. Whatever you put in the route configuration object, it cannot depend on the value that the route param is going to take in the future. Think of how this code gets executed : the configuration object will be evaluated only once, when the route is configured.
On the other hand, if you want to change the page's title when going through this route, you can do it using the $routeParamsservice to access the param value, and the $document service to change the page's title, either in a controller or in a resolveclause.
An example with the latter option:
$routeProvider.when('/movies/:type', angular.extend({
templateUrl: 'pages/movies/movies.html',
controller: 'MoviesCtrl',
resolve: {
title: ['$routeParams','$document', function ($routeParams, $document) {
var title = 'Filmes-' + $routeParams.type;
$document.title = title;
return title;
}]
}
}, routeParams));
That works also in a controller of course.
Some notes on your code :
I'm not even sure that there is a point setting a title property in a route config object, I don't see it in the documentation at least.
That second argument routeParams in that angular.extend call - the name is confusing, one could mistake it for the $routeParams service. I think you should call it routeDefaults or something like that instead.
Give a try to $location.absUrl(); requires some calculation too .
https://docs.angularjs.org/api/ng/service/$location
I'm trying to structure my app using the Restful/Ruby convension /<resource>/[method]/[id]. How I've done it previously when using a server-side MVC framework like CodeIgniter was to dynamically route based on the URI:
ex.
www.foo.com/bar/baz/1
The app would then use method baz in controller/class bar and return views/bar/baz.php (populated with data from bar->baz)
I would like to do the same in Angular, but I'm not sure if it supports this (and if it does, I'm not sure exactly how to go about it). At the moment I'm using $routeProvider's when method to specify each case. $location.path() looks like it might have what I need, but I don't think I can use it in app.js (within config()).
What I'd like to do is something like this:
.config([
'$routeProvider', function($routeProvider) {
$routeProvider
.when(//<resource> controller exists
resource+'/'+method, {
"templateURL": "views/" + resource + "/" + method + ".html",
"controller": resource
}
).otherwise({ "redirectTo":"/error" });
}
]);
And the router automatically calls the appropriate method.
EDIT Also, why does $routeProvider freak out when I specify when('/foo/bar', {…}) ?
EDIT 2 Per Lee's suggestion, I'm looking into doing something like this:
$routeProvider
.when(
'/:resource/:method/:id', {
"templateUrl": function(routeParams){
var path = 'views/'+routeParams.resource+'/';
return ( typeof routeParams.method === 'undefined' ) ?
path+'index.html' : path+routeParams.method+'.html';
},
"controller": RESOURCE
})
.otherwise({redirectTo: '/error'});
I noticed the following in $routeProvider's doc:
templateUrl – {string=|function()=} – path or function that returns a
path to an html template that should be used by ngView.
If templateUrl is a function, it will be called with the following
parameters:
• {Array.<Object>} - route parameters extracted from the current
$location.path() by applying the current route
Edit: The option to set templateUrl to a function is part of the unstable 1.1.2 build: #1963 (but it doesn't work as of 2013-02-07).
There is a dicussion about adding this functionality on AngularJS's Github: #1193 #1524, but I can't tell if it was actually implemented (in the docs from Dash quoted above, it looks like it has been, and the docs on the site haven't been updated yet).
EDIT 3 To clarify what I want to happen (per lee's request), in simplest terms, I would like to go to www.foo.com/index.html#/people
Angular should use controller people, automatically call its index method, and should serve up
./views/people/index.html
./views/people/map.html
Also, if I go to www.foo.com/index.html#/people/map
Angular should use the people controller again, but this time automcatically call its map method and serve up …map.html (because map was specified in the url)
./views/people/index.html
./views/people/map.html
Then, if I go to
www.foo.com/index.html#/widgets
Angular should serve up
./views/widgets/index.html
./views/widgets/details.html
The code for the router should be very generic—I shouldn't have to specify a .when() for every route.
Thinking about this a little more. You could just have a single controller for those generic CRUD/REST type operations. Then load the templates using the resource and view parameters.
Create
#/foo/create/0
This has it's own form template "/views/foo/create.html" and the 0 os just there for a placeholder.
on submit you would call a method on the controller ng-click="save()" which would post to the server at POST "/rest/foo".
Read
#/foo/view/1
Again the template "/views/foo/view.html" is just a view of the data
You can call a service method to get the data from your server using GET "/rest/foo/1"
Update
-#/foo/edit/1
Could use the same template as create or you could use a different one "/views/foo/edit.html" if you like.
Also pull the data using GET "/rest/foo/1"
Submit the data using PUT "/rest/foo/1"
Delete
#/foo/delete/1
service method would call DELETE "/rest/foo/1"
I don't think you want a hash for this, but you could use one because the controller could actually do a verification or anything you like to confirm the deletion. Maybe have a view called "/views/foo/delete.html" that asks if you want to delete the record. Then you could have ng-click="delete(itemid)" on a button somewhere that deletes the item via ajax.
All this could be done using a single controller/service and dynamically generating the service and view urls.
Anything that's custom you would need a custom controller and custom routes and service methods for. I could probably throw together an example, but not tonight.
Here is a project on github that does something close to what you are asking
EDIT:
I discovered something interesting that had not occurred to me before. If you leave out the controller in the route it will use the controller specified in the template. So as long as all the templates that you use for a given controller have ng-controller="resource" then it will load that controller for the template as expected. Of course with the current implementation of routes there are no optional parameters, so if you have two or three parameters you would need to specify a separate route. Biggest problem is it appears to call the controller method twice. I am guessing this is because there are two views with the same controller. However one view should replace the other so there should not be two calls. This seems like a bug to me. I also found some discussion of a possible new routing system in the works that may meet your needs, but it may be pretty far off: https://github.com/angular-ui/router/issues?page=1&state=open. The sample on github is now using the following method so you can browse that if you like.
var restrouteApp = angular.module('restrouteApp', [])
.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/:ctrl/:method', {
templateUrl: function(rp){
if(!rp.method) {rp.method = 'index';}
console.log('route one');
return 'views/'+rp.ctrl+'/'+rp.method+'.html';
}
})
.when('/:ctrl/:method/:id', {
templateUrl: function(rp){
if(!rp.method) {rp.method = 'index';}
console.log('route two');
return 'views/'+rp.ctrl+'/'+rp.method+'.html';
}
})
.otherwise({
redirectTo: '/resource1/'
});
}]);
And the templates:
<div ng-controller="resource1">
<h1> resource1/one.html </h1>
<div>{{r1data.selected}}</div>
</div>
Now in your controller you can do this to call the method dynamically.
restrouteApp.controller('resource1', function($scope,$routeParams,$log,Resource1Service) {
$log.info('new resource1');
$scope.controllername = $routeParams.ctrl;
$scope.r1data= Resource1Service.shared;
$scope.index = function(){
Resource1Service.index().then(function(){
//when the service returns
});
}
$scope.one = function(){
$scope.r1data.selected = $scope.r1data.resources[0];
}
$scope.two= function(){
$scope.r1data.selected = $scope.r1data.resources[1];
}
//call the specified method of this controller
$scope[$routeParams.method]();
});
/EDIT
To conform to existing routing systems like Rails, the ability to define the method in the route is now available.
I created a super simple solution that allows routes to call a method based on the route definition and a directive in the view. I think ui-router is not conventional and is too complicated for a such a "should be" core feature.
The project is called ngMethod and is located at: https://github.com/jzumbrun/ng-method.
An example of its use is: https://github.com/jzumbrun/chrome-apps-angularjs-bootstrap
So if I have a route like so:
$routeProvider.
when('/contacts/new', {
controller: 'ContactsController',
method: 'new',
templateUrl: $configProvider.template('contacts/form.html'),
});
$routeProvider.
when('/contacts/:id/edit', {
controller: 'ContactsController',
method: 'edit',
templateUrl: $configProvider.template('contacts/form.html'),
});
and I have ng-method in the contacts/form template:
<div class="col-lg-12" ng-method>
<form role="form">
...
Then the ng-method will call either $scope.edit() or $scope.new() in the ContactsController.
Than the contacts/form template can be shared, and depending on the route call the correct method
to load the data. This style is now more "Angularjs" and the loading the code is much like angular calling to modules and controllers.
The full directive that makes this happen is less than 20 lines of code:
app.directive('ngMethod', ['$route', function($route) {
return {
// Restrict it to be an attribute in this case
restrict: 'A',
// responsible for registering DOM listeners as well as updating the DOM
link: function(scope, element, attrs) {
// Call method without params. Use $routeParams
if(angular.isFunction(scope[attrs.ngMethod])){
scope[attrs.ngMethod]();
// default to the route method if attrs.ngMethod is empty
} else if(angular.isObject($route.current)
&& angular.isString($route.current['method'])
&& angular.isFunction(scope[$route.current['method']])){
scope[$route.current['method']]();
}
}
};
}]);
This is now possible with ui-router 0.2.8:
$stateProvider
.state('base', {
url: '/:resource/:collection/:id',
controllerProvider: function( $stateParams )
{ // assuming app.controller('FooCtrl',[…])
return $stateParams.collection + 'Ctrl';
},
templateUrl: function( $stateParams )
{
return '/partials/' + $stateParams.collection + '.html';
}
});
But in order to take advantage of $state.includes() on nav menus, this would probably be better:
$stateProvider
.state('base.RESOURCE_NAME1', {
url: '/:collection/:id',
controllerProvider: function( $stateParams )
{ // assuming the convention FooCtrl
return $stateParams.collection + 'Ctrl';
},
templateUrl: function( $stateParams )
{
return '/partials/' + $stateParams.collection + '.html';
}
}).state('base.RESOURCE_NAME2', {
url: '/:collection/:id',
controllerProvider: function( $stateParams )
{ // assuming the convention FooCtrl
return $stateParams.collection + 'Ctrl';
},
templateUrl: function( $stateParams )
{
return '/partials/' + $stateParams.collection + '.html';
}
});
The above could be simplified with a loop to build the states from an array of resources ($stateProvider supports adding states basically whenever):
var resources = [ 'r1', 'r2', '…' ];
for ( var r = resources.length-1; r >=0; r-- )
{
var name = resources[r];
$stateProvider.state('base.'+name, {
…
});
}
Caveat ui-router doesn't not really support optional state parameters (planned for v0.4)