Run controllers only after initialization is complete in AngularJS - javascript

I have some global data that needs to be loaded before any controller is executed in my AngularJS application (i.e. resolve dependencies globally in AngularJS).
For example, I have a UserService with the getCurrentUser() method which does a request to the backend server in order to get data about the currently authenticated user. And I have a controller that needs this data in order to launch yet another request (for example to load user's balance).
How can I achieve that?

Update
Please consider using method specified in the «Asynchronously Bootstrapping AngularJS Applications with Server-Side Data» article if possible.
You can use the angular-deferred-bootstrap module to achieve that now!
I'm not sure about validity of this answer anymore, you can still use the ideas, but be sure to properly test it with your actual code. I will try to keep this answer up to date with never technologies.
Old answer
There are several approaches to the problem of asynchronous application initialization.
When it comes to data that must be resolved before a single controller is called - you can easily use resolve option of ngRoute's $routeProvider. However, when you need some global data to be loaded before ANY controller is called - you have to improvise.
I've tried to collect all possible solutions in this answer. I'm providing them in the order of preference.
1. Using ui-router
When using ui-router instead of native ngRoute you can create an abstract root state and resolve all data in it, before sub-states are activated.
I would recommend to use this approach. ui-router provides a lot of additional features including ability to resolve dependencies hierarchically and is well accepted by the developer community.
Example
module.config(function($urlRouterProvider, stateHelperProvider) {
$urlRouterProvider.otherwise('/404');
stateHelperProvider.setNestedState({
name: 'root',
template: '<ui-view/>',
abstract: true,
resolve: {
user: function(UserService) {
// getCurrentUser() returns promise that will be resolved
// by ui-router before nested states are activated.
return UserService.getCurrentUser();
}
},
children: [{
name: 'index',
url: '/',
templateUrl: '/partials/index'
}, {
name: 'not-found',
url: '/404',
templateUrl: '/partials/404'
}, {
name: 'balance',
url: '/balance',
templateUrl: '/partials/balance',
resolve: {
balance: function(UserService, user) {
// Using data resolved in parent state.
return UserService.getBalanceByAccountId(user.accountId);
}
}
}]
});
});
The stateHelper will help greatly to reduce the code when using abstract root scope approach.
Root scope is defined as abstract so can not be activated directly and it has no URL.
template: '<ui-view/>' is required for nested views to be properly rendered.
2. Making promises in root controller
You can make promises and add them to the $rootScope inside of your root controller, i.e. run() function.
I've created a Plunk to demonstrate the idea:
http://plnkr.co/edit/gpguG5Y2S4KOz1KOKzXe?p=preview
This is a perfectly working solution, however, it bloats the code and makes it harder to use and understand (callback hell). I would recommend it only if the first approach is not working for you.
3. Passing data with the application page
You can include all initialization data directly to the HTML page generated on the server and access it from your application.
Consider this example:
<html>
<body>
<script src="application.js"></script>
<script type="text/javascript">
application.init({
// Pass your data here.
userData: { ... }
});
</script>
</body>
</html>
And you can bootstrap AngularJS application manually in the init() method of your custom application object.
I don't really like this approach, as I do believe that frontend and backend of Web application should be highly separated. Ideally, your frontend should be a static website (e.g. bunch of HTML, CSS and JS that can be delivered via CDN) and your backend should be a strictly an API server without a presentation layer (i.e. it should know nothing about HTML, CSS and such). However, it's a working solution if you can live with tight integration between application components.

Related

Calling same service multiple times, sign of bad design?

I'm in the process of building out a fairly large Angular app and I've stuck to the design of building 'thin' controllers. My controllers don't try to do too much, they are each focused on one piece of functionality within my app.
There, however, is certain data that is 'shared' between controllers. I aim to avoid using $rootScope and instead rely on services to share data and 'state'.
When looking in the 'Network' tab of Chrome Dev Tools I notice certain services being called half a dozen times. So my question is, is this bad design? Are multiple calls to the same service within an Angular app not the 'Angular way' to do things? Note: these service calls take ~ 20ms each, so clearly not much of a performance hit...so far.
I'd suggest reducing the number of unnecessary HTTP requests that you're making for two reasons:
In production environments, these HTTP requests may take more time to complete when factors such as network latency or server load are taken into account; and
will delay the loading of any other assets such as images (assuming that they're coming from the same domain).
An approach that I've used when dealing with the scenario that you've described is to cache the response/data from the API in the service. So, on subsequent calls to the service, the data can be pulled from the cache rather than the API.
See a brief example below:
angular.module('app', ['ngResource'])
.factory('Post', ['$resource', function($resource) {
var posts = [];
var service = {
all: all
}
return service;
function all() {
// if cached posts exist, return those. Otherwise, make call to external API.
if (posts.length > 0) {
return posts.$promise;
} else {
posts = $resource('http://localhost/api/v1/posts.json').query();
return posts.$promise;
}
}
}]);
Note: you'll also have to consider resetting your cache however this would be dependent on your application logic.
Hope this helps!
L
In this case you should look to use $cacheFactory to reduce service calls.
Are you talking about REST services? Are you making $http calls in order to share data and state between controllers?
Why not use service/factory in Angular?
What you need is
1. DataCache service/factory - which will store your response from server
2. A directive - to call these services. include it in your different views
3. Now inside your service which is responsible for making http call first check if data is available in cache if yes return promise of stored data (you can use $q.when) if not make service call.
I have mentioned point 2 since I am assuming inside your various controller you must be doing something like AbcFactory.getItem().then() to avoid duplication of this code as you never know when the requirement will change since change is the only constant during development ;)

Get templateUrl from ui-router

Maybe the title isn't really clear.
I'm working on two nested Angular apps. Both have their own router built with ui-router. If I call a state that is unknown by the main router, I'd like it to search for this state in the sub-app router to get the templateUrl and url related to this state.
I thought about creating a service with a parser. It could parse the file to find the data I want. This solution is probably not the best option I have, that's why I wanted to know if there is a specific function/method in ui-router to achieve it. From what I read on ui-router doc, it seems not :/
Feel free to ask for more details or to suggest another solution which can match with my goal :)
You can achieve this by using dynamic parameters in your $stateProvider configuration defined on your sub-module. So you have some anchored routes on the main module, and if there is a match, ui-router will simply fetch the associated template. If there is no matched absolute route/url, then ui-router falls back on your parameterised route, like so:
// anchored
$stateProvider
.state('mainAppState', {
url: '/anchored',
controller: 'myCtrl'
})
// sub module
.state('subAppState', {
url: /:parameter
resolve: { // use the resolve and $stateParams to get some parameter with which you can make a request to your API }
templateProvider: { // inject a service and use it to call your backend }
You can use a the parameter, which gets passed to $stateParams, which gets passed to your resolve block functions, which can retrieve some data or process the data from $stateParams, that can then be used by templateProvider to provide a meaningful fallback URL. That's because templateProvider receives all resolves. It will always wait for all promises in the resolve block to resolve one way or another before executing.
That way you can catch any unmatched URLs from the main app in your sub app. Hope this helps.
See my issue here: ui-router: async data in templateUrl function
Oh and one more thing - if you are going to mangle your JS in your build process, make sure you define your templateProvider function with .$inject = [ ... ]. If not, you'll get an unknown provider error. ngAnnotate will NOT catch the dependencies in your templateProvider function. Alternatively you can just use /* #ngInject */, which worked for me.

Syncronizing Session data with other angular directives and controllers

In my AngularJS application, I have a Session service object that contains stuff like the current user, their preferences, the current company they belong to, and the current theme that they are using. Many places in my application refer to the Session service when they need to get at this data.
Because these variables are in a service, I cannot use scope watches to detect changes. So instead, I've decided to use the observer pattern. Various places in the application, such as other services, directives, controllers, etc. will register themselves with the Session service and provide a callback to be executed whenever the Session changes.
For example, if the user changes their theme, the <style> element in index.html that uses a custom directive will be notified, and it will recreate all of the overriding css rules for the new colors.
For another example, whenever the user's avatar is updated, the main menu bar controller will be notified to refresh and redraw the avatar. Stuff like this.
Obviously the data in Session has to be refreshed at least once before the various controllers, directives, etc. use it. The natural place to ask the Session service to get its session data was in a run block for the application-level module. This works pretty well, but I don't think it's the best place either.
One problem I have noticed is that when Firebug is open, the asynchronous nature of things loading causes ordering issues. For example, the directive that runs on the <style> element will run AFTER the Session service has refreshed in the application's run block... which means the theme will not get updated after pressing F5 because the callback is registered after the initialization of the data occured. I would have to call a manual refresh here to keep it in sync, but if I did that, it may execute twice in the times where the order is different! This is a big problem. I don't think this issue is just related to Firebug... it could happen under any circumstance, but Firebug seems to cause it somewhat consistently, and this is bad.
To recap... This asynchronous ordering is good:
Theme Directive registers callback to Session
Menu Bar application controller registers callback to Session
Session.refresh() is called in .run block.
This asynchronous ordering is bad:
Menu Bar application controller registers callback to Session
Session.refresh() is called in .run block.
Theme Directive registers callback to Session, but callback does not get executed since Session.refresh() was already executed.
So rather than use the observer pattern, or refresh the Session state via a run block, what the best way to design the services, etc. so that the session data will ALWAYS get refreshed after (or maybe before) the various other parts of the application require it? Is there some kind of event I can hook into that gets executed before directives and controllers are executed instead of the run block?
If my approach is generally sound, what can I add to it to really make it work the way it should?
Thanks!
In angular.js you have 2 way of using global variables:
use a $rootScope
use a service
Using $rootScope is very easy as you can simply inject it into any controller and change values in this scope. All global variables have problems!
Services is a singletons(What you need)!
I think in your case you can use
$rootScope
And
$scope.$watch
Great answer
Is there a reason you can't access the variables directly like this:
app.factory('SessionService', function() {
var items = {
"avatar": "some url"
};
return items;
});
var MainController = [$scope, 'SessionService', function($scope, SessionService){
$scope.session = SessionService;
$scope.modifyAvatar = function(url){
$scope.session.avatar = "some new url";
};
}];
var HeaderController = [$scope, 'SessionService', function($scope, SessionService){
$scope.session = SessionService;
// You probably wouldn't do this, you would just bind
// to {{session.avatar}} in your template
$scope.getAvatar = function(){
return $scope.session.avatar;
};
}];

Don't reload page when back button is pressed - AngularJS

I'm developing an AJAX-heavy application with AngularJS and need requests to not be re-made when the user clicks the back button on their browser. For example a set of data takes 2-3 seconds to load and then if the user navigates to another page and then clicks back the data has to be reloaded. Is there a way to prevent this - or alternatively a different way to design my app such that data persists through a session?
If you're using ngResource for loading data from api then set the cache to true in actions as in described in documentation
cache – {boolean|Cache} – If true, a default $http cache will be used to cache the GET request, otherwise if a cache instance built with $cacheFactory, this cache will be used for caching.
https://docs.angularjs.org/api/ngResource/service/$resource
Example:
this.service=$resource('www.example.com/api/v/stats',
{
callback: "JSON_CALLBACK"
}, {'foo': {'method': 'GET', 'cache': true}}
);
Loading data through this.service.foo() will cache the request and on back button, it will use the cached data instead of reloading it.
I strongly suggest that you study more on the usage of routing with AngularJS
A nice and quick way to do so would be watching some of John's tutorials, starting with this one: https://egghead.io/lessons/angularjs-ng-view
Finally, what you are trying to accomplish is showed in this one: https://egghead.io/lessons/angularjs-route-life-cycle
Hope this helps.
In an app I'm currently developing, I use the $routeProvider heavily for each of the modules.
I do something like:
$routeProvider.when(/someModule/page1Condition1, {templateUrl: 'page1.tpl.html', controller: 'page1Ctrl'});
$routeProvider.when(/someModule/page1Condition2, {templateUrl: 'page1.tpl.html', controller: 'page1Ctrl'});
Then, in the controller for page1 I do something like:
if($location.path()==='/someModule/page1Condition2){
// something that should be done only when Condition2 is true,
// for example, loading data using ajax
$http.get('somePath')
.success(function(data){
$scope.myData = angular.fromJson(data);
});
}
In this way, I have just one controller but conditionally fetch data using the url path as an information source. Think of it like something that wants to be as close as REST as possible, but is actually not REST.
It's important to mention that I also have several weird requirements imposed by my client, thus this is quite probably not the best way to solve this problem (it could even be an antipattern, although I like to think that is just a temporary hack).

What's the AngularJS "way" of handling a CRUD resource

I am interested in moving a lot of my client's "logic" away from Rails routing to AngularJS. I have slight confusion in one topic and that is linking. Now, I do understand there's more than one way to handle this, but what is the common practice in the AngularJS community for handling URLs on handling CRUD for resources. Imagine in the case of an athlete we have a URL such as the following to list all athletes:
http://example.com/athletes
To view an individual athlete:
http://example.com/athletes/1
To edit an individual athlete:
http://example.com/athletes/1/edit
To create a new athlete:
http://example.com/athletes/new
In AngularJS, is it common practice to reroute to similar URLs to create/edit/update? Would you just have one URL handle all of the CRUD type actions in one interface and never change the URL? If you were to change the URL, does that get handled via ng-click and in the click event would you use the $location object to change URLs? I'd love to be able to read up on common practices such as these, but having a difficult time in finding more recent literature on it in an AngularJS context.
** NOTE **
I totally get that you can still use RESTful routes to the backend in order to interact with server-side resources. My question is, what is the style that is recommended to use when updating URLs on the client-side. Are you using AngularJS to do that for each of the CRUD operations?
I would definitely recommend separate URLs for each operation (to enable direct linking). The ones you suggest look fine.
In AngularJS you can use the $route service in combination with the ngView directive to load the appropriate template for each operation and handle the browser location and history mechanics for you.
Step 7 of the AngularJS tutorial gives an example of using Views, Routing and Templates the way I describe here. The following is a simplified version for your example:
Define the routes
In your main application script (e.g. app.js):
angular.module('AthletesApp', []).
config(['$routeProvider', function($routeProvider, $locationProvider) {
// Configure routes
$routeProvider.
when('/athletes', {templateUrl: 'partials/athletes-list.html', controller: AthleteListCtrl}).
when('/athletes/:athleteId', {templateUrl: 'partials/athlete-detail.html', controller: AthleteDetailCtrl}).
when('/athletes/:athleteId/edit', {templateUrl: 'partials/athlete-edit.html', controller: AthleteEditCtrl}).
when('/athletes/:athleteId/new', {templateUrl: 'partials/athlete-new.html', controller: AthleteNewCtrl}).
otherwise({redirectTo: '/athletes'});
// Enable 'HTML5 History API' mode for URLs.
// Note this requires URL Rewriting on the server-side. Leave this
// out to just use hash URLs `/#/athletes/1/edit`
$locationProvider.html5Mode(true);
}]);
We also enable 'HTML Mode' for URLs, see note below.
2. Add an ngView directive to your HTML
In your main index.html you specify where the selected partial template will go in the overall layout:
<!doctype html>
<html ng-app="AthletesApp">
...
<!-- Somewhere within the <body> tag: -->
<div ng-view></div>
...
</html>
3. Create templates and controllers
Then you create the partial view templates and matching controllers for each of the operations. E.g. for the athlete detail view:
partials/athelete-detail.html:
<div>
... Athete detail view here
</div>
athleteDetailCtrl.js:
angular.module('AthletesApp').controller('AtheleteDetailCtrl',
function($scope, $routeParams) {
$scope.athleteId = $routeParams.athleteId;
// Load the athlete (e.g. using $resource) and add it
// to the scope.
}
You get access to the route parameter (defined using :athleteId in the route config) via the $routeParams service.
4. Add links
The final step is to actually have links and buttons in your HTML to get to the different views. Just use standard HTML and specify the URL such as:
Edit
Note: Standard vs Hash URLs
In older browsers that don't support the HTML5 History API your URLs would look more like http://example.com/#/athletes and http://example.com/#/athletes/1.
The $location service (used automatically by $route) can handle this for you, so you get nice clean URLs in modern browsers and fallback to hash URLs in older browsers. You still specify your links as above and $location will handle rewriting them for older clients. The only additional requirement is that you configure URL Rewriting on the server side so that all URLs are rewritten to your app's main index.html. See the AngularJS $location Guide for more details.
The angular way is the restful way:
GET all http://example.com/athletes
GET one http://example.com/athletes/1
POST new http://example.com/athletes
PUT edit http://example.com/athletes/1
DELETE remove http://example.com/athletes/1
Note that $resource also expects a few other things, like resource URLs not ending with a slash, PUT requests returning the updated resource, etc.
If your API doesn't meet these criteria, or you simply need more flexibility, you can build your own $resource-like CRUD service based on the lower-level $http service. One way of doing the latter is explained here
Option 1: $http service
AngularJS provides the $http service that does exactly what you want: Sending AJAX requests to web services and receiving data from them, using JSON (which is perfectly for talking to REST services).
To give an example (taken from the AngularJS documentation and slightly adapted):
$http({ method: 'GET', url: '/foo' }).
success(function (data, status, headers, config) {
// ...
}).
error(function (data, status, headers, config) {
// ...
});
Option 2: $resource service
Please note that there is also another service in AngularJS, the $resource service which provides access to REST services in a more high-level fashion (example again taken from AngularJS documentation):
var Users = $resource('/user/:userId', { userId: '#id' });
var user = Users.get({ userId: 123 }, function () {
user.abc = true;
user.$save();
});
Option 3: Restangular
Moreover, there are also third-party solutions, such as Restangular. See its documentation on how to use it. Basically, it's way more declarative and abstracts more of the details away from you.
In AngularJS you can definitely use RESTful server side data sources, there is build in service called $resource.
Alternatively you can also use restangular which has additional features over $resource.
If you want to have full control you can always use $http service which is low level angular component for interacting with http.
Simply implement something that is RESTful, that is the angularJS way. If you have no idea what RESTful is or, know a little and want to know a lot more, then I would recommend that you read this article.
Basically, REST is what is understood to be, an intuitive implementation of WEB URIs, it also makes use of all HTTP verbs, their correct use actually. REST is an approach, and architecture to building web apps.

Categories

Resources