I want to make an include refresh itself when the state changes in my app.
Example:
<div ng-include="'partials/search-form.html'"></div>
Global listener:
phonecatApp.run(function ($state,$rootScope, $log) {
$rootScope.$state = $state;
$rootScope.$on('$stateChangeSuccess', function(){
if( !$state.includes('search') ) {
// refresh the search-form include in the header
}
});
});
How can I do this? Because as the form is outside of the ui-view and when I change the page it still has a value from the previous search query as it's still in the previous state... How can I refresh this so it's updated???
Note: creating another ui-view isn't a solution as this form is just a simple include shown in a header across all pages and doesn't relate to any states, etc.
Update: So you can see what I am doing with the form and controller:
phonecatControllers.controller('SearchCtrl', function($rootScope, $scope, $state, $location) {
$scope.query = ($state.includes('search') ? $location.search()['q'] : '');
$scope.filterQuery = ($state.includes('search') ? $location.search()['q'] : '');
if(!$scope.query){
$location.search('q', null);
}
$scope.queryChanged = function () {
if($scope.query){
$state.go('search', {'q': $scope.query} );
} else {
$location.search('q', null);
}
$scope.filterQuery = $scope.query;
}
});
<form class="form-search" ng-controller="SearchCtrl" ng-submit="queryChanged()">
<input name="q" ng-model="query" id="filter" type="text">
<button type="submit" class="btn">Search</button>
</form>
Update: The best idea I could come up with, would be to empty the input field like so:
var q = document.getElementsByName('q');
q.setAttribute('value', '');
When the state changes is NOT the search state. This automatically fires a change event on the input and then causes it to have it's ng-dirty class removed, etc.
Since the form is available globally, my advice is that you put the query in the $rootScope. The rootscope is available on the entire application (if you inject it, of course), so you would be able to reset the value at any moment.
An even better solution would be to store the query value in the rootscope and access it using a service.
For reloading the route you can use:
$route.reload();
But i'm not sure if that will reload the included file.
Perhaps you can set the string of the included file as a $scope variable and then set it as an empty string first and then put it back again? Not sure if it will work, probably needs a $timeout, $digest or $watch to make sure it is ready to flip.
Another option would be to set an
ng-if="someForm"
var to the first route and when you are done, set it to false and have another
ng-if="someOtherForm"
for the second include. Or use the same but set it to something falsy first.
But if it is about cleaning the form, there are other ways to do that. Like setting all model-vars to an empty string or null. So if you have it at $scope.searchEntry, you set it undefined or "".
In your example:
$scope.query = "";
This is what I have at the moment:
phonecatApp.run(function ($state, $rootScope, $log, $location) {
$rootScope.$state = $state;
$rootScope.$on('$stateChangeSuccess', function(){
if( !$state.includes('search') ) {
$('[name=q]').val('');
} else {
$('[name=q]').val($location.search()['q']);
}
});
});
I've had to use jQuery because Vanialla JS couldn't see the element. But it basically just sets the input value depending on where you are in the app.
The ONLY issue is that if you submit the form when it's empty it will then default back to what it was before! Presumably because the model hasn't been reset!
So if I do this instead:
$('[name=q]').val('').change();
It then triggers the model to be reset (from what I can tell) as it no longer submits the previous result... Thoughts? It feels dirty, but it works!
Related
I have a navbar directive which sits above ng-view. It utilises the $rootScope to trigger events to show buttons in certain views.
I am trying to add a button to the directive template which will switch a boolean in a controller for a particular view. The view shows a period of time and each period has a particular boolean that I want to switch from the directive.
The boolean value is saved in a local storage object which is initialized when each iteration of this particular view is loaded.
First, the value needs to be communicated to the directive so the button can display as being set to true or false. When the switch is toggled, the value of that boolean needs to make its way from the directive, through the $rootScope, to the controller and then be saved in the storage object.
When the view is changed, the whole process needs to repeat. The switch needs to be able to be switched on and off multiple times, obviously.
At present, I am emitting the value from the controller to the $rootScope and then listening for that value in the directive link function.
However, what is the best way to get that $rootScope value BACK into the controller. I tried setting up a $rootScope.$watch in the controller which appeared to work on any single page but when navigating between different time periods, the $rootScope value of the boolean was not resetting properly.
I tried resetting the value in the controller initialization as follows:
$rootScope.booleanValue = false;
but this didn't work.
I have also tried the following:
$scope.$on('$routeChangeSuccess', function (next, current) {
$rootScope.booleanValue = false;
});
but I can't get the whole cycle to work properly. It still seems as though the value of the property in the $rootScope is not resetting from the view before and is then carrying over when an adjacent pay period view is loaded.
I hope this makes sense. I will save you from too much code as I think the basic idea is here.
What you are trying to do is share state from your navbar directive (an isolate scope) and your view's controller. I recommend you use a factory provider service to share that state:
angular.module('myApp').factory('navbarState', function (){
return {started: false}
});
In your navbar directive, inject the service and store the state in that service:
angular.module('myApp').directive('navigationBar', [
'$rootScope',
'navbarState',
//'NavigationStackService',
//'NavigationBarService',
function ($rootScope, navbarState) {
function link(scope, element) {
scope.startEditMode = function(){
console.log("Edit clicked");
navbarState.started=true;
//NavigationBarService.hideNavigationEdit();
//NavigationBarService.showNavigationDone();
};
scope.finishEditMode = function(){
console.log("Done clicked");
navbarState.started=false;
//NavigationBarService.hideNavigationDone();
//NavigationBarService.showNavigationEdit();
};
}
return {
templateUrl: 'templates/navigation-bar.html',
restrict: 'E',
scope: {},
link: link
};
}
]);
In your view controller, retrieve the service, put it on the controller's scope, and use it in your template.
angular.module('myApp').controller('controller2', function(navbarState) {
console.log("view controller2 started");
var vm = this;
vm.navState = navbarState;
vm.message = "hello from ct2";
});
The DEMO on JSFiddle.
We have a mover directive that uses 2 list controls. The problem is that when I simply click on an item in the assigned items list, it makes my form dirty. I tried the following solution in the directive controller onChanged event (which is called by the second list ng-change):
$scope.onChanged = function (assigned) {
var currentState = $scope.form.$dirty;
$scope.selectedItem = assigned[0];
if (currentState === false)
$scope.form.$setPristine();
}
However, the currentState is already true, so my code does nothing. How can I prevent the clicking in the list control to set form's dirty status? I found two related questions How can I exclude controls from making form Dirty? and How can I denote which input fields have changed in AngularJS but it's not clear to me if I should try either of these solutions. The code checks for $scope.form.$dirty in a few places, so the best solution is somehow to make sure that clicking on the list doesn't make it dirty. I also have noDirty directive which I haven't yet tried applying to that list. I'm going to try that now.
The solution I implemented for now is the following change in the directive:
var directive = {
controller: ['$scope', '$timeout', function ($scope, $timeout) {
$scope.upDisabled = true;
$scope.downDisabled = true;
$scope.assigned = null;
$timeout(function () {
$scope.form.assignedList.$pristine = false;
});
based on last answer in that thread Prevent input from setting form $dirty angularjs. In my quick limited test it seems to work. I originally tried to just use our noDirtyCheck directive on the list, but somehow it didn't work in my first try, so I already switched to this solution.
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. :)
HTML:
...
<input ng-init="item.check=false" type="checkbox" ng-model="item.check">
...
<input ng-init="item.name=''" class="form-control" ng-model="item.name">
...
JS:
var myApp = angular.module('myApp', []);
myApp.controller('AppCtrl', function ($scope, $http) {
var refresh = function() {
$scope.item.check = false;
$scope.item.name = "";
}
//Call upon loading app to initialize values, also called upon events to refresh the field
refresh();
I initially thought that just the JS is sufficient to initialize the values and so I got rid of both the checkbox and the form's ng-init then ended up getting the error:
TypeError: Cannot set property 'name' of undefined
The app works if I just leave both ng-init. It also works when I remove the ng-init from only one of the 2 elements (so either from the checkbox or the form) but it will not work if I remove both. What is going on here? Am I doing this correctly or is there a better way to initialize?
Thanks.
You can add $scope.item = {} in first line of your controller.
Javascript reports error when you are trying to access $scope.item.name when $scope.item itself doesn't exist (or not an object in some cases).
When you do ng-init, angular creates the object for you so you don't get the error.
Add this to the controller
$scope.item = {};
I have a check box that I need to keep checked after navigating away from the page. I am using AngularJS and bootstrap. Right now I am resetting the bound variable to false every time I reload the page (when the controller runs): how can I store my most up to date variable ($scope.disableCheck)?
In my controller....
$scope.disableCheck = false;
$scope.removeCheck = function () {
$scope.disableCheck = !$scope.disableCheck;
}
And in my HTML...
<input class="notification-checkbox" type="checkbox" value="{{disableCheck}}" ng-click="removeCheck()" ng-clicked="{{disableCheck}}">
Try using $rootScope instead. It is global to all controllers
Something like this
.controller('someCtrl', function($scope, $rootScope) {
$rootScope.disableCheck = true;//set root scope here and refer to it as needed from other controlers
})