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.
Related
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 ;)
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.
I have a single page AngularJS application which is backed by Ruby on Rails server. The routing definition is only defined in RESTful Rails route.rb config, such as:
http://localhost/pages/:page_id/comments/:comment_id
The client is AngularJS with no routeProvider configuration. So it is just a simple AngularJS controller with view.
When I am in http://localhost/pages/:page_id/comments/:comment_id.html, I want to extract the value of :page_id and :comment_id. What is the best way to do it in AngularJS?
Note: I don't mind if really needed to use routeProvider. But I would only have one templateUrl in that case.
The $routeParams service allows you to retrieve the current set of route parameters.
Requires the ngRoute module to be installed.
The route parameters are a combination of $location's search() and path(). The path parameters are extracted when the $route path is matched.
Note that the $routeParams are only updated after a route change completes successfully. This means that you cannot rely on $routeParams being correct in route resolve functions. Instead you can use $route.current.params to access the new route's parameters.
Example:
// Given:
// URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
// Route: /Chapter/:chapterId/Section/:sectionId
//
// Then
$routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'}
for more infor
EDIT :
If you are using angular-ui-router, you can inject $stateParams
check out this https://github.com/angular-ui/ui-router/wiki/URL-Routing
While routing is indeed a good solution for application-level URL parsing, you may want to use the more low-level $location service, as injected in your own service or controller:
var paramValue = $location.search().myParam;
This simple syntax will work for http://example.com/path?myParam=someValue. However, only if you configured the $locationProvider in the html5 mode before:
$locationProvider.html5Mode(true);
Otherwise have a look at the http://example.com/#!/path?myParam=someValue "Hashbang" syntax which is a bit more complicated, but have the benefit of working on old browsers (non-html5 compatible) as well. [via : Javarome]
Goodday Folks,
I have implemented a get or read operation using an AngularJS application to invoke a Isis service via an $http call. And displaying the collection on the screen using Angular ng-repeat. My next task is to do a create and update on the same entity using AngularJS. I am aware i have to send some parameters in the endpoint URL. Please, I need both Isis guidance and also importantly AngularJS hints or references or code.
I think i should get some sort of acknowledgment to confirm the create or update is succesful.
Below, is an extract from my code for the getList operation, just for starters.
Your logic might be totally different from this.
Thanks a lot.
sampleApp.controller('CrateUpdateController', function($scope, $http) {
$http({ method:'GET',
url: 'http://localhost:8080/xxx-webapp-1.0-SNAPSHOT/restful/services',
headers: {'Accept': 'application/json'}
}).
success(
function (data) {
//code to process outcome and acknowledgement etc
}
);
});
You might be best doing some Angular tutorials to learn the principles of that library. Then, (as I've mentioned before) take a look at using the Spiro library as the main interface to the RO server (Isis or RO.Net).
You should not mix so much different topics. A good practice is to distinguish the server side interface exposed by your HTTP server (Apache Isis) from the client side code accessing it.
This question should be break down to the following:
How to expose create and delete verbs for an existing resource in Isis
How to use create and delete verbs with Angular
The two topics are very different and it does not make so much sense to ask them together. The main point about having Isis exposing an HTTP interface is exactly to abstract from the client side logic.
As a side note, when accessing restful resources with Angular, try to use $resource instead of $http
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).