I'm familiar with returning 404 as the HTTP status code when you visit pages that do not exist. When you build static web sites, it works well and it makes sense, but in Angular it's different.
Let's say I visit /blah and it's not in my list of routes - then I redirect to /not-found. But let's say I visit a valid URL like /stuff/123 but the object Stuff123 does not exist in my backend. Should I then manually make a redirect in my
StuffController with $location.path('/not-found')?
This is my config now:
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/stuff/:id', {
templateUrl: '/views/stuff.html',
controller: 'StuffController'
})
.when('/not-found', {
templateUrl: '/views/not-found.html'
})
.otherwise({
redirectTo: '/not-found'
});
});
Also, does it make sense to return 404 in Angular? If so, then how do I do that?
Should I then manually make a redirect in my StuffController with $location.path('/not-found')?
I would not redirect to not-found route. If object Stuff123 does not exist it still doesn't mean route is not found. It is found, just no data for this specific parameters. You should just show proper corresponding message.
Or think of this way. Not found is 404 status code. It's error code. However, what you describe is not an error situation, it just the absence to the data for this id. Thus in classical application you should have been SUCCESS 200 with just empty response [] for data. So again it's not "not found".
I know this is kind of old, but I found it easily by looking for 404 and angular so....
I think what you wanted to do way back when was use the 'catch all' route. The catch all route is '**'. So put something like this in your app module...
},
{
path: 'admin/orders',
component: AdminOrdersComponent,
canActivate: [AuthGuard, AdminAuthGuard]
},
{ path: '**', component: CatchAllComponent }
This way in the template you can give your users useful information, no matter what stuff they put into the address bar, after making that mistake.
Related
I hava a list page:
$routeProvider.when('/', {
redirectTo:'/1', // so default will be page 1
}).when('/:page',{
controller:'listCtrl',
templateUrl:'xxxxx'
})
In the controller will use $route.current.params.page to do the logic.
The question is:redirectTo will cause a browser 301 which I don't want.(bad performance) And I also don't want everytime one click the main nav button, it would cause a 301.
How can I archive the same goal without use redirectTo?
It's something like: if I use the route same as '/:page'(write the same controller / templateUrl), how can I pass the page number(1) to the controller without route param?
EDIT:
Thanks to the accepted answer.
Changed the title to Flask related.
Found that the 301 was caused by Flask's defaults route:
#bp.route('/list', defaults={'page':1})
#bp.route('/list/<int:page>')
def lst(page):
when the request uri is /list/1, will be 301 to /list... don't know how to resolve yet, but this is another question.
redirectTo in $route doesn't actually do an HTTP redirect - it just redirects logically to the right ng-view. So, there isn't any performance hit.
But just for completeness-sake, you can pass a parameter in the resolve property:
.when('/:page?',{
controller:'listCtrl',
templateUrl:'xxxxx',
resolve: {
page: function($route) { return $route.current.params.page || 1; }
}
})
I develop an application with Angularjs. In order to access to the application, the user has to be identified thanks to an external system (not developed by myself).
When the user is correctly identified, this external system redirected to my application.
I have implemented a general remote service for retrieving data (thanks to Coldfusion and an Oracle database) about a user (thanks to the login). This service is equally used in other applications. It returns general user information: firstname, lastname,..., and the user role for the specific application.
I would like to authorize the access to the sections of my application when the user has the corresponding role.
Thus I have called once the function of this service before to implement the routes in order to avoid many calls to the functions in the controllers. It works and I retrieve the user role.
var app=angular.module('ContactsApp', ['ngRoute', 'RemoteService']);
// CALL the method from THE REMOTE SERVICE - used $http.get and retrieve JSON data
app.factory('RemoteServiceFunction', ['RemoteServiceFactory', function (RemoteServiceFactory) {
return RemoteServiceFactory.Auth_getUserFromLogin(userid)
}]);
app.config(function($routeProvider, $httpProvider){
// FUNCTION USING THE REMOTE SERVICE IN ORDER TO INJECT DATA IN THE ROUTES
var wait = ['RemoteServiceFunction', function(RemoteServiceFunction) {
return RemoteServiceFunction;
}];
$routeProvider.when('/all-contacts',
{
templateUrl: 'template/allContacts.html',
controller: 'ctrlContacts',
resolve: {
personInfo: wait
}
})
.when('/view-contacts/:contactId',
{
templateUrl: 'template/viewContact.html',
controller: 'ctrlViewContacts',
resolve: {
personInfo: wait
}
})
.when('/search-contacts',
{
templateUrl: 'template/fastSearch.html',
controller: 'ctrlContactSearch',
resolve: {
personInfo: wait
}
})
.when('/add-contacts',
{
templateUrl: 'template/manageContact.html',
controller: 'ctrlAddContacts',
resolve: {
personInfo: wait
}
})
.otherwise({redirectTo:'/all-contacts'});
});
//THE CONTROLERS WITH DEPENDANCIES INJECTED
app.controller('ctrlContacts', function ($scope, ContactService, personInfo){
// alert(personInfoRole.data["VALUES"][0]["ROLES"]); OK the role is retrieved
}
Now I would like to enable or not the access to the various sections ( for example the role "admin" is mandatory for example in order to add a contact: ctrlAddContacts) . I do not know if I have to add a condition or a function in the resolve part to give the access.
Could you please tell me how to do that? Or tell me if another solution is better for using roles with Angularjs.
Thanks
Your best bet is probably to "intercept" the route change, that is: listening to the $routeChangeStart event on your rootScope. There you can check against whatever model variable you store the user priviledge in, and stop the routeChange from happening or redirect to an authentication screen.
Keep in mind though that such client-side authentication in an angular (or any javascript) application is just a user interface thing, since the user's browser will have full access to all the resources, and can even rewrite the validating parts of the javascript code, bypassing any route restrictions you set up. Sensitive information shouldn't travel to the client in the first place without proper authentication.
edit: see a full code solution here:best way to limit access to 'logged in' users
Mates,
I'm developing a Backbone/Laravel application.
When I try to send a POST request to a certain resource, it returns me 301 status with no response, regardless that on the controller i'm printing a 'hello world', to check if it's getting to that point.
Here's some code...
public function store()
{
//
return 'hello world';
}
This is the routes.php
Route::group(array('before' => 'auth'), function()
{
Route::get('/', 'SitesController#index');
Route::resource('rooms', 'RoomsController');
});
So, when I make a POST request to
rooms
With some info to save, it gives me back 301 status with no response at all.
Any idea what am I doing wrong?
Thanks in advance!
Solved!
On backbone collection configuration, I had url parameter with '/' at the end.
rooms/
Deleted that slash and now it works fine.
Hope someone find's this helpful
(Adding this answer in case anyone has the same issue as me)
In my case I had a resource videos which gave the exact same problem. The reason was that I also had a directory on the server /videos/ (with files in it) which the server tried to point me to before it let Laravel direct it to the controller.
For some reason, posting to "image" in Laravel, even without a trailing "/" gives me a 301 Moved Permanently HTTP response.
I don't have an "image" folder in my public directory and still don't understand why the redirect occurs.
Changing the request to "foo" seems to work fine.
So if you found this page because you're trying to store an image restfully, try something like this:
Route::resource('api/image', 'ImageController');
What is the best practice of doing CRUD operations via REST with AngularJS?
Specially what is the Angular-Way here. By this I mean the way using the least code and the most default angular settings to achive this.
I know $resource and it's default operations. Where I'm not sure is how to implement/name the endpoints and which controllers to use.
For this example I would like to implement a simple user-management system which creates / updates /deletes / lists users. Since I'm implementing the Server-Endpoints by myself I'm completely free in doing it in the most angular friendly way.
What I like as answer is something like:
Server-Endpoints:
GET /service/users -> array of users
GET /service/user/new -> return an empty user with default values which has no id
POST /service/user/new -> store a new user and create an id. return the saved user.
POST /service/user/:ID -> save an existing user. Return the saved user
DELETE /service/user/:ID -> delete an existing user
Angular-Services:
.factory( 'User', [ '$resource', function( $resource ){
return $resource( '/service/user/:userId', { userId: '#id' } )
[...]
}])
Routing:
.when( '/users', {
templateUrl: BASE + 'partials/user-list.html',
controller: 'UserListCtrl' } )
.when( '/user/new', {
templateUrl: BASE + 'partials/user-edit.html',
controller: 'UserNewCtrl' } )
.when( '/user/:userId', {
templateUrl: BASE + 'partials/user-edit.html',
controller: 'UserEditCtrl' } )
...
Controllers:
UserListCtrl:
$scope.data = User.get(...)
UserNewCtrl:
$scope.user = User.get( { userId: "new" } )
...
Note that I'm not interessted in opinion what is the best (tm) way to do this but I'd like to know what is the Angular intended way (which I think should produce the least code because it can use the most default).
EDIT:
I'm looking for the whole picture. What I would love would be an answer like e.g.: "You can do this using online 3 Endpoints [...], 2 routes [...] and 2 controllers [...] if you do it this way using that defaults ..."
There is no Angular prescribed way for what you are asking. It's up to you to determine the implementation detail.
Typically I only use two controllers and templates per resource:
ListController
FormController
The Form controller is used for both Edit and Create operations. Use the resolve option in your route definitions to pass in either User.get() or User.new() and a flag indicating if this is an edit or create operation. This flag can then be used inside your FormController to decide which save method to call. Here's a simple example:
.when( '/users', {
templateUrl: BASE + 'partials/user-list.html',
controller: 'UserListCtrl' } )
.when( '/user/new', {
templateUrl: BASE + 'partials/user-form.html',
resolve: {
data: ['User', function(User) { return User.new(); }],
operation: 'create'
}
controller: 'UserFormCtrl' } )
.when( '/user/:userId', {
templateUrl: BASE + 'partials/user-form.html',
resolve: {
data: ['User', '$route', function(User, $route) { return User.get($route.current.params.userId); }],
operation: 'edit'
}
controller: 'UserFormCtrl' } )
And your form controller:
app.controller('UserFormCtrl', ['$scope', 'data', 'operation', function($scope, data, operation){
$scope.data = data;
$scope.save = function() {
if (operation === 'edit') {
// Do you edit save stuff
} else {
// Do you create save stuff
}
}
}]);
You can go a step further and create a base list and form controller to move stuff like error handling, server-side validation notifications etc. In fact for the majority of CRUD operations you can even move the save logic to this base controller.
My research into a similar quest has lead me to this project "angular-schema-form" https://github.com/Textalk/angular-schema-form.
For this approach...
You make a JSON-Schema that describes your data. Then augment it with another little JSON-struct that describes a "form" (ie. view specific info that does not belong in the data schema) and it makes a UI (form) for you.
One cool advantage is that the Schema is also useful in validating the data (client and server side), so that is a bonus.
You have to figure out what events should fire off GET/POST/... back to your API. but that would be your preference, eg. Hit the API for every key stroke OR the classic [Submit] button POST back style OR something in between with a timed Auto Save.
To keep this idea going, I think that it is possible to use StrongLoop to make a quick API, which (again) uses your data's schema (augmented with some storage details) to define the API.
no <3 uses of that schema, see [http://json-schema.org/] which is central to this approach.
(read: no less than three :)
You maybe mixing things up. CRUD operations at API level are done using $resource and these may or may not map to UI.
So using $resouce if you define resource as
var r = $resource('/users/:id',null, {'update': { method:'PUT' }});
r.query() //does GET on /users and gets all users
r.get({id:1}) // does GET on /users/1 and gets a specific user
r.save(userObject) // does a POST to /users to save the user
r.update({ id:1 }, userObject) // Not defined by default but does PUT to /users/1 with user object.
As you see the API is resource full but is in no way linked to any UI view.
For view you can use the convention you have defined, but nothing specific is provided by Angular.
I think what you are looking for can be found in http://www.synthjs.com/
Easily create new RESTful API resources by just creating folders and
naming functions a certain way.
Return data or promises from these
functions and they'll be rendered to the client as JSON.
Throw an
error, and it'll be logged. If running in dev mode, the error will
also be returned to the client.
Preload angular model data on page load (saving an extra
roundtrip).
Preload html view on page load (saving another extra
roundtrip!)
A simplified project structure
where front-end code (angular code, html, css, bower packages, etc)
is in the 'front' folder and back-end code (node code and node
packages) are in the 'back' folder.
A command-line tool for
installing third party packages, using npm + bower, that auto-updates
manifest files.
Auto compilation of assets on request for dev, and
pre-compilation for prod (including minification and ngmin).
Auto-restarts the server when changes are detected.
Support for
various back-end and front-end templates to help get a new project
going quickly.
By enabling HTML5 mode in AngularJS, the $location service will rewrite URLs to remove the hashbang from them. This is a great feature that will help me with my application, but there is a problem with its fallback to hashbang mode. My service requires authentication, and I am forced to use an external authentication mechanism from my application. If a user attempts to go to a URL for my app with a hashbang in it, it will first redirect them to the authentication page (won't ever touch my service unless successfully authenticated), and then redirect them back to my application. Being that the hash tag is only seen from the client side, it will drop off whatever parts of the routes come after by the time they hit my server. Once they are authenticated, they may re-enter the URL and it will work, but its that one initial time that will cause a disruption to the user experience.
My question is then, is there any way to go from $location.html5Mode(true) to the fallback of full page reloads for un-supportive browsers, skipping the hashbang method of routing entirely in AngularJS?
The best comparison of available implementations of what I'm aiming for would be something such as browsing around folders on github.com. If the browser supports rewriting the URL without initiating a page refresh, the page will asynchronously load the necessary parts. If the browser does not support it, when a user clicks on a folder, a full-page refresh occurs. Can this be achieved with AngularJS in lieu of using the hashbang mode?
DON'T overwrite the core functionality.
Use Modernizr, do feature detection, and then proceed accordingly.
check for history API support
if (Modernizr.history) {
// history management works!
} else {
// no history support :(
// fall back to a scripted solution like History.js
}
Try to wrap $location and $routeProvider configuration in browser's HTML5 History API checking, like this:
if (isBrowserSupportsHistoryAPI()) {
$location.html5Mode(true)
$routeProvider.when(...);
}
Also may be you need to create a wrapper to $location if you use it to change path.
(Sorry for terrible english)
Why not handle the un-authenticated redirect on the client side for this situation? I'd need to know a bit more about exactly how your app works to give you a more specific solution but essentially something like:
User goes to a route handled by AngularJS, server serves up the AngularJS main template and javascript
User is not authenticated, AngularJS detects this and redirects to the authentication page
You could have something in the module's run block for when the AngularJS application starts:
module('app',[])
.configure(...yadda...yadda...yadda...)
.run(['$location', 'authenticationService', function($location, auth) {
if (!auth.isAuthenticated()) {
$location.url(authenticationUrl)
}
});
I've subbed in a service which would find out if you were authenticated somehow, up to you how, could be checking a session cookie, could be hitting your API to ask. Really depends on how you want to continue to check authentication as the client application runs.
You can try and override the functionality of the $location service. The general idea would be to rewrite the URL according to whether someone is already authenticated or not, or just use a single approach (without hashbangs) for all URLs, regardless to whether html5mode is on or not.
I'm not sure that I fully understand the use-case so I can't write the exact code that you need. Here is a sample implementation of how to overrides/implements and registers the $location service, just making sure that hashbang is always eliminated:
app.service('$location', [function() {
var DEFAULT_PORTS = {
ftp: 21,
http: 80,
https: 443
};
angular.extend(this, {
absUrl: function() {
return location.href;
},
hash: function(hash) {
return location.hash.substr(1);
},
host: function() {
return location.host;
},
path: function(path) {
if (!path) {
return location.pathname;
}
location.pathname = path;
return this;
},
port: function() {
return location.port ? Number(location.port) : DEFAULT_PORTS[this.protocol()] || null;
},
protocol: function() {
return location.protocol.substr(0, location.protocol.length - 1);
},
replace: function() {
return this;
},
search: function(search, paramValue) {
if (search || paramValue) {
return this;
}
var query = {};
location.search.substr(1).split("&").forEach(function(pair) {
pair = pair.split("="); query[pair[0]] = decodeURIComponent(pair[1]);
});
return query;
},
url: function(url, replace) {
return this.path();
}
});
}]);