Use HTTPD's mod_rewrite and AngularJS RouteProvider with Parameters - angularjs

I am trying to use HTTPD's mod_rewrite and AngularJS's routeProvider to allow users to access a webpage with a clean URL that will change its content based on some parameters in the URL.
For example, let's say there are profile pages for JohnSmith and JaneDoe. I want them to be able to access their pages via URL, like so:
http://my.example.com/JohnSmith
I want the AngularJS code to be able to grab the name "JohnSmith" from the URL, and use that for the REST call thusly:
$http.get('http://my.example.com/profile/name/' + $routeParams.profile)
So far, I have managed to get AngularJS to grab the correct profile IF the URL is formatted like one of the following:
http://my.example.com/#/JohnSmith
http://my.example.com/index.html#/JohnSmith
Here is the relevant code thus far:
index.html:
<html ng-app="profile">
<head>
<title>Profile</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js">
</script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.js"></script>
<link rel='stylesheet' href='style/style-main.css' type='text/css' />
<script src="js/app.js"></script>
</head>
<body>
<ng-view></ng-view>
</body>
</html>
app.js:
(function() {
var app = angular.module('profile', ['ngRoute']);
app.config(function($routeProvider) {
$routeProvider.when("/:profile",
{
templateUrl: "profile.html",
controller:"ProfileController",
controllerAs: "profile"
});
});
app.controller("ProfileController", [ '$http', '$routeParams', function($http, $routeParams) {
var SELF = this;
SELF.profile = $routeParams.profile;
console.log(SELF.profile);
}) ();
Using a clean URL like this:
http://my.example.com/JohnSmith
...will return a 404.
So I'm trying to use Apache's mod_rewrite to change the URL into a form that AngularJS can understand and extract the 'JohnSmith' parameter from.
I've got the following RewriteRule in place on my Apache node:
RewriteRule "(^[^\/#\.]+$)" "#/$1"
I've checked it out, and I know that this RewriteRule should trigger on any URL that doesn't contain any extra slashes, pound signs, or periods. Therefore, it should rewrite a URL like this:
http://my.example.com/JohnSmith
to this:
http://my.example.com/#/JohnSmith
That's a form that I know AngularJS can serve content for correctly, so after Apache rewrites the URL into that form, it should work, right?
Well, with that RewriteRule in place, trying to access the clean URL no longer results in a 404. In fact, if I right-click and select 'View Source', I can see that my browser has definitely attempted to load the HTML page. It looks like the routeProvider is no longer picking up the parameter from the URL, however.
How can I resolve this issue so the HTTPD RewriteRule and AngularJS routeProvider work together to provide the correct content using the clean URL?

Related

Angularjs base tag and $locationProvider prevents templates to be loaded

I want to remove the # on angularjs url. So I followed this blog.
This is my .config() :
.config(['$stateProvider', '$locationProvider' function($stateProvider, $locationProvider,) {
$stateProvider.state('main', {
url: '/',
templateUrl: 'path/to/main.html',
controller:'mainCtrl'
})
$locationProvider.html5Mode(true);
}])
And this is my <header> :
<head>
<scripts>....
<base href="/"> // I ADDED THIS
</head>
But when I access the / link. Like this http://localhost/my-app/ it only shows a blank page. There are no error/s on the console so Im assuming that the problem is on the loading of the template. Im using ui.router. Someone encountered this problem? Thanks.
1st
When you use <base href="/"> yu set ALL relative URLs appends to that path.
So IF your app is aviable at /my-app/ use <base href="/my-app/">
Also you can try relative base href <base href="./">, but i don't recomend it.
Now your app is in http://localhost/my-app/ but base is http://localhost/
2nd
When you use html5 routing, you may ensure that webserver does not repsonse other page, but your index.html
3rd, after that check your JS, maybe cant load template because of base url...

Without giving # in url I'll get a 404 error [duplicate]

This question already has answers here:
Removing the fragment identifier from AngularJS urls (# symbol)
(14 answers)
Closed 7 years ago.
Is it possible to not to give # symbol from url to display record by giving id from url directly?
I am developing a website in which I have only one view page as Login.html.
I need to access it by giving id of user from url as abc.in/1 here 1 is id of user. I set Login.html as a default page and when I try to access it by giving abc.in/1 ,i.e. id in url it can not display the record. but when I give abc.in#/1 it displays the record properly. I dont want to access it by giving # in url.
My code is as follow-
<html ng-app="myApp" style="height: 500px; overflow: auto;">
<head>
<meta charset="utf-8">
<base href="/">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title style="color: white;">c60</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<script src="../app/js/controllers/LoginController.js"></script>
</head>
<body>
---record details
</body>
</html>
and in app.js-
angular.module('myApp', [
'ngRoute', 'ngResource', 'ngCookies',
'myApp.filters',
'myApp.services',
'myApp.directives',
'myApp.controllers',
'ui.bootstrap', 'ngAnimate', 'ngDragDrop' //'ngSanitize',
]).
config(['$routeProvider','$locationProvider', function ($routeProvider,$locationProvider) {
$routeProvider.when('/:id',
{
templateUrl: '/Login.html',
controller: 'LoginController'
});
$routeProvider.otherwise({ redirectTo: '' });
//check browser support
if (window.history && window.history.pushState) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
}
}])
How to do that? I just dont want to give # in url to display a record of perticular id provided in url.I need a clean url.How to do that?
I tried to remove the # from my ulrs using the tip from the following question Removing the # symbol from angular.js urls .Now, the issue is that my url is not working if I try a direct access to them. from the given example in the related question if put the url below directly in the browser http://localhost/phones ; in my case it is abc.in/1
I'll get a 404 error. Any idea how to solve this?
It is possible to suppress the # in the url of an angular application. To do so, you need to configure the $locationProvider which you already have injected in your app config. Add the following after the $routeProvider configuration
$locationProvider.html5Mode(true);
Finally add the following to the head section of your html file
<base href="/">
A couple of things seem to be wrong here. First even when you are having only one view in your app, it is nice to have something like an index.html which will host your ng-view directive. then when you navigate to the one view of your app, that view will be loaded where you have the ng-view directive. Secondly, from your app.js, you have something as shown below
$routeProvider.when('/:id',
{
templateUrl: '/Login.html',
controller: 'LoginController'
});
$routeProvider.otherwise({ redirectTo: '' });
So this clearly says that when you enter '/' or '/1' for example in the address bar of your browser your application will fetch login.html as stated in the templateUrl. However, in your test you are entering abc.in/1 (which I do not know where it comes from)in the url address bar of your browser. when this url is checked in the route provider's routes, no match will be found. Hence you you get a 404 error. To make the matter worse your $routeProvider.otherwise is redirecting to empty string. The right thing to do is state the various routes with their respective templateUrl for your application and set otherwsise to redirect to the root of your application which in your case is still login.html or index.html if you add that now as you have only one view. Hope this helps.

Cannot get current route. $location.path() returns empty.

I am currently at /addDoc, and I want current route I am at. Here is my controller:
app.controller('DocRegistrationController',[ '$http', '$scope', '$upload', '$location', function($http, $scope, $upload, $location){
$scope.validation=function(){
alert($location.path());
}
}
However it returns empty. I dont want to hardcode all the routes. What am I doing wrong?
$location service respond for parsing url in browser address bar and make the URL available to your APP.
Because you're using regular URL path and search segments, you have to set $locationProvider html5Mode to true.
$locationProvider will use hashbang as default mode.
If you don't set html5Mode to true, you will get empty string when you try to fetch url path.
Then use $location service to fetch the url path after set html5Mode.
And write your own rule to process the path.
Assume that your Full URL looks like: http://example.com/person/show/321
Main.js
angular.module("MyAPP",[],function($locationProvider){
$locationProvider.html5Mode(true);
});
function MainController($location){
var pId = $location.path().split("/")[3]||"Unknown"; //path will be /person/show/321/, and array looks like: ["","person","show","321",""]
console.log(pId);
}
index.html
<!DOCTYPE html>
<html lang="en" ng-app="MyAPP">
<head>
<meta charset="utf-8">
<title>Angular test</title>
</head>
<body ng-controller="MainController">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
Hope this is helpful for you.
Use $location.absUrl()....Full URL Looks like : http://localhost:6292/Example/URLName
Try also this, which is native code in JS. It worked for me. But if you need to parse long url with many nested pages, you have to modify the code a bit.
var path = "/" + window.location.pathname.split('/')[1];
console.log(path
);

Routing single page application using angular js

I am building a web Single Page Application using AngularJS. I need that clicking on link change URI in client browser without http request.
http://example.com/ ---> it shows my single page application and clicking on a specific link I need the URL is http://example.com/about but without send http request and show hidden div.
I don't know what you precisely want to do but if you only want do one http request you can perhaps use angular ui router with something like
$stateProvider
.state('main', {
url: "/",
templateUrl: "main.html"
})
.state('about', {
url: "/about",
templateUrl: "main.html",
controller: function($scope) {
$scope.showDiv = "true";
}
})
That way you can switch state and because everything you need is already loaded, nothing gets loaded anymore. Or perhaps you can also use parameters.
But why is it so bad to have one additional request? That would be something interesting to know! :)
EDIT: The easy approach with $location
(https://docs.angularjs.org/guide/$location)
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example</title>
<base href="/">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="html5-mode">
<div ng-controller="LocationController">
<button ng-click="changeUrl()">Change url</button>
</div>
</body>
</html>
app.js:
angular.module('html5-mode', [])
.controller("LocationController", function ($scope, $location) {
$scope.$location = {};
$scope.changeUrl = function () {
// https://docs.angularjs.org/guide/$location
console.log("The current path: " + $location.path());
console.log("Changing url...");
$location.path('/newValue')
};
})
.config(function ($locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
})
Be sure to set the basePath correct.
Take a look at html2js. This is a grunt task to convert your html templates into a pre-cached js file.
Ideally you would run this as part of your build process. As well you can run a watch task to compile your HTML templates into the pre-cache whenever you save a template -- this is nice for development.
If you are already using gulp, there is a package for you. There are many alternatives to html2js that do essentially the same thing. So if it doesn't suit your needs, try another.
So with this in place, when you navigate to another page -- the HTML template will just be pulled out of angular's cache, and not grabbed from the server.

$location / switching between html5 and hashbang mode / link rewriting

I was under the impression that Angular would rewrite URLs that appear in href attributes of anchor tags within tempaltes, such that they would work whether in html5 mode or hashbang mode. The documentation for the location service seems to say that HTML Link Rewriting takes care of the hashbang situation. I would thus expect that when not in HTML5 mode, hashes would be inserted, and in HTML5 mode, they would not.
However, it seems that no rewriting is taking place. The following example does not allow me to just change the mode. All links in the application would need to be rewritten by hand (or derived from a variable at runtime. Am I required to manually rewrite all URLs depending on the mode?
I don't see any client-side url rewriting going on in Angular 1.0.6, 1.1.4 or 1.1.3. It seems that all href values need to be prepended with #/ for hashbang mode and / for html5 mode.
Is there some configuration necessary to cause rewriting? Am I misreading the docs? Doing something else silly?
Here's a small example:
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.3/angular.js"></script>
</head>
<body>
<div ng-view></div>
<script>
angular.module('sample', [])
.config(
['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
//commenting out this line (switching to hashbang mode) breaks the app
//-- unless # is added to the templates
$locationProvider.html5Mode(true);
$routeProvider.when('/', {
template: 'this is home. go to <a href="/about"/>about</a>'
});
$routeProvider.when('/about', {
template: 'this is about. go to <a href="/"/>home</a'
});
}
])
.run();
</script>
</body>
Addendum: in re-reading my question, I see that I used the term "rewriting" without an abundance of clarity as to who and when I wanted to do the rewriting. The question is about how to get Angular to rewrite the URLs when it renders paths and how to get it to interpret paths in the JS code uniformly across the two modes. It is not about how to cause a web server to do HTML5-compatible rewriting of requests.
The documentation is not very clear about AngularJS routing. It talks about Hashbang and HTML5 mode. In fact, AngularJS routing operates in three modes:
Hashbang Mode
HTML5 Mode
Hashbang in HTML5 Mode
For each mode there is a a respective LocationUrl class (LocationHashbangUrl, LocationUrl and LocationHashbangInHTML5Url).
In order to simulate URL rewriting you must actually set html5mode to true and decorate the $sniffer class as follows:
$provide.decorator('$sniffer', function($delegate) {
$delegate.history = false;
return $delegate;
});
I will now explain this in more detail:
Hashbang Mode
Configuration:
$routeProvider
.when('/path', {
templateUrl: 'path.html',
});
$locationProvider
.html5Mode(false)
.hashPrefix('!');
This is the case when you need to use URLs with hashes in your HTML files such as in
link
In the Browser you must use the following Link: http://www.example.com/base/index.html#!/base/path
As you can see in pure Hashbang mode all links in the HTML files must begin with the base such as "index.html#!".
HTML5 Mode
Configuration:
$routeProvider
.when('/path', {
templateUrl: 'path.html',
});
$locationProvider
.html5Mode(true);
You should set the base in HTML-file
<html>
<head>
<base href="/">
</head>
</html>
In this mode you can use links without the # in HTML files
link
Link in Browser:
http://www.example.com/base/path
Hashbang in HTML5 Mode
This mode is activated when we actually use HTML5 mode but in an incompatible browser. We can simulate this mode in a compatible browser by decorating the $sniffer service and setting history to false.
Configuration:
$provide.decorator('$sniffer', function($delegate) {
$delegate.history = false;
return $delegate;
});
$routeProvider
.when('/path', {
templateUrl: 'path.html',
});
$locationProvider
.html5Mode(true)
.hashPrefix('!');
Set the base in HTML-file:
<html>
<head>
<base href="/">
</head>
</html>
In this case the links can also be written without the hash in the HTML file
link
Link in Browser:
http://www.example.com/index.html#!/base/path
Fur future readers, if you are using Angular 1.6, you also need to change the hashPrefix:
appModule.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('');
}]);
Don't forget to set the base in your HTML <head>:
<head>
<base href="/">
...
</head>
More info about the changelog here.
This took me a while to figure out so this is how I got it working - Angular WebAPI ASP Routing without the # for SEO
add to Index.html - base href="/">
Add $locationProvider.html5Mode(true); to app.config
I needed a certain controller (which was in the home controller) to be ignored for uploading images so I added that rule to RouteConfig
routes.MapRoute(
name: "Default2",
url: "Home/{*.}",
defaults: new { controller = "Home", action = "SaveImage" }
);
In Global.asax add the following - making sure to ignore api and image upload paths let them function as normal otherwise reroute everything else.
private const string ROOT_DOCUMENT = "/Index.html";
protected void Application_BeginRequest(Object sender, EventArgs e)
{
var path = Request.Url.AbsolutePath;
var isApi = path.StartsWith("/api", StringComparison.InvariantCultureIgnoreCase);
var isImageUpload = path.StartsWith("/home", StringComparison.InvariantCultureIgnoreCase);
if (isApi || isImageUpload)
return;
string url = Request.Url.LocalPath;
if (!System.IO.File.Exists(Context.Server.MapPath(url)))
Context.RewritePath(ROOT_DOCUMENT);
}
Make sure to use $location.url('/XXX') and not window.location ... to redirect
Reference the CSS files with absolute path
and not
<link href="app/content/bootstrapwc.css" rel="stylesheet" />
Final note - doing it this way gave me full control and I did not need to do anything to the web config.
Hope this helps as this took me a while to figure out.
I wanted to be able to access my application with the HTML5 mode and a fixed token and then switch to the hashbang method (to keep the token so the user can refresh his page).
URL for accessing my app:
http://myapp.com/amazing_url?token=super_token
Then when the user loads the page:
http://myapp.com/amazing_url?token=super_token#/amazing_url
Then when the user navigates:
http://myapp.com/amazing_url?token=super_token#/another_url
With this I keep the token in the URL and keep the state when the user is browsing. I lost a bit of visibility of the URL, but there is no perfect way of doing it.
So don't enable the HTML5 mode and then add this controller:
.config ($stateProvider)->
$stateProvider.state('home-loading', {
url: '/',
controller: 'homeController'
})
.controller 'homeController', ($state, $location)->
if window.location.pathname != '/'
$location.url(window.location.pathname+window.location.search).replace()
else
$state.go('home', {}, { location: 'replace' })

Resources