How to execute AngularJS controller function on page load? - angularjs

Currently I have an Angular.js page that allows searching and displays results. User clicks on a search result, then clicks back button. I want the search results to be displayed again but I can't work out how to trigger the search to execute. Here's the detail:
My Angular.js page is a search page, with a search field and a search
button. The user can manually type in a query and press a button and
and ajax query is fired and the results are displayed. I update the URL with the search term. That all works fine.
User clicks on a result of the search and is taken to a different page - that works fine too.
User clicks back button, and goes back to my angular search page, and the correct URL is displayed, including the search term. All works fine.
I have bound the search field value to the search term in the URL, so it contains the expected search term. All works fine.
How do I get the search function to execute again without the user having to press the "search button"? If it was jquery then I would execute a function in the documentready function. I can't see the Angular.js equivalent.

On the one hand as #Mark-Rajcok said you can just get away with private inner function:
// at the bottom of your controller
var init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
// and fire it after definition
init();
Also you can take a look at ng-init directive. Implementation will be much like:
// register controller in html
<div data-ng-controller="myCtrl" data-ng-init="init()"></div>
// in controller
$scope.init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
But take care about it as angular documentation implies (since v1.2) to NOT use ng-init for that. However imo it depends on architecture of your app.
I used ng-init when I wanted to pass a value from back-end into angular app:
<div data-ng-controller="myCtrl" data-ng-init="init('%some_backend_value%')"></div>

Try this?
$scope.$on('$viewContentLoaded', function() {
//call it here
});

I could never get $viewContentLoaded to work for me, and ng-init should really only be used in an ng-repeat (according to the documentation), and also calling a function directly in a controller can cause errors if the code relies on an element that hasn't been defined yet.
This is what I do and it works for me:
$scope.$on('$routeChangeSuccess', function () {
// do something
});
Unless you're using ui-router. Then it's:
$scope.$on('$stateChangeSuccess', function () {
// do something
});

angular.element(document).ready(function () {
// your code here
});

Dimitri's/Mark's solution didn't work for me but using the $timeout function seems to work well to ensure your code only runs after the markup is rendered.
# Your controller, including $timeout
var $scope.init = function(){
//your code
}
$timeout($scope.init)
Hope it helps.

You can do this if you want to watch the viewContentLoaded DOM object to change and then do something. using $scope.$on works too but differently especially when you have one page mode on your routing.
$scope.$watch('$viewContentLoaded', function(){
// do something
});

You can use angular's $window object:
$window.onload = function(e) {
//your magic here
}

Another alternative:
var myInit = function () {
//...
};
angular.element(document).ready(myInit);
(via https://stackoverflow.com/a/30258904/148412)

Yet another alternative if you have a controller just specific to that page:
(function(){
//code to run
}());

When using $routeProvider you can resolve on .state and bootstrap your service. This is to say, you are going to load Controller and View, only after resolve your Service:
ui-routes
.state('nn', {
url: "/nn",
templateUrl: "views/home/n.html",
controller: 'nnCtrl',
resolve: {
initialised: function (ourBootstrapService, $q) {
var deferred = $q.defer();
ourBootstrapService.init().then(function(initialised) {
deferred.resolve(initialised);
});
return deferred.promise;
}
}
})
Service
function ourBootstrapService() {
function init(){
// this is what we need
}
}

Found Dmitry Evseev answer quite useful.
Case 1 : Using angularJs alone:
To execute a method on page load, you can use ng-init in the view and declare init method in controller, having said that use of heavier function is not recommended, as per the angular Docs on ng-init:
This directive can be abused to add unnecessary amounts of logic into your templates. There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, as seen in the demo below; and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope.
HTML:
<div ng-controller="searchController()">
<!-- renaming view code here, including the search box and the buttons -->
</div>
Controller:
app.controller('SearchCtrl', function(){
var doSearch = function(keyword){
//Search code here
}
doSearch($routeParams.searchKeyword);
})
Warning : Do not use this controller for another view meant for a different intention as it will cause the search method be executed there too.
Case 2 : Using Ionic:
The above code will work, just make sure the view cache is disabled in the route.js as:
route.js
.state('app', {
url : '/search',
cache : false, //disable caching of the view here
templateUrl : 'templates/search.html' ,
controller : 'SearchCtrl'
})
Hope this helps

I had the same problem and only this solution worked for me (it runs a function after a complete DOM has been loaded). I use this for scroll to anchor after page has been loaded:
angular.element(window.document.body).ready(function () {
// Your function that runs after all DOM is loaded
});

You can save the search results in a common service which can use from anywhere and doesn't clear when navigate to another page, and then you can set the search results with the saved data for the click of back button
function search(searchTerm) {
// retrieve the data here;
RetrievedData = CallService();
CommonFunctionalityService.saveSerachResults(RetrievedData);
}
For your backbutton
function Backbutton() {
RetrievedData = CommonFunctionalityService.retrieveResults();
}

call initial methods inside self initialize function.
(function initController() {
// do your initialize here
})();

Related

$templateCache from file undefined? When is the accessible by other js code? (with np-autocomplete)

I'm rather new to angular and I'm trying to integrate np-autocomplete in my application (https://github.com/ng-pros/np-autocomplete). However I can only get it to work when I'm passing a html string as a template inside the $scope.options and it doesn't work when I want to load it from a separate html.
the Code for my app looks as follows:
var eventsApp = angular.module('eventsApp',['ng-pros.directive.autocomplete'])
eventsApp.run(function($templateCache, $http) {
$http.get('test.html', {
cache: $templateCache
});
console.log($templateCache.get('test.html')) // --> returns undefined
setTimeout(function() {
console.log($templateCache.get('test.html')) // --> works fine
}, 1000);
//$templateCache.put('test.html', 'html string') //Would solve my issue in the controller,
//but I would rather prefer to load it from a separate html as I'm trying above
Inside my controller I am setting the options for autocomplete as follows:
controllers.createNewEventController = function ($scope) {
$scope.options = {
url: 'https://api.github.com/search/repositories',
delay: 300,
dataHolder: 'items',
searchParam: 'q',
itemTemplateUrl: 'test.html', // <-- Does not work
};
//other stuff...
}
however, it seems that test.html is undefined by the time np-autocomplete wants to use it (as it is also in first console.log above).
So my intuition tells me that the test.html is probably accessed in the controller before it is loaded in eventsApp.run(...). However I am not sure how to solve that?
Any help would be highly appreciated.
You are most likely correct in your assumption.
The call by $http is asynchronous, but the run block will not wait for it to finish. It will continue to execute and the execution will hit the controller etc before the template has been retrieved and cached.
One solution is to first retrieve all templates that you need then manually bootstrap your application.
Another way that should work is to defer the execution of the np-autocomplete directive until the template has been retrieved.
To prevent np-autocomplete from running too early you can use ng-if:
<div np-autocomplete="options" ng-if="viewModel.isReady"></div>
When the template has been retrieved you can fire an event:
$http.get('test.html', {
cache: $templateCache
}).success(function() {
$rootScope.$broadcast('templateIsReady');
});
In your controller listen for the event and react:
$scope.$on('templateIsReady', function () {
$scope.viewModel.isReady = true;
});
If you want you can stop listening immediately since the event should only fire once anyway:
var stopListening = $scope.$on('templateIsReady', function() {
$scope.viewModel.isReady = true;
stopListening();
});

How can I route a URL request to get data, plus run a function to change the CSS in angularjs?

I have a link that is used to load data, and then I want the CSS styling to change. I want both the data to be loaded and the CSS change to happen from one click event.
<section ng-controller="OffrdHeadCntrl">
<section id="menuOff" ng-class="{true: 'OpenMenu', false: 'ZeroWide'}[MenuStatus]">
<b>Menu</b><br>
<nav>
<ul id='OffrdCat'>
<li>Antiques</li>
<li>Appliances</li>
</ul>
</nav>
</section>
.... More HTML in another section here.
</section>
This is the routeProvider:
var ysshApp = angular.module('ysshApp', [
'ngRoute',
'ysshControllers',
'firebase'
]);
// 'ysshServices'
ysshApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/Antiques', {
templateUrl: 'Client_Pages/Offered_Menu.html',
controller: 'CommonController',
customInput: 'zeg0yv7nxle6a5sr2xl-ezs'
}).
when('/Appliances', {
templateUrl: 'Client_Pages/Offered_Menu.html',
controller: 'CommonController',
customInput: 'zh86tu488b8g6maw9wrfk'
})
.otherwise({ redirectTo:'/Tab_Home_1' });
}]);
When the user clicks 'Antiques', a bunch of data loads. The link triggers the URL to change, and that causes the $routeProvider to use a controller that then loads the data. I also have a function that changes the CSS styling:
This is the controller that loads the data:
var ysshControllers = angular.module('ysshControllers', []);
ysshControllers.controller('CommonController',
function($scope, $http, $route) {
$scope.dbKey = $route.current.customInput;
$scope.urlToDb = 'https://' + $scope.dbKey + '.firebaseio.com/.json';
$http.get($scope.urlToDb).success(function(data) {
var values = [];
for (var name in data) {
values.push(data[name]);
}
$scope.UsedItems = values;
});
// Initially order by date and time
$scope.orderProp = 'Time';
}
);
There is the function/controller? that changes the CSS: (I stripped some stuff out)
function OffrdHeadCntrl($scope) {
$scope.OpenCloseMenu = function() {
if ($scope.MenuStatus === true) {
$scope.OutputStatus = 'MuchWide';}
else
{$scope.OutputStatus = 'FullWideVal';}
};
}
I can get either the CSS to change, or the data to load, but not both. I've tried putting the ng-click="OpenCloseMenu()" in all kinds of places, but either the routeProvider will work, or the CSS change will work, but not both.
I've tried adding lines of code to the Common Controller, but that doesn't work. Can I add an event that runs after the data is done loading? I've seen examples that create a service that relates multiple controllers, but it's for binding data, not running a button click event.
I'm trying to change the CSS in order to hide the menu part the page before the data is loaded. I can use the Resolve object property to cause something to be resolved first, before the data is loaded. That would work, but I'm having trouble configuring the controller so it is available. I can get Resolve to work, but I can't get the function to fire. If I add this to the routeProvider, an alert msg will pop up, then once I click OK, the data loads.
resolve: {
// Run code in resolve first
whatever: function() {
alert("it ran");
}
}
This will work for me if I can get the function I need to run, but I haven't figured that out yet.
I successfully got a $routeChangeSuccess to run, but it runs BEFORE the data is loaded.
ysshControllers.controller('OffrdCatChg', function($scope) {
$scope.$on('$routeChangeSuccess', function(){
alert("it ran the success?");
});
});
So, I don't think that is going to help me. I created a new controller, and put the name of the controller into an HTML tag in that page. It runs when the route changes, but it runs before the data is loaded. That would be okay, but some of my other code runs afterwards, and sets the CSS back to the original. There must be a way to test for a particular controller having been run.
Since the data loaded is accompanied by url change, so link may be directly visited, not only from the clicking previous ones, so you should put data-loading in the first place.
As for the CSS changing, it's a state reflection thing, closely related to url changing(If I understand your description right), so you should listen to url change event, in something like a rootController( aside from ng-view), such as:
$scope.$on '$routeChangeSuccess', (e, current, previous) ->
//getting url segments, and updates css as it should be
return
The logic for me is:
click the link ---> router change ---> new controller(data load)
|
|
|--------->routeChangeSuccess event ---> update the css
I was able to find a solution. First of all, I think I had my controller defined wrong. I had a function defined, and it was working, but I don't think it was set up right. I had a function written like this:
function CntrlMyCSS($scope) {
$scope.OpenCloseMenu = function() {
if ($scope.MenuStatus === true) {
$scope.OutputStatus = 'MuchWide';}
else
{$scope.OutputStatus = 'FullWideVal';}
};
}
I didn't have it tied to a module, or defined as a controller. This is the new change.
ysshControllers.controller('HideShowInOffered',
ysshControllers.controller('HideShowInOffered',
function CntrlMyCSS($scope) {
$scope.OpenCloseMenu = function() {
if ($scope.MenuStatus === true) {
$scope.OutputStatus = 'MuchWide';}
else
{$scope.OutputStatus = 'FullWideVal';}
};
}
);
So, I guess that even though I added
ng-controller="CntrlMyCSS"
to a HTML tag, and it worked, it wasn't really a controller? idk. I was using the name of a function as the controller. I know that defining it as a controller seemed to make the stuff inside that controller available to my other controller. I still don't really understand, but basically I just added a line at the top of the function to tie it to the module, and define it as a controller, and change the HTML tag to the name of the controller instead of the name of the function.
The other thing that helped was being able to debug the code by setting break points, stepping through lines, and looking at assigned values. If it wasn't for that, I'd probably still be going down roads to nowhere.
The fix was basically just tweaking what I had set up. I tried $routeChangeSuccess, but by stepping through the code, I saw that it was firing before the data loaded. That didn't work for me, because the controller was setting default CSS values for when the page originally loads. So it would change the CSS, and then set it back to original. Which happens so fast, I wouldn't have known that without the debugger.
$scope.$on('$routeChangeSuccess', function(){
alert("it ran the success?");
});
Trying to get a fix through the routeProvider didn't seem to be of any use either. It was basically an issue of the code being available to be called, and learning the timing the program flow through debugging.
The lesson to the story? Take the time to make sure you have a way to step through your code, and determine what the program flow is. There are probably infinite numbers of bad guesses, and maybe only one solution. The odds are against you unless you can debug.
This is the controller that loads the data, then formats the data, then changes the CSS to close the menu.
ysshControllers.controller('CommonController',
function($scope, $http, $route) {
$scope.dbKey = $route.current.customInput;
$scope.urlToDb = 'https://' + $scope.dbKey + '.firebaseio.com/.json';
$http.get($scope.urlToDb).success(function(data) {
var values = [];
for (var name in data) {
values.push(data[name]);
}
$scope.UsedItems = values;
});
// Initially order by date and time
$scope.orderProp = 'Time';
$scope.MenuStatus = false;
$scope.OutputStatus = 'FullWideVal';
}
);

AngularJS loading progress bar

When using AngularJS and doing a redirect using $location.path('/path') the new page takes a while to load, especially on mobile.
Is there a way to add a progress bar for loading? Maybe something like YouTube has?
For a progress bar as YouTube has, you can take a look at ngprogress. Then just after the configuration of your app (for example), you can intercept route's events.
And do something like:
app.run(function($rootScope, ngProgress) {
$rootScope.$on('$routeChangeStart', function() {
ngProgress.start();
});
$rootScope.$on('$routeChangeSuccess', function() {
ngProgress.complete();
});
// Do the same with $routeChangeError
});
Since #Luc's anwser ngProgress changed a bit, and now you can only inject ngProgressFactory, that has to be used to create ngProgress instance. Also contrary to #Ketan Patil's answer you should only instantiate ngProgress once:
angular.module('appRoutes', ['ngProgress']).run(function ($rootScope, ngProgressFactory) {
// first create instance when app starts
$rootScope.progressbar = ngProgressFactory.createInstance();
$rootScope.$on("$routeChangeStart", function () {
$rootScope.progressbar.start();
});
$rootScope.$on("$routeChangeSuccess", function () {
$rootScope.progressbar.complete();
});
});
if it is the next route that takes time to load e.g. making ajax call before the controller is run (resolve config on route) then make use of $route service's $routeChangeStart, $routeChangeSuccess and $routeChangeError events.
register a top level controller (outside ng-view) that listens to these events and manages a boolean variable in its $scope.
use this variable with ng-show to overlay a "loading, please wait" div.
if the next route loads fast (i.e. its controller runs quickly) but data that are requested by the controller take a long to load then, i'm afraid, you have to manage the visibility state of spinners in your controller and view.
something like:
$scope.data = null;
$http.get("/whatever").success(function(data) {
$scope.data = data;
});
<div ng-show="data !== null">...</div>
<div ng-show="data === null" class="spinner"></div>
use angular-loading-bar
Standalone demo here ..
https://github.com/danday74/angular-loading-bar-standalone-demo
Here is a working solution which I am using in my application. ngProgress is the best library out there for showing load-bars when changing urls.
Remember to inject the ngProgressFactory instead of ngProgress, as opposed to Luc's solution.
angular.module('appRoutes', []).run(function ($rootScope, ngProgressFactory) {
$rootScope.$on("$routeChangeStart", function () {
$rootScope.progressbar = ngProgressFactory.createInstance();
$rootScope.progressbar.start();
});
$rootScope.$on("$routeChangeSuccess", function () {
$rootScope.progressbar.complete();
});
});
Update Nov-2015 - After analyzing this approach with chrome timings, I have observed that this would not be the correct way for adding a loading bar. Sure, the loading bar will be visible to visitors,but it will not be in sync with actual page load timings.

Send message between hierarchically equal controllers

This is the HTML (fragment):
<div class="header" ng-controller="Header" ng-hide="hideHeader"></div>
<div class="page" ng-view></div>
The .header has the same controller always, while the .page has different controllers based on the route.
The problem is that hideHeader is set in the controller for the current URL.
What would be the best way to tell the Header controller that the controller for the current route has changed the value of hideHeader?
I don't think setting it on the $routeScope is the right way. Also, most of the time, the header will be visible, there are very few pages that want to hide it.
Another idea is for that variable to be set in the config() method:
$routeProvider
.when('/',
{
templateUrl:'views/hello.html',
controller:'Hello',
hideHeader:true
});
However, I am not sure that's a proper "AngularJS" way of doing that.
What's my best option?
Thank you.
I'd lean towards a service since the Angular team states "having two controllers that want access to the same data is a classic sign that you want a service." (One of the places they mention this is in their Angular Best Practices discussion: http://www.youtube.com/watch?v=ZhfUv0spHCY). And they also discuss services being the right place for shared state (with thin controllers that are the "glue between views and services").
So, something either like this:
myApp.service('headerStatus', function () {
var hideHeader = true;
this.hideHeader = function() {
return hideHeader;
};
this.setHeader = function(visibility) {
hideHeader = visibility;
};
});
Then there's a bunch of ways to tie into it, but here's a simple one:
myApp.controller('Header', function ($scope,headerStatus) {
$scope.hideHeader = headerStatus.hideHeader();
});
And a fiddle of this: http://jsfiddle.net/Yxbsg/1/
Or potentially you could use a value:
myApp.value('headerStatus',true);
myApp.controller('Header', function ($scope,headerStatus) {
$scope.hideHeader = headerStatus;
});
You could listen for the $locationChangeStart or $locationChangeSuccess events in your header controller and then hide it based on the change in the url.
http://docs.angularjs.org/api/ng.$location
From the AngularJS API Docs
$locationChangeStart
Broadcasted before a URL will change. This change can be prevented by calling preventDefault method of the event. See ng.$rootScope.Scope#$on for more details about event object. Upon successful change $locationChangeSuccess is fired.
Type:
broadcast
Target:
root scope
Parameters
Param Type Details
angularEvent Object Synthetic event object.
newUrl: string New URL
oldUrl: (optional) string URL that was before it was changed.
EDIT
angular.module('myApp',[]).config(['$routeProvider',function($routeProvider){
// application routes here
}).run(['$rootScope','$location',function($rootScope,$location){
$rootScope.$on('$locationChangeStart',function(evt,newPath,oldPath){
switch(newPath){
case '/some/path':
case '/some/other/path':
case '/some/more/paths':
$rootScope.$broadcast('header.hide');
break;
default:
$rootScope.$broadcast('header.show');
break;
}
});
}])
.controller('HeaderCtrlr',['$scope',function($scope){
$scope.hideFlag = false;
$scope.$on('header.hide',function(){
$scope.hideFlag = true;
});
$scope.$on('header.show',function(){
$scope.hideFlag = false;
});
}]);

AngularJs $scope.$apply not working as expected

I am using this controll: http://blueimp.github.io/jQuery-File-Upload/angularjs.html to do file uploading in angular, once the file is uploaded to my server I return a url and would like to display it to the client on the success callback. The plugin being used is just a normal jquery file upload but there is a angular directive wrapper provided which I'm using.
Here's how I define the callback:
$scope.options = {
url: '/api/Client/',
type: 'PUT',
done: function (event, data) {
var scope = angular.element(this).scope();
scope.test = "doesn't work";
scope.$apply(function () {
scope.test = "this doesn't work either";
});
}
};
The file uploads fine, and the done function is called however I am unable to update the view. I initially tried by just changing scope, then I realised I would require the $apply() function but that isn't working either.
I have also tried
$scope.options = {
url: '/api/Client/',
type: 'PUT',
done: function (event, data) {
$scope.test = "doesn't work";
$scope.$apply(function () {
$scope.test = "this doesn't work either";
});
}
};
and that also doesn't work. I am not sure why it isn't updating my view, and as the done call is just an ajax success event I don't see how this specific plugin could be causing any issues with $scope.$apply. I am using AngularJs 1.1.5, but I have also tried 1.0.7 and am getting the same issue.
Copied from my comment to make it clearer what the problem was:
I managed to figure out the problem. When using their example I had a duplicate of ng-controller(Their example was nested within my other controller) and even though both were using the same controller it seems like it would only update anything that was within nested controller scope. When removing the duplicate ng-controller attribute it all works fine.
I thought I would chime in too, as I've spent a whole day dealing with this issue.
Make sure that in your HTML, you apply the ng-controller to a div wrapper, not a ul element.

Resources