Angular.js专场angularAngular开发指南

01、AngularJS路由

2017-10-22  本文已影响90人  spilledyear

本文是自己学习angularJS的第一篇总结,并不是一个step by step 的教程,而是将自己学习过程中有疑惑的地方记录下来。考虑到之后的AngularJS总结也将依赖这个应用,因此本文会在前面两个章节具体的介绍应用搭建过程,在之后的总结中,将不再涉及应用搭建的内容。

路由是前端构建单页面应用(SPA)必不可少的一部分。AngularJS中有两种路由实现,一个是内置的ngRouter,还有一个是基于 ngRoute 开发的第三方路由模块uiRouter。考虑到目前主要使用的是功能更强大uiRouter模块,因此本文将主要介绍uiRouter的使用。
需要注意的是,AngularJS1.2以后做了模块化的处理,也就是路由没有包含在Angular.js这个文件里面,而是把它独立出来成了一个模块。也就是说,不管是 使用ngRouter还是uiRouter,都需要单独引入模块文件。ngRouter对应的外部 js文件是 angular-route.js,uiRouter对应的外部文件是angular-ui-router.js。引入模块的方式有两种,一种是直接在index.html中中引入文件模块文件,这种方式比较简单便捷,甚至可以不用单独下载,直接引入cdn地址,如下:

<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
 <script src="http://apps.bdimg.com/libs/angular-route/1.3.13/angular-route.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
这里的地址是分别从不同的地方找过来的,有出入可以在网上查查

还有一种方式是使用npm包管理器安装依赖。为了响应前端工程化开发,这里使用npm包管理器安装依赖,考虑到有些同学坑没有接触了模块化,详细的介绍以下npm配合webpack搭建前端应用的过程。

新建应用

创建一个项目并安装依赖。

mkdir routerStart
cd routerStart
npm init
查看angular 版本: npm view angular version
根据查出来的版本进行安装  
安装angular :npm install --save angular@1.6.6
安装angular-route:npm install --save angular-route@1.6.6
安装@uirouter/angularjs:npm install --save @uirouter/angularjs@1.0.10

也可以直接在这个网站查看各个包的信息:npm包管理器

启动应用

以上已经安装好了最重要的三个依赖包,但为了能够更方便便捷的进行测试,再引入一个概念:webpack。可以把webpack看作是一个组织模块的框架,它的作用是:分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。有关于webpack的详细讲解配置,webpack

接下来新建几个文件:index.html、index.js、webpack.config.js。其中index.htm作为在浏览器上展示的界面,index.js是index.html中用到的脚本,webpack.config.js是webpack的配置文件,各个文件的内容如下:

//package.json配置文件
{
  "name": "routerstart",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack"
  },
  "author": "spilledyear",
  "license": "ISC",
  "dependencies": {
    "angular": "^1.6.6",
    "angular-route": "^1.6.6",
    "@uirouter/angularjs": "^1.0.10",
    "webpack": "^3.8.1"
  }
}
//webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: './bundle.js',
  },

  module: {
    rules: [{
      // test: /\.less$/,
      // use: [{
      //   loader: "style-loader"  // creates style nodes from JS strings
      // }, {
      //   loader: "css-loader"    // translates CSS into CommonJS
      // }, {
      //   loader: "less-loader"   // compiles Less to CSS
      // }]
    }]
  }
};
//index.js
import angular from 'angular';
import ngRoute from 'angular-route';
import uiRouter from '@uirouter/angularjs';

var app = angular.module("app", []) 

  .controller("appCtrl",function($scope){
      $scope.name = "hello!";
  });
index.html
<html ng-app="app" ng-controller="appCtrl"> 
  <head>
    <title>test</title>
    <meta charset="utf-8">  
  </head>   
 <body> 
    <input ng-model="name"/>
    <h1>{{name}}</h1>

        <script src="bundle.js"></script> 
</body> 
</html>

需要注意index.html中的这部分内容

<script src="bundle.js"></script> 

并没有直接引用index.js文件,而是引入了bundle.js文件?这里会让人产生疑惑,为什不引入index.js文件?bundle.js文件从哪里来?这也是前端模块化工程与非模块化工程的主要区别,在非模块化工程中,习惯性的将所有依赖的js框架引入到html界面,这种做法造成的后果就是项目结构组织混乱、文件依赖关系错综复杂,代码功底不够还会让各种全局满天飞。模块化很好的解决了这些问题,想知道模块化更具体的细节,请自行google。而这里引入的bundle.js文件,其实和webpack有关,如果此时没有利用webpack做任何事情,仅仅是用浏览器打开index.html文件,那么肯定是可以在控制台看到报错信息的,此时的bundle.js文件是不存在的。在webpack的配置文件中,有这么一段内容:

 entry: './index.js',
 output: {
    filename: './bundle.js',
  },

根据字面的意思,应该可以猜到这段代码的用意,事实也是如此,entry和output是webpack中的两个概念,entry作为整个应用的入口文件,webpack会找到入口文件,然后根据这个文件里面的内容依次加载其它依赖的模块,然后进行编译等操作,最后将所有编译后结果输出到output 配置的输出文件中,文中配置的是bundle.js,这就是为什么只需要在index.html引入bundle.js的原因。如果你有仔细观察bundle.js文件的话,会发现这个文件很大(最少有1M),这就很好的说明了它是编译后的产物。
还有一个需要注意的地方是package.json文件

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack"
  },

在scripts脚本中配置了start命令,大概就是说在执行npm start的时候相当于执行了 webpack命令。具体的细节还请参照这篇文章:webpack

接下来是启动过程

在命令行以下执行
npm start
如果一切正常,会在命令行下得到以下信息


image.png

这时候再观察应用目录结构,会发现根目录下多了一个bundle.js文件,此时完全可以把 npm start 当作是一个编译过程。
准备工作完毕,直接在浏览器下打开index.html文件,运行结果正常


image.png

如果模块化工程开发过程是这样的,那也太麻烦了吧。可能这样吗?不存在的。想使用更强大的功能,只需要修改webpack配置文件就可以了,比如开发中最期待的热部署。考虑到之后的许多AngularJS特性都需要利用这个应用来测试,在此贴出webpack比较全面的配置文件(以后可能还会添加),想了解详情请跳转上文中提到的链接

//webpack.congfigjs
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './app/index.js',
  output: {
    path: __dirname + "/dist",
    filename: "app.js"
   },
  devtool: 'eval-source-map',
  devServer: {
    port: 9000,                              //端口
    contentBase: "./app",            //本地服务器所加载的页面所在的目录
    historyApiFallback: true,     //不跳转
    inline: true                              //实时刷新
  },
  module: {
    rules: [
    {
      test: /(\.jsx|\.js)$/,
      use: {
        loader: "babel-loader"
      },
      exclude: /node_modules/
    },

    {
      test: /\.less$/,
      use: [{
        loader: "style-loader"  // creates style nodes from JS strings
      }, {
        loader: "css-loader",    // translates CSS into CommonJS
        options: {
          modules: true
        }
      }, {
        loader: "less-loader"   // compiles Less to CSS
      }]
    }]
  },
  plugins: [
    //new 一个这个插件的实例,并传入相关的参数
    new webpack.BannerPlugin('版权所有,翻版必究'),
    new HtmlWebpackPlugin({
      template: __dirname + "/app/index.tmpl.html"    
    })
  ],
};

同时,应用的目录结构也做了相应的调整,如下:

image.png

app目录下存放的是源文件,dist下是编译后的输出文件。其它几个配置文件的配置信息如下:

//.babelrc
{
  "presets": ["es2015", "stage-0"]      //babelr支持的模块
}
//package.json
{
  "name": "routerstart",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --open",
    "webpack": "webpack"
  },
  "author": "spilledyear",
  "license": "ISC",
  "dependencies": {
    "angular": "^1.6.6",
    "angular-route": "^1.6.6",
    "@uirouter/angularjs": "^1.0.10",
    "bootstrap": "^3.3.7"
  },
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "html-webpack-plugin": "^2.30.1",
    "webpack": "^3.8.1",
    "webpack-dev-server": "^2.9.3"
  }
}

工作完毕,命令行下直接执行以下命令

npm start

浏览器自动打开,结果和刚才的一样,同时,修改源代码,浏览器也会即时刷新。
注意,虽然应用运行正常,但是这里还有些问题。从package.json可以看出目前配置了两个命令,一个是 npm start,另一个是 npm run webpack。其中npm run webpack 完全是用来打包的, npm start 配置的是webpacke-dev,是为了方便调试。但是angularJS开发和react开发是有不同的,这里也没有用JSX语法,这就意味着需要将angularJS中的一些html文件搬到编译后的目录(dist),可能需要用gulp,为了不再增加应用的复杂性,这里不再引入gulp相关内容。因此,再之后的开发过程中,不需要考虑 npm run webpack 这个命令。如果需要打包,可能需要将app目录下的一些文件手动拷贝到dist目录下。

uiRouter

uiRouter是基于 ngRoute 开发的第三方路由模块,功能比ngRoute 强大,使用方式也比较简答,因此先介绍uiRouter。当然,最好的文档还是官方文档:ui-router ,例如:

image.png

uiRouter的首页就详细的介绍了与ngRoute的关系以及使用方式。通过api文档API可以发现,uiRouter 中存在这么几个概念 。

image.png

接下来介绍使用最多的几个。
模块
ui.router

服务
$urlRouterProvider
$stateProvider

指令
ui-sref
ui-view

首先需要明白“state” 是路由中非常重要的一个概念,在使用过程中,可以为每个state分配所对应的模块(界面),当需要改变界面时,只需要找到对应界面(模板)的那个state,最后uiRouter将模板加载到模板容器(ui-view),用户就可以在界面看到想要的效果。

$urlRouterProvider起着监视$location服务的作用。用官方的一段原话:

$urlRouterProvider has the responsibility of watching $location. When $location changes it runs through a list of rules one by one until a match is found. $urlRouterProvider is used behind the scenes anytime you specify a url in a state configuration. All urls are compiled into a UrlMatcher object (see $urlMatcherFactory below).
大概就是说当 $location发生改变的时候,$urlRouterProvider将会采用遍历的方式找到发生变化的那个选项。因为在配置state的时候是会绑定url的,而这个过程其实使用到了$urlRouterProvider。

$stateProvider用于管理state和控制state状态直接的转换,进而通过改变state改变界面效果。它提供了一些触发事件和回调函数,异步解析目标状态的任何依赖项,并更新$ location以反映当前状态。

ui-sref指令

Equivalent to href or ng-href in <a /> elements except the target value is a state name. Adds an appropriate href to the element according to the state associated URL.
可以作用在<a>标签上,它指是一个 state 对应的值。当点击链接的时候,就会找到state对应的URL,进而导航到对应的URl。

ui-view指令

Renders views defined in the current state. Essentially ui-view directives are (optionally named) placeholders that gets filled with views defined in the current state.
可以看作时一个界面的容器。通过路由加载的界面,都必须放到一个或者多哥ui-view容器中。它最主要的作用是呈现当前状态下定义的视图,也就意味着如果没有定义ui-view,当前state对应的视图就没办法展现,在界面上就看不到任何效果。

接下来通过一个实例来讲解uiRouter的使用方式。

//index.js
import angular from 'angular';
import ngRoute from 'angular-route';
import uiRouter from '@uirouter/angularjs';

var app = angular.module("app", [uiRouter]) 
  .config(["$stateProvider", "$urlRouterProvider",
    function ($stateProvider, $urlRouterProvider) {
        $stateProvider
          .state("app", { 
            url: "/",
            templateUrl: "index.html"
          })
        .state("page01", { 
          url: "/page01",
          templateUrl: "template/page01.html"
        })
        .state("page02", { 
          url: "/page02",
          templateUrl: "template/page02.html"
        })
        .state("page03", { 
          url: "/page03",
          templateUrl: "template/page03.html"
        })
    }
  ])
  .controller("appCtrl",function($scope, $state){
    $scope.name = "hello!";
    $scope.goPage = function(){
      $state.go('page03');
    }
  });
//index.html
<html ng-app="app" ng-controller="appCtrl"> 
  <head>
    <title>test</title>
    <meta charset="utf-8">  
  </head>   
 <body> 
    
    <div style="display: flex;flex-direction: row;justify-content: space-between;">
        <div style="">
            <div>
                <a ui-sref="page01">page01</a>  
            </div>

            <div>
                <a ui-sref="page02">page02</a>  
            </div>

            <div>
                <button ng-click="goPage()">page03</button>  
            </div>
        </div>

        <div class="item">
            <ui-view></ui-view>
        </div>
    </div>

    <script type="text/javascript" src="index.js"></script>
 </body> 
</body> 
</html>

从上可以看出,在index.js中定义了4个state, $stateProvider的state方法接收两个参数,第一个参数是 “state”的名称,第二个参数是“state"相关的配置,其中包括url和对应的视图模板(界面)。

image.png

同时,在index.html中,使用了ui-serf指令。如上所述,这个指令的作用其实和$state.go('xxx')是一样的,都是为了跳转到对应state的界面。通过改变state的指向从而改变ui-view 模板容器渲染的内容。
为了看到界面效果,还需要在app目录下添加一个template目录,这个目录的作用就是用来存放视图模板(界面、html文件)。因此,最终的目录结构如下:

image.png

执行 npm start 命令,在浏览器查看效果;

初始界面效果图如下


image.png

点击相应的按钮,观察浏览器地址栏的变化:


image1.png
image2.png
image3.png

可以很容易的发现,地址栏在变,界面右侧区域的内容也在变化,这就是一个最基本的路由系统。在此之余,简单的分析其中的一些细节和路由过程。


image.png

从上图可以看出,此处有两种路由实现,ui-sref 和 添加一个单击事件,然后在单击事件中完成逻辑处理。

简单看看 goPage() 实现:
$scope.goPage = function(){
  $state.go('page03');
}

调用的就是 $state.go('x'x'x') 方法。

路由过程
点击对应链接,触发 $state.go('x'x'x') 方法,uiRouter根据state找到对应的视图并加载在ui-view容器中,实现界面的变化。一定要注意,是将视图模板加载到 ui-view容器中,没有ui-view容器,是不能够成功显示对应的模板内容的,这点必须要记住。

父子路由

父子路由实现的方式也比较简单。对index.js 稍作修改

import angular from 'angular';
import ngRoute from 'angular-route';
import uiRouter from '@uirouter/angularjs';

var app = angular.module("app", [uiRouter]) 
  .config(["$stateProvider", "$urlRouterProvider",
    function ($stateProvider, $urlRouterProvider) {
        $stateProvider
          .state("app", { 
            url: "/",
            templateUrl: "index.html"
          })
        .state("page01", { 
          url: "/page01",
          templateUrl: "template/page01.html"
        })
        .state("page02", { 
          url: "/page02",
          templateUrl: "template/page02.html"
        })
        .state("page03", { 
          url: "/page03",
          templateUrl: "template/page03.html"
        })
        .state("page03.children", { 
          url: "/children",
          views: {
            'children': {
              templateUrl: "template/children.html"
            }
          }
        })
    }
  ])
  .controller("appCtrl",function($scope, $state){
    $scope.name = "hello!";
    $scope.goPage = function(){
      $state.go('page03');
    }
  });

注意观察这一部分内容:

 .state("page03.children", { 
      url: "/children",
      views: {
        'children': {
             templateUrl: "template/children.html"
        }
     }
})
这是实现父子路由的关键部分,”page03.children“ ,以”."作为分隔符,代表“page03"作为一个父界面, 
新的界面作为”page03"对应的一个子界面。同时,需要对page03.html文件做修改,page03.html文件内容如下:
//page03.html
<html> 
  <head>
    <title>page03</title>
    <meta charset="utf-8">      
  </head>   
 <body> 
    <div>
        <h1>hello, page03</h1>
        <a ui-sref="page03.children">children</a>  

        <div>
            chirdren content area
            <ui-view name="children"></ui-view>
        </div>
    </div>
</body> 
</html>
image.png

同时,在template目录下添加一个新html文件: children.html,作为 page03的子界面。

// children.html
<html> 
  <head>
    <title>children</title>
    <meta charset="utf-8">  
  </head>   
 <body> 
    <div>
        <h1>hello, children</h1>
    </div>
</body> 
</html>

查看界面效果:

page03.png
chirldre.png

观察浏览器地址栏变化,观察界面变化。

上一篇下一篇

猜你喜欢

热点阅读