How to structure a multi step wizard with ajax calls in angularjs - javascript

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.

Related

Angular JS: can we set resolve or promise on controller which did not have route defined?

I am trying to fetch some data from server before controller get render.
I have found many answers for it with respect to routeProvider.
But my main issue is my controller does not bound with any route.
So is there any way to make this possible?
I have controller in following ways...
<!-- HERE I WANT TO BLOCK RENDERING TILL DATA GET LOAD -->
<AppController>
<ng-view>
</AppController>
It sounds like a resolve is what you are looking for, but if you are not using a routing table for this controller, you'll not have this option. Why not just resolve an asynchronous call in your controller, and set scope variables inside the callback. This is what I interpret your desire to await controller "rendering", whereas a resolve through a route table would await controller instantiation. Observe the following...
module.controller('ctrl', function($scope, $http) {
$http.get('/uri').then(function(response) {
// set $scope variables here
});
console.log('executed first');
});
You could also set a variable to prevent the associated view from rendering if your data call is lengthy. This would prevent the UI from "dancing." Observe the following changes to the above example...
<div ng-controller="ctrl" ng-show="resolved"></div>
module.controller('ctrl', function($scope, $http) {
$http.get('/uri').then(function(response) {
$scope.resolved = true; // show rendering
});
});
JSFiddle Link - simplified demo
JSFiddle Link - demo ng-if
One idea will work
in html controller:
<p ng-if="notLoadedContent">Wait</p>
<div ng-if="!notLoadedContent">Content fetched</div>
And in Controller all controller is inside one function will start all, and the controller will be :
fetch(init)
$scope.notLoaded = true;
function init(){
$scope.notLoaded=false;
}
hope it help you

Angular: how to make a "search" take you to another route and display results?

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. :)

AngularJS controller needs to have data from $http before initialized

I have a problem to initialize controller in AngularJS.
Below is the process which I want to implement.
Get data from mongoDB by $http before DOM is ready.
By Using the data, some div element should be created using ng-repeat.
But the problem is that the view is rendered before controller gets data from $http.
So I searched all over the stack-overflow and google, and found about ui-router's resolve function.
Below is my ui-router code.
.state('floor', {
url: '/floor/:domainId',
templateUrl: 'modules/floor/views/floor.client.view.html',
controller: 'FloorController',
resolve: {
initData: ['$http', '$stateParams', function($http, $stateParams) {
return $http.get('/users/getFloor/' + $stateParams.domainId).success(function(user) {
return $http.get('/users/' + user._id + '/data/get').success(function(data) {
return data;
});
});
}]
}
})
The first $http is to get user id from domain id. (e.g. User can connect to /floor/my_personal_domain_address), and the second $http is what I need for initial data.
This is my Controller code.
angular.module('floor').controller('FloorController', ['$scope', 'initData',
function($scope, initData) {
console.log(initData);
}]);
Small tip or search keyword or anything will be very thankful for me.
I'm still learning AngularJS so just give me a small tip please.. Thank you!
UPDATE
This was my misunderstanding of how controller works. As some people commented, I didn't have to use resolve to retrieve data before controller initialized. The problem was occurred because I declared array variable used in ng-repeat as [] for the first time and client shows error. If I declare the variable after I get value from database, controller data-bind it to view properly.
So the problem is solved. Thank you all for valuable comments and answers.
UPDATE 2
Anyway, ui-router's resolve can return a value even though it is promise. I worked for it for some hours, and what I found out is that if I return $http promise in resolve, controller can get its data when successful. But if $http error is occurred in resolve, nothing can catch it. So if there's someone who wants to use resolve to send data to controller before it is initialized, I think it must be used with care.
Get data from mongoDB by $http before DOM is ready.
In this case probably the simpler solution would be not to make any tricky $http requests before Angular initialization but instead just to embed your data as JavaScript global variable into the main HMTL page just before loading of angular.min.js script.
I don't know if I get your question correctly, but this should help you:
(from the ui-router docs https://github.com/angular-ui/ui-router/wiki)
// Another promise example. If you need to do some
// processing of the result, use .then, and your
// promise is chained in for free. This is another
// typical use case of resolve.
promiseObj2: function($http){
return $http({method: 'GET', url: '/someUrl'})
.then (function (data) {
return doSomeStuffFirst(data);
});
},
So you'd have to use .then() instead of .success() and it should work.

Angular ui router dynamic url from server and API

I'm working on a web app which implements a wizard-like behavior. it uses an API to get the "wizard" steps. the API works in a way where you send a request with the current step and all previous answers so far > and get the next step (which also includes the step "name").
My problem is with the URL's of my app, since I need/want them to match the current step. BUT I don't know what is the "current" step until the user already routed to the page.
Example:
user clicks on <a ui-sref="wizard({step: 'second'})"> ('second' is the current step)
$stateProvider than invoke templateUrl e.g: http://whatever.com/getStep/second
server gets the "second" param and passes to the API: current step: second & answer to first step 1 (for example) than receiving the next step HTML and name - lets say: "step_three" and some HTML
Angular renders that HTML
problem with the example above: the user is now in http://myapp.com/#/wizard/second and the HTML that is shown is for the "step_three"
What I would like to do is a request to the server with does params & without routing > than according to the response set the state config: url and template and than "route" to that state. so that the user will be in http://myapp.com/#/wizard/XXX and see the HTML for XXX...
Is this possible? any ideas?
Simplistic approach (you could choose template on the fly in route if you would like)
.state('wizard', function() {
url: 'wizard/:step',
templateUrl: 'views/template/wizard.html',
controller: function($scope, $stateParams, stepData) {
$scope.step = $stateParams.step;
$scope.stepData = stepData;
},
resolve: {
stepData: function(api, $stateParams) {
return api.getdata($stateParams.step);
}
}
})
in wizard html:
<div ng-show='step == "first"'>first data content</div>
<div ng-show='step == "two"'>second data content</div>
<div ng-show='step == "three"'>third data content</div>
if you want to avoid using ng-shows and prefer a different template depending on the route, then use the templateProvider instead of templateUrl:
templateProvider: function($stateParams) {
return a valid string path to the template based on the $stateParams.step value
},
Just a note which might not directly answer your question but give you another option of what is possible to do with ui-router and ng bindings.
In your case you can specify ui-sref as such:
ui-sref="wizard({step: 'second'})"
However you can also use variable bindings inside the ui-sref.
ui-sref="wizard({step: step})"
^
this is a variable in your scope ($scope.step = 'second')
You can also use variable to modify the url-name like:
ui-sref="wizard{{step}}({step: 'second'})"
$scope.step = 'Second'; //results in: ui-sref="wizardSecond({step: 'second'})"
$scope.step = 'Foo'; //results in: ui-sref="wizardFoo({step: 'second'})"

AngularJS $location.path() not reloading data of the destination view

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

Categories

Resources