258

Is it possible to remove the # symbol from angular.js URLs?

I still want to be able to use the browser's back button, etc, when I change the view and will update the URL with params, but I don't want the # symbol.

The tutorial routeProvider is declared as follows:

angular.module('phonecat', []).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider.
  when('/phones', {templateUrl: 'partials/phone-list.html',   controller: PhoneListCtrl}).
  when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
  otherwise({redirectTo: '/phones'});
}]);

Can I edit this to have the same functionality without the #?

Ricky Dam
  • 1,833
  • 4
  • 23
  • 42
Tim Webster
  • 9,158
  • 8
  • 27
  • 30

14 Answers14

254

Yes, you should configure $locationProvider and set html5Mode to true:

angular.module('phonecat', []).
  config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {

    $routeProvider.
      when('/phones', {templateUrl: 'partials/phone-list.html',   controller: PhoneListCtrl}).
      when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
      otherwise({redirectTo: '/phones'});

    $locationProvider.html5Mode(true);

  }]);
Eugene
  • 4,352
  • 8
  • 55
  • 79
Maxim Grach
  • 4,300
  • 2
  • 20
  • 19
  • Doing this seems to cause issue in IE, I get all sorts of digest errors in the console and it still ends up redirecting to #/phones Error: 10 $digest() iterations reached. Aborting! I don't use any custom tags in my App either – Tim Webster Feb 08 '13 at 11:36
  • I've read through that article, although it refers to IE7 & 8, I have the same issues in 9. I'm able to get my app working in IE7 using my module definition above. But that has the hash in the url. If I use your definition I get errors in IE9. :( – Tim Webster Feb 08 '13 at 11:52
  • 10
    Because __IE lt 10__ doesn't support html5 history API which were enabled by setting up `html5Mode(true)`. In IE you have to use `#` in routes. – Maxim Grach Feb 08 '13 at 16:50
  • Ok, thanks. I opted to have a different config for ie not setting html5mode. – Tim Webster Feb 08 '13 at 17:42
  • @MaximGrach can you please make it more clear what `IE lt 10` means? – powtac Aug 05 '13 at 14:28
  • 10
    @powtac `IE lt 10` means Internet Explorer less then version 10 – Maxim Grach Aug 06 '13 at 18:26
  • 6
    Can you set it so this fallsback to using # in IE? Is it as simple as just wrapping `$locationProvider.html5Mode(true);` in `if !(IE lt 10)` ? – Andy Hayden Aug 14 '13 at 15:07
  • After doing this, is it possible to access localhost/phones directly? not passing by the index.html? – storm_buster Jan 05 '14 at 00:05
  • 54
    So this solution does solve removing the hashtag in the url given the url has a hashtag, however it doesn't solve how to respond to requests that doesn't want the hashtag to be included in the first place. Aka, it solves http://blah/#/phones -> http://blah/phones but it doesn't handle http://blah/phones directly. – Luke Feb 12 '14 at 01:56
  • 1
    @Luke is very correct... does anybody have a solution for a way to directly handle URLs given to the browser without the # to begin with? (as in blah/phones resolving to a page in our app). That's the problem I want to solve... – Mik Cox Apr 15 '14 at 20:33
  • 2
    @Mik Cox, I was actually able to solve it. Have your backend send back index.html for all URLs, that way you won't be seeing 404s. – Luke Apr 16 '14 at 02:57
  • Sorry, I don't get what you mean @Luke, could you add a bit more detail? – Mik Cox Apr 17 '14 at 22:31
  • 1
    @MikCox, when a URL is directly inputted in the browser, your server may return a 404 because it can't find that resource. What you want to do is for the browser to hand back index.html (since most SPAs have just a single html template) and have Angular handle the URL to correctly manipulate the template. – Luke Apr 18 '14 at 04:44
  • @Luke, how do you handle something like emailing a url to someone that looks like "http://www.mywebsite.com/user/123131/settings/" instead of "http://www.mywebsite.com/#/user/123131/settings/"? – Bogdan Varlamov Apr 28 '14 at 17:44
  • @BogdanVarlamov can you clarify? Handling the emailing? Or handling the link? If you are talking about handling the link, the answer is already provided above. – Luke Apr 29 '14 at 17:50
  • @Luke, I see where you say that "it doesn't handle blah/phones" directly. I'm wondering if there is a different solution that can handle that type of URL as a request (i.e. i paste that URL in the browser and wan't the app to resolve the route without the #) – Bogdan Varlamov Apr 29 '14 at 18:55
  • 1
    You may find this useful - https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-configure-your-server-to-work-with-html5mode – Moin Haidar Jul 16 '14 at 11:45
  • 2
    I also had to add `requireBase: false` to get it to work under the `$locationProvider.html5Mode` block – Aaron Lelevier Nov 23 '14 at 15:36
  • @Luke The .htaccess solution by Thomas Knápek fixed this for me. – plong0 Mar 10 '15 at 17:57
  • 3
    I wish this answer had the explanation in the answer rather than in the comments. I find myself unsure of how to actually implement this @MaximGrach – David Dec 03 '15 at 18:13
  • 10
    I needed to put in my index.html section. – nyxz Dec 18 '15 at 13:51
  • It works fine but it doesn't work when we refresh / reload the page. please help if you have any suggestions – PHP_RIDER Feb 25 '16 at 10:39
  • when I tried this, it works but /actionname is removed from the url so /actionname changes to / – Mike W Jun 02 '17 at 17:51
  • 1
    This solutions just removes the "#" from the URL visually. I cannot access the Page directly like http://www.example.com/about from the Browser URL. If I want to access the Page from Browser URL, I still need to include "#" like http://www.example.com/#/about. Is that correct or I am making some mistake? I am using AngularJS 1.6. – Ankit Prajapati Jun 28 '18 at 16:29
56

Be sure to check browser support for the html5 history API:

  if(window.history && window.history.pushState){
    $locationProvider.html5Mode(true);
  }
SSaidi
  • 576
  • 4
  • 5
  • 23
    This might be better suited as a comment as it doesn't directly answer the question. – brandonscript Dec 07 '13 at 07:50
  • 31
    According to the [developer guide](http://docs.angularjs.org/guide/$location), this detection is done automatically: `If the HTML5 History API is not supported by a browser, the $location service will fall back to using the hashbang URLs automatically` – IanB Mar 26 '14 at 00:10
  • Worked like a charm, and I appreciate method checking rather than browser version checking--future proof. – Shane May 26 '14 at 00:59
47

To remove the Hash tag for a pretty URL and also for your code to work after minification you need to structure your code like the example below:

jobApp.config(['$routeProvider','$locationProvider',
    function($routeProvider, $locationProvider) {
        $routeProvider.
            when('/', {
                templateUrl: 'views/job-list.html',
                controller: 'JobListController'
            }).
            when('/menus', {
                templateUrl: 'views/job-list.html',
                controller: 'JobListController'
            }).
            when('/menus/:id', {
                templateUrl: 'views/job-detail.html',
                controller: 'JobDetailController'
            });

         //you can include a fallback by  including .otherwise({
          //redirectTo: '/jobs'
        //});


        //check browser support
        if(window.history && window.history.pushState){
            //$locationProvider.html5Mode(true); will cause an error $location in HTML5 mode requires a  tag to be present! Unless you set baseUrl tag after head tag like so: <head> <base href="/">

         // to know more about setting base URL visit: https://docs.angularjs.org/error/$location/nobase

         // if you don't wish to set base URL then use this
         $locationProvider.html5Mode({
                 enabled: true,
                 requireBase: false
          });
        }
    }]);
Emeka Mbah
  • 16,745
  • 10
  • 77
  • 96
18

I write out a rule in web.config after $locationProvider.html5Mode(true) is set in app.js.

Hope, helps someone out.

  <system.webServer>
    <rewrite>
      <rules>
        <rule name="AngularJS" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
            <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>

In my index.html I added this to <head>

<base href="/">

Don't forget to install url rewriter for iis on server.

Also if you use Web Api and IIS, this match url will not work out, as it will change your api calls. So add third input(third line of condition) and give out a pattern that will exclude calls from www.yourdomain.com/api

Yagiz Ozturk
  • 5,408
  • 7
  • 29
  • 44
11

If you are in .NET stack with MVC with AngularJS, this is what you have to do to remove the '#' from url:

  1. Set up your base href in your _Layout page: <head> <base href="/"> </head>

  2. Then, add following in your angular app config : $locationProvider.html5Mode(true)

  3. Above will remove '#' from url but page refresh won't work e.g. if you are in "yoursite.com/about" page refreash will give you a 404. This is because MVC does not know about angular routing and by MVC pattern it will look for a MVC page for 'about' which does not exists in MVC routing path. Workaround for this is to send all MVC page request to a single MVC view and you can do that by adding a route that catches all

url:

routes.MapRoute(
    name: "App",
    url: "{*url}",
    defaults: new {
        controller = "Home", action = "Index"
    }
);
Mo.
  • 26,306
  • 36
  • 159
  • 225
Maksood
  • 1,180
  • 14
  • 19
6

You can tweak the html5mode but that is only functional for links included in html anchors of your page and how the url looks like in the browser address bar. Attempting to request a subpage without the hashtag (with or without html5mode) from anywhere outside the page will result in a 404 error. For example, the following CURL request will result in a page not found error, irrespective of html5mode:

$ curl http://foo.bar/phones

although the following will return the root/home page:

$ curl http://foo.bar/#/phones

The reason for this is that anything after the hashtag is stripped off before the request arrives at the server. So a request for http://foo.bar/#/portfolio arrives at the server as a request for http://foo.bar. The server will respond with a 200 OK response (presumably) for http://foo.bar and the agent/client will process the rest.

So in cases that you want to share a url with others, you have no option but to include the hashtag.

chris
  • 1,787
  • 1
  • 13
  • 13
5

Follow 2 steps-
1. First set the $locationProvider.html5Mode(true) in your app config file.
For eg -
angular.module('test', ['ui.router']) .config(function($stateProvider, $urlRouterProvider, $locationProvider) { $locationProvider.html5Mode(true); $urlRouterProvider.otherwise('/'); });

2.Second set the <base> inside your main page.
For eg ->
<base href="/">

The $location service will automatically fallback to the hash-part method for browsers that do not support the HTML5 History API.

GrvTyagi
  • 4,231
  • 1
  • 33
  • 40
Anshul Bisht
  • 1,644
  • 20
  • 21
3

My solution is create .htaccess and use #Sorian code.. without .htaccess I failed to remove #

RewriteEngine   On
RewriteBase     /
RewriteCond     %{REQUEST_URI} !^(/index\.php|/img|/js|/css|/robots\.txt|/favicon\.ico)
RewriteCond     %{REQUEST_FILENAME} !-f
RewriteCond     %{REQUEST_FILENAME} !-d
RewriteRule     ./index.html [L]
Shivek Parmar
  • 2,865
  • 2
  • 33
  • 42
1

According to the documentation. You can use:

$locationProvider.html5Mode(true).hashPrefix('!');

NB: If your browser does not support to HTML 5. Dont worry :D it have fallback to hashbang mode. So, you don't need to check with if(window.history && window.history.pushState){ ... } manually

For example: If you click: <a href="/other">Some URL</a>

In HTML5 Browser: angular will automatically redirect to example.com/other

In Not HTML5 Browser: angular will automatically redirect to example.com/#!/other

1

This answer assumes that you are using nginx as reverse proxy and you already did set $locationProvider.html5mode to true.

->For the people who might still be struggling with all cool stuff above.

Surely, @maxim grach solution works fine, but for the solution for how to respond to requests that doesn't want the hashtag to be included in the first place what can be done is check if php is sending 404 and then rewrite the url. Following is the code for nginx,

In the php location, detect 404 php error and redirect to another location,

location ~ \.php${
  ...
  fastcgi_intercept_errors on;
  error_page 404 = /redirect/$request_uri ;
}

Then rewrite url in redirect location and proxy_pass the data, offcourse, put your website url at the place of my blog's url. (Request : Question this only after you tried it)

location /redirect {
  rewrite ^/redirect/(.*) /$1;
  proxy_pass http://www.techromance.com;
}

And see the magic.

And it definitely works, atleast for me.

Satys
  • 2,319
  • 1
  • 20
  • 26
1

Start from index.html remove all # from <a href="#/aboutus">About Us</a> so it must look like <a href="/aboutus">About Us</a>.Now in head tag of index.html write <base href="/"> just after last meta tag.

Now in your routing js inject $locationProvider and write $locatonProvider.html5Mode(true); Something Like This:-

app.config(function ($routeProvider, $locationProvider) {
    $routeProvider
        .when("/home", {
            templateUrl: "Templates/home.html",
            controller: "homeController"
        })
            .when("/aboutus",{templateUrl:"Templates/aboutus.html"})
            .when("/courses", {
                templateUrl: "Templates/courses.html",
                controller: "coursesController"
            })
            .when("/students", {
                templateUrl: "Templates/students.html",
                controller: "studentsController"
            })
        $locationProvider.html5Mode(true);
    });

For more Details watch this video https://www.youtube.com/watch?v=XsRugDQaGOo

Aniket Jha
  • 1,751
  • 11
  • 13
1

Guess this is reallllly late for this. But adding the below config to the app.module imports does the job:

RouterModule.forRoot(routes, { useHash: false })
Sagar Bhat
  • 111
  • 7
0

Step 1: Inject the $locationProvider service into the app config's constructor

Step 2: Add code line $locationProvider.html5Mode(true) to the app config's constructor.

Step 3: in the container (landing, master, or layout) page, add html tag such as <base href="/"> inside the tag.

Step 4: remove all '#" for routing config from all anchor tags. For examples, href="#home" becomes href="home"; href="#about" becomes herf="about"; href="#contact" becomes href="contact"

 <ul class="nav navbar-nav">
     <li><a href="home">Home</a></li>
     <li><a href="about">About us</a></li>
     <li><a href="contact">Contact us</a></li>
</ul>
Thomas.Benz
  • 8,381
  • 9
  • 38
  • 65
-1

Just add $locationProvider In

.config(function ($routeProvider,$locationProvider)

and then add $locationProvider.hashPrefix(''); after

.otherwise({
        redirectTo: '/'
      });

That's it.

Narendra Solanki
  • 1,042
  • 14
  • 20