I am implementing an advanced search in an application I'm working on. In this search I want the back and next buttons from the browser to work with the search.
To achieve this I added this to my $routeProvider:
.when('/search', {
templateUrl: 'Scripts/App/Views/Search.html',
controller: 'SearchController',
reloadOnSearch: false
})
In my SearchController I have a search function on the $scope that just adds some query string parameters to my url:
$scope.advancedSearch = function(){
$location.search("page", $scope.currentPage);
$location.search("groupingField", $scope.groupingField);
$location.search("topgroup", angular.toJson($scope.filter));
}
I listen to $routeUpdate, get the query string parameters and execute the search with these parameters.
$scope.$on('$routeUpdate', function () {
var qs = $location.search();
var topGroup = qs["topgroup"];
$scope.filter = angular.fromJson(topGroup);
$scope.groupingField = qs["groupingField"];
$scope.currentPage = qs["page"];
performSearch();
});
In my real search method I check the form for errors. If there are any I show a message else I perform the search (do an api call).
var performSearch = function () {
if ($scope.searchForm.$invalid) {
$scope.message = "Please fix the errors before you search.";
} else {
//get search results from database and put them on $scope.
}
}
With this code I can search and get the correct results. If I do a new search it also get's the correct results and I can use the back and next buttons from the browser.
When I do a search and my form is invalid the message shows, but when I go back after an invalid search the $invalid of the form updates after my "performSearch" method is called. This causes "Please fix the errors before you search." message to display even if the form is $valid.
If I click on the next button after this I get even more trouble since the form is $valid now but with the querystring parameters filled in it should be $invalid. Again this only updates after the performSearch has been called. wich is to late.
It might be hard to understand my problem, if something is unclear ask away!
Found the answer!
On the $routeupdate I waited for a digest cycle to finish and update the form properties. This can be done with a $timeout without a time.
$scope.$on('$routeUpdate', function () {
var qs = $location.search();
var topGroup = qs["topgroup"];
$scope.filter = angular.fromJson(topGroup);
$scope.groupingField = qs["groupingField"];
$scope.currentPage = qs["page"];
$timeout(function () {
performSearch();
});
});
Related
I'm having trouble with a protractor test.
Overview of the test
Click button to show a form
Fill out the form
Click another button to save the form - this should call a function in one of my controllers that makes an http call and then reloads some models on the page.
When clicking the final "save" button, everything seems to freeze and the attached ng-click function never seems to get called. I don't see any errors in the console when I use browser.pause. I can see that the button is in fact clicked, but at that point, nothing seems to happen.
Final button definition:
this.addConfigFilterLineButton = element(by.css('[ng-click="cflCtrl.create();"]'));
Function that fills out the form:
this.addNewConfigFilterLine = function (cb) {
var self = this;
var deferred = protractor.promise.defer();
browser.executeScript('window.scrollTo(0,10000);')
.then(function(){
self.newConfigFilterLineButton.click();
// code that fills out the form
self.addConfigFilterLineButton.click();
browser.waitForAngular()
.then(function(){
deferred.fulfill();
});
});
return deferred.promise;
};
Spec
it('should allow creating a new ConfigFilterLine', function (done) {
var length;
settingsPage.configFilterLines.count()
.then(function(count){
length = count;
return settingsPage.addNewConfigFilterLine();
})
.then(function(){
expect(settingsPage.configFilterLines.count()).to.eventually.equal(length+1);
done();
});
});
I've tried with browser.waitForAngular and without it, and it doesn't seem to matter. When the button is clicked, nothing happens.
Any ideas would be helpful. Let me know if there's more info I can provide.
Instead of using
this.addConfigFilterLineButton = element(by.css('[ng-click="cflCtrl.create();"]'));
try something more like this:
this.addConfigFilterLineButton = element(by.id('id-of-final-button'));
My guess is that Protractor isn't correctly finding "addConfigFilterLineButton".
SOLVED
the trick was using window.location = '....';
I'm new to the AngularJS and I'm experiencing problems with redirecting after saving form data.
when I click 'Save' button, I send the data to the server, which saves it to the database.
$scope.save = function (trips) {
if (trips.length != 0) {
tripsRepositorySave.save(trips).then(function () {
$location.url('/Trips?id=' + trips[0].id);
});
}
};
this is the tripsRepositorySave
app.factory('tripsRepositorySave', function ($http, $q) {
return {
save: function (trips) {
var deferred = $q.defer();
$http.post('/Trips/Save', trips).success(function () { deferred.resolve(); });
return deferred.promise;
}
}
})
and the result is this url
http://localhost:3333/Trips?assignmentID=2#/Trips?assignmentID=2
I cant manage to make it rewrite the whole path, I have tried $location.path, $scope.apply() and everything i could google, but the result is still the same.
Another thing is that I want the page to get reloaded as well and it doesnt seem to be happening.
Thank you for any suggestions :)
You want something like this:
$location.path("/Trips").search('id', trips[0].id);
Also see the hash function if you need to append a hash.
https://docs.angularjs.org/api/ng/service/$location
In addition to Andy Gaskell answer.
$location.url(...) will manage only part after '#'. So your code works as expected.
If you want to reload full page use window.location or $window
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?
I have a search 'page' in my AngularJS app which essentially consists of a view that holds the search form (and displays the results) and a controller which handles the search request. When the user types in a search query and hits 'Search', the $scope.submit() method is called and I can see the results properly. However, when the user clicks on a result and then goes back to the search page, it is blank. I thought of implementing a $cookieStore-based solution so that the query is stored in a cookie and whenever the user goes back to the search page, it automatically re-runs the previous search so they don't have to do it manually. Problem is, the model updates (search gets run from cookieStore value) but the view stays the same (blank). Here's a sample of my controller:
.controller('SearchCtrl', ['$scope', '$http', '$cookieStore','authService', function($scope, $http, $cookieStore, authService) {
var submitted = false;
$scope.submit = function(query){
$cookieStore.query = query;
submitted = true;
$http.jsonp(url).success(function(data) {
$scope.searchResults = data;
});
}
/*
Rerun query if user has pressed "back" or "home" button automatically:
*/
if(!submitted && $cookieStore.query){
console.log("submitting query from cookie store", $cookieStore.query);
$scope.submit($cookieStore.query);
}
... });
I tried using $scope.$apply() after the auto-search but still no joy. The view just won't update. Any hints you guys could give me? Cheers
You should place $scope.$apply at the end of your callback function. It's because $http makes an async AJAX call and by the time response comes back Angular is already done auto-$applying changes. So when you check the model you see the difference but since Angular is no longer $applying the difference cannot be seen on the view.
So when you add $scope.$apply you will have something like this:
.controller('SearchCtrl', ['$scope', '$http', '$cookieStore','authService', function($scope, $http, $cookieStore, authService) {
var submitted = false;
$scope.submit = function(query){
$cookieStore.query = query;
submitted = true;
$http.jsonp(url).success(function(data) {
$scope.searchResults = data;
$scope.$apply();
});
}
/*
Rerun query if user has pressed "back" or "home" button automatically:
*/
if(!submitted && $cookieStore.query){
console.log("submitting query from cookie store", $cookieStore.query);
$scope.submit($cookieStore.query);
}
... });
My AngularJS controller is calling an API to get a list of members. The API can be queried with multiple filters, like age range, name search, zipcode, etc.
What I am trying to accomplish is to make the user feel like the search is really fast. So while the user is filtering, I am $watch'ing the filters for changes. When a filter changes I immediately do a new search API call, instead of waiting for the user to click "search".
The idea is that when the user finishes filtering and clicks "search", the system should already be finished searching and can just output the search results.
The problem: When a user clicks "search" and the publish() function is called, I have to make sure that no API search is currently ongoing and if one is, wait for it to finish. I then publish the search results from the hidden memberslist.temp to the visible memberslist.members.
// Current code works, except the publish function. See the TODO:
app.controller('SearchCtrl', ['$scope', 'Restangular', '$timeout', function($scope, Restangular, $timeout) {
// Pushes prefetched search results over to the visible search results
// TODO: Check if search is ongoing...
$scope.publish = function() {
$scope.memberslist.members = $scope.memberslist.temp;
};
// Watch filters for changes
var searchTimer = false;
var previousSearch = [$scope.name, $scope.minAge, $scope.maxAge];
$scope.$watch('[name, minAge, maxAge]', function(newVal, oldVal, scope){
if (searchTimer) {
$timeout.cancel(searchTimer);
}
// Do search only after the user has stopped typing for 400 ms
searchTimer = $timeout(function(){
if (!_.isEqual(newVal, previousSearch)) {
previousSearch = newVal;
scope.search();
}
}, 400);
}, true);
$scope.search = function() {
var query_params = {
name: $scope.name,
price_min: $scope.minAge,
price_max: $scope.maxAge
};
Restangular.all('members').getList(query_params)
.then(function(members){
$scope.memberslist.temp = members;
});
};
}]);
My first thought was to set a variable like isSearching = true, then set that back to false once the search call returns it's results. Can't seem to get it to work though.
Perhaps something like:
$scope.$watch('!is_searching && publishing', function(newVal){
if (newVal) {
$scope.memberslist.members = $scope.memberslist.temp;
$scope.publishing = false;
}
})
Set "is_searching" to true on the scope when the search starts, and set it to false when it is done. Set "publishing" on the scope when the search button is clicked.
That should give you the desired behavior. Publish right away if no search is running, otherwise wait until search is complete.