AngularJS去掉的URL里的#号

AngularJS体验式编程系列文章,将介绍如何用angularjs构建一个强大的web前端系统。angularjs是由Google团队开发的一款非常优秀web前端框架。在当前如此多的web框架下,angularjs能脱颖而出,从架构设计上就高人一等,双向数据绑定,依赖注入,指令,MVC,模板。Angular.js创新地把后台技术融入前端开发,扫去jQuery一度的光芒。用angularjs就像写后台代码,更规范,更结构化,更可控。

关于作者

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/angularjs-url/

angularjs-url

前言

天天都在用AngularJS,各类文档也都看过好几遍,但总是些编程上的事找不到优雅的解决办法。今天终于把AngularJS的项目访问路径URL里的#号去掉了,这个问题不见得有多难,关键是花多长时间去理解AngularJS框架本身。

目录

  1. URL的#号问题
  2. 找到错误原因
  3. 静态网站的解决方案
  4. 动态网站的解决方案

1. URL的#号问题

使用AngularJS的朋友都应该了解,AngularJS框架定义了自己的前端路由控制器,通过不同URL实现单面(ng-app)对视图(ng-view)的部署刷新,并支持HTML5的历史记录功能,详细介绍可以参考文章:AngularJS路由和模板

对于默认的情况,是不启动HTML5模式的,URL中会包括一个#号,用来区别是AngularJS管理的路径还是WebServer管理的路径。

比如:下面的带#号的URL,是AngularJS管理的路径。

  • http://onbook.me/
  • http://onbook.me/#/
  • http://onbook.me/#/book
  • http://onbook.me/#/about

这种体验其实是不太友好的,特别是像我这种喜欢简洁设计的人,#号的出现非我自愿的,怎么看怎么难受。AngularJS框架提供了一种HTML5模式的路由,可以直接去掉#号。

通过设置$locationProvider.html5Mode(true)就行了。


book.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {

   //..省略代码
    $locationProvider.html5Mode(true);
}]);

支持HTML5的路由URL。

  • http://onbook.me/
  • http://onbook.me/book
  • http://onbook.me/about

接下来的问题就来了,当用这种方式设置了路径以后。如果用户从首页(http://onbook.me/)开始访问,然后跳转到 图书页(http://onbook.me/book)一切正常。但如果用户直接打开 图书页(http://onbook.me/book) ,就会出现404错误。

url1

就是这个问题纠结了我好长时间,让我不得不用带#号的URL。

2. 找到错误原因

那么,这个问题的原因出在哪里了呢? 在路径解析上出错了。

让我从头说起,AngularJS是单页应用,一个ng-app对应一个页面,一个URL。AngularJS实现了自己的前端路由,让一个ng-app可以管理多个URL,再对应到多个ng-vew上面。当我们去访问URL(http://onbook.me/book) 的时候,怎么确定这个路径是 WebServer 后台管理的URL还是AngularJS前台管理的URL呢?

分2种情况看:

  • 1. 用户如果是先访问 首页(http://onbook.me),然后再跳转到 页面(http://onbook.me/book),则这个跳转是由AngularJS前台管理的URL,访问是正常的。
  • 2. 用户直接访问 页面(http://onbook.me/book)时,请求是先被提交到了WebServer后台,后台路由没有对应页面(http://onbook.me/book)的路由管理,就会出现404的错误。

如果能把这层想明白,技术上就非常容易解决了。我们让WebServer把属于AngularJS管理的路由URL,都发转到ng-app就可以解决404的问题了,同时,没有#号,还支持HTML5的历史记录查询!!

实现起来分为2种解决方案:

  • 1. 静态网站:纯前台网站(JS+HTML+CSS),通过Nginx提供Web服务。
  • 2. 动态网站:前台(JS + HTML + CSS) + 后台Node.js提供Web服务。

3. 静态网站的解决方案

静态网站,我们需要修改的地方包括3个文件

  • index.html : ng-app的定义文件
  • app.js : 对应ng-app的控制文件
  • nginx.conf : nginx的网站配置文件

编辑 index.html,增加base标签。


<html lang="zh-CN" ng-app="book">
<head>
    <base href="/">
	
// 省略代码
</head>

编辑app.js,增加 $locationProvider.html5Mode(true);


book.config(['$routeProvider', '$locationProvider', '$sceProvider', 'tplProvider', function ($routeProvider, $locationProvider, $sceProvider, tplProvider) {
    $routeProvider
        .when('/', {templateUrl: tplProvider.html('welcome'), controller: 'WelcomeCtrl'})
        .when('/book', {templateUrl: tplProvider.html('book'), controller: 'BookCtrl'})             //图书
        .when('/book-r1', {templateUrl: tplProvider.html('book-r1'), controller: 'BookR1Ctrl'})   //R的极客理想
        .when('/video', {templateUrl: tplProvider.html('video'), controller: 'VideoCtrl'})         //视频
        .when('/about', {templateUrl: tplProvider.html('about'), controller: 'AboutCtrl'})         //关于作者
        .otherwise({redirectTo: '/'});
    $locationProvider.html5Mode(true);
}]);

编辑nginx的配置文件,增加try_files配置。


server {
        set $htdocs /www/deploy/mysite/onbook;
        listen 80;
        server_name onbook.me;
        location / {
            root $htdocs;
            try_files $uri $uri/ /index.html =404;
        }
}

这样,静态网站就搞定了,没有麻烦的#号了,可以直接访问和任意页面的刷新。

4. 动态网站的解决方案

动态网站,我们同样需要修改的地方包括3个文件。

  • index.html : ng-app的定义文件
  • app.js : 对应ng-app的控制文件
  • server.js : Express框架的路由访问控制文件

index.html 和 app.js两个文件修改,同静态网站的解决方案。动态网站,一般不是通过Nginx直接路由,而是通过Web服务器管理路由。假设我们使用的是Node.js的Express的Web框架。

打开Express框架的路由访问控制文件server.js,增加路由配置。


app.use(function (req, res) {
    console.log(req.path);
    if(req.path.indexOf('/api')>=0){
        res.send("server text");

    }else{ //angular启动页
        res.sendfile('app/index.html');
    }
});

设置当 站内路径(req.path) 不包括 /api 时,都转发到 AngularJS的ng-app(index.html)。所以,我们再直接访问地址 (http://onbook.me/book)时,/book 不包括 /api,就会被直接转发到AngularJS进行路由管理。我们就实现了路由的优化!

我已用了AngularJS有8-9个月了,主要功能都用到了,但还不见其全貌。经常会遇到各种问题,不过比起jQuery的各种无解的问题,还是值得的。优秀的框架值得我们的研究和使用,在摸索中前进!

转载请注明出处:
http://blog.fens.me/angularjs-url/

打赏作者

This entry was posted in Javascript语言实践

0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

44 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Kaitoborn

前天刚遇到 问题原因想明白了 但是对express不熟悉 采用的方法是禁用html5 mode
感谢分享 学习了

Conan Zhang

🙂

choon

Hi,张丹,你的文章帮了我大忙,解决了一直困扰我的问题。但是虽然去掉了#号,锚点依然会被Angualr路由拦截而失效。这个棘手的问题该如何解决呢?my email:mercury1259@outlook.com,thanks.

Conan Zhang

锚点应该没有问题,可以直接使用的。

比如,下面的URL,第一#用于Angular的路由,第二#是文章的锚点匹配。
http://onbook.me/#/video#b 映射 http://onbook.me/video#b

Evan
Evan

你好,这样做,二级页面是可以的,但是三级页面好像会有问题。

Conan Zhang

我没有遇到你说的情况,尽量不要自己把问题给搞复杂了。

caidewu

我也遇到这个问题,比如:http://localhost:8000/about是好的,但是http://localhost:8000/about/detail就会出现样式丢失的情况,

Zero.k

那是因为你的css的路径使用的相对路径导致的

Conan Zhang

Good!!

caidewu

如果是使用grunt serve启动的服务,该如何修改web服务器的配置

Conan Zhang

grunt serve一般只能用于开发环境,没什么可配置的,自己按习惯调一下就行了。

caidewu

我的意思是,你上面说如果是静态网站,修改nginx的配置文件,达到刷新不会404,可是是开发的时候跑在grunt serve上,那还是会刷新404的,这咋破呢,

Atomic

res.sendfile(‘app/index.html’);//相当于java里的啥?转发?重定向?

Conan Zhang

类似于Java里的res.redirect()

Gloria

你好,我最近玩angularjs也被这个#惹出了强迫症…你的这篇文章刚好帮上大忙了。不过有些地方不明白,我的express是4.2的里面好像没有server.js,我改在app.js里面像路由定向那样给angularjs路由定义一个路由定向,从里面用你配置server.js的方法处理重定向。但是不知道为啥res.sendfile()重定向不成,到页面上就变成下载文件了。我的ng-app是views/index.ejs:

app.use(‘/’, routes);
app.use(‘/users’, users);
// 重定向URL
app.use(‘/ng’,function(req,res){
res.sendfile(“views/index.ejs”);
});

Gloria

好吧…ng-app文件是ejs文件所以就算重定向angular也不会对其进行处理么(自己猜测)。经过几番折腾我把nodejs的ejs模板舍掉,暂时直接后台返回html文件,这样就能按照文中的方法实现重定向了,不过问题又来了…重定向后的页面CSS不渲染了,样式文件有引用但不起作用,请问是啥原因?T.T

Gloria

解决了…错误原因是我把放到body里了,放head里就OK了

Conan Zhang

🙂

Conan Zhang

express 4.2以后版本,我用下面的写法,完全不加载EJS了。

app.use(function (req, res) {
res.sendFile(path.join(__dirname, ‘app/index.html’));
});

Gloria

嗯,我这几天一直在捣鼓这个,也是这么解决的。然后呢…又碰到的另外的问题= =…我文件结构是采用 express -e 自动生成的结构,在app.js里面用app.use重定向没问题,采用它里面的route中间件重定向就不行了。这个我姑且把路由放在express的app里算是解决了(网上看到的别人的解决办法),另外还有个没解决的问题:如果有两套页面怎么办?比如一套负责前端的angular页面,另一套负责后端的angular页面,这个时候app应该怎么区分这两套页面的路由?

Conan Zhang

几套页面都可以,匹配好路径就行了。2套页面的授权就会麻烦一些了。

app.use(‘/’,function (req, res) {
res.sendFile(path.join(__dirname, ‘app/index.html’));
});

app.use(‘/admin’,function (req, res) {
res.sendFile(path.join(__dirname, ‘app/admin.html’));
});

Gloria

唔,你这样写没问题么?我像你这样写的话会一直执行’/’的路径,就算把路由写成‘/admin’也还是会被执行成‘/’的路由的。把两个use位置调换一下就可以了,先定义”/admin”路由,再定义‘/’路由,这样两个路由执行就能分开了。额,虽然感觉一直在麻烦大神,不过还是请教一下我后来又碰到的个问题:我在一个angular中通过$http处理一些逻辑之后,想跳到另一套页面的话,用res.sendFile()转换不了angular页面的html呢,第二套的html会被$http的success包裹成data形式返回了= =,有办法把这返回的data完全替代掉原来的angular页面么?

Conan Zhang

对,要顺序掉过来写。第二问题,angularjs跳另一个页用$location.path(“/admin”)

Gloria

唔,不是这意思,可能是我没表达好。这么说:angular是单页面应用吧,我想跳的是另一套页面,不是本套页面的另一个页面。需要的是把现有的angular APP废除掉再通过res.sendFile传一套新的html页面建立个新的angular app,这样能做到吗?

Conan Zhang

需要在后台跳转,用res.sendFile(),可以实现的。

YY

那你直接修改loation.href不行吗?

Tony7Lee

赞,很不错的文章!
个人认为,前端路由加上“#”或者“?”,标记前端路由部分的开始,比较合理,“/”这个路由标记还是server side 专用比较好,毕竟URL的美观只是针对程序员自己的,用户也没人在乎这个,而且#号也可以更好的兼容IE。

Conan Zhang

/ 主要用于后台,# 主要用于锚点,? 主要用于参数
#和?,对于SEO其实是不太友好的,整个Angularjs对于是SEO来说,都是需要额外优化的。

具体怎么设计和使用,还是看个人的兴趣吧!

MarzinZ

Hi, 文章很赞。有一个问题想请教一下,如果不是使用静态服务器,而是直接将js、html、css文件放在 github page 上,怎么解决html5mode下的链接重载问题呢?

多谢!

Conan Zhang

放到github上,参考hexo的做法。
http://blog.fens.me/hexo-blog-github/

yangbai

恩,正好有这个问题的疑惑,知道去配置nginx,但是nginx不会配置,过来抄袭了一下 哈哈

yangbai

哈哈 已经知道要通过niginx去匹配url了,但是不会配置,过来抄袭一下 哈哈 [亲亲]

Conan Zhang

嘿嘿,

yuelanfenghua

我是放在tomcat下来运行这个应用的,请问,我要去掉这个#应该怎么配置,在刷新的时候不会报错

Conan Zhang

Anguarjs只是静态的网页,不需要后台渲染;你这里应该是Tomcat和Nginx作用是一样的,要参考Nginx的现实。

zhouzhou

我用你的方法试过,发现会把JS文件的内容也变成index.html里面的内容

Conan Zhang

没看明白你说的。

Kagol

你好!请问nginx配置里的$uri是什么意思?谢谢!

Conan Zhang

用户访问的domain地址,比如 http://www.fens.me

Alice yang

你好,你的文章帮了我了解URL,非常感谢,一直有一个困惑,直接打开链接,例如:http://www.wquan.net/www/weblogin.html这个链接,它会自动在链接后面加上#/weblogin.html这个参数,不知道是怎么回事呢?一直困惑很久

Conan Zhang

$locationProvider.html5Mode(true);
这个设置,就可以去掉默认的#了

yuki

好文章,解决了一个一直困扰我的问题。估计很多人想知道Apache的设置,贴出来.htaccess的写法,和大家一起学习。

RewriteEngine On
# If an existing asset or directory is requested go to it as it is
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ – [L]

# If the requested pattern is file and file doesn’t exist, send 404
RewriteCond %{REQUEST_URI} ^(/[a-z_-s0-9.]+)+.[a-zA-Z]{2,4}$
RewriteRule ^ – [L,R=404]

# otherwise use history router
RewriteRule ^ /index.html

Conan Zhang

厉害,不错不错!!!

44
0
Would love your thoughts, please comment.x
()
x