zoukankan      html  css  js  c++  java
  • 走进AngularJs(一)angular基本概念的认识与实战

    一、前言

      前端技术的发展是如此之快,各种优秀技术、优秀框架的出现简直让人目不暇接,作为一名业界新秀,紧跟时代潮流,学习掌握新知识自然是不敢怠慢。当听到AngularJs这个名字并知道是google在维护它时,便一直在关注,看到其在国外已经十分火热,可是国内的使用情况却有不小的差距,参考文献/网络文章也很匮乏。朝思暮想良久,决定深入学习angular,并写系列博客,一方面作为自己学习路程上的记录,另一方面也给有兴趣的同学一些参考。

      首先我自己是一名学习者,会以学习者的角度来整理我的行文思路,故该系列博客也不能叫做教程,只能是些探索,有理解或是技术上的错误还请大家指出。其次我特别喜欢编写小例子来把一件事情说明白,故在文中会尽可能多的用示例加代码讲解,我相信这会是一钟比较好的方式。最后,我深知在现有条件下对于angular的学习会困难重重,不过我更相信坚持的力量,所以谨以此文作为日后学习的动力,让我们一起来走进angular的世界吧~

    二、AngularJs是什么

      这个定义一定要定准了,AngularJs(后面就简称ng了)是一个用于设计动态web应用的结构框架。首先,它是一个框架,不是类库,是像backbone一样提供一整套方案用于设计web应用。它不仅仅是一个javascript框架,因为它的核心其实是对HTML标签的增强,有图有真相,请看官网描述:

     

      何为HTML标签增强?其实就是使你能够用标签完成一部分页面逻辑,具体方式就是通过自定义标签、自定义属性等,这些HTML原生没有的标签/属性在ng中有一个名字:指令(directive)。后面会详细介绍。那么,什么又是动态web应用呢?与传统web系统相区别,web应用能为用户提供丰富的操作,能够随用户操作不断更新视图而不进行url跳转。ng官方也声明它更适用于开发CRUD应用,即数据操作比较多的应用,而非是游戏或图像处理类应用。

      为了实现这些,ng引入了一些非常棒的特性,包括模板机制、数据绑定、模块、指令、依赖注入、路由。通过数据与模板的绑定,能够让我们摆脱繁琐的DOM操作,而将注意力集中在业务逻辑上。这些我将在以后的学习中一一研究。

      另外一个疑问,ng是MVC框架吗?还是MVVM框架?官网有提到ng的设计采用了MVC的基本思想,而又不完全是MVC,因为在书写代码时我们确实是在用ng-controller这个指令(起码从名字上看,是MVC吧),但这个controller处理的业务基本上都是与view进行交互,这么看来又很接近MVVM。让我们把目光移到官网那个非醒目的title上:“AngularJS — Superheroic JavaScript MVW Framework”。

      好吧,MVW。W—whatever。随便是MV什么好了,所以也有人写为了MV*。其实纠结这个也真没必要,等今后对整个框架熟悉了,其中结构自然了然于心。

    三、开始运行angular

      有了一个大概的朦胧的了解就够了,我相信很多概念在使用的过程中会慢慢清晰。我迫不及待的想要让angular运行起来了。动手~

      首先从官网http://angularjs.org/下载angular.js,引入你的页面中,然后我们使用最简单的手工启动方式,直接调用bootstrap方法。所有的代码如下:

    <!DOCTYPE html>
    <html >
    <head>
    <meta charset="utf-8" />  
    <title>运行ng</title>
    <script type="text/javascript" src="../angular.js"></script>
    </head>
    <body>
    <div>
        1+1={{1+1}}
    </div>   
    <script>
    angular.bootstrap(document,[]);
    </script>
    </body>
    </html>

      只有一行代码。调用bootstrap方法传入作用域和初始化的模块数组(此处为空)。是不是很简单。你很快会看到一处比较特别的,就是这里:

    <div>
        1+1={{1+1}}
    </div>

      如果你使用过其他模板库,应该对这种写法不陌生了,{{}}双大括号,这是ng的模板中用于书写表达式的标记,ng成功运行起来后,{{}}内的表达式会生效,即页面会显示如下:

     

      为了不让你把ng看的这么简单,我必须告诉你一般是不这么启动的,来看稍微修改以后的代码:

    <!DOCTYPE html>
    <html ng-app="MyApp">
    <head>
    <meta charset="utf-8" />  
    <title>运行ng</title>
    <script type="text/javascript" src="../angular.js"></script>
    </head>
    <body>
    <div>
        1+1={{1+1}}
    </div>
       
    <script type="text/javascript" charset="utf-8">
    var app = angular.module('MyApp', [], function(){console.log('started')});
    </script>
    </body>
    </html>

      在<html>标签上多了一个属性ng-app=”MyApp”,它的作用就是用来指定ng的作用域是在<html>标签以内部分。在js中,我们调用angular对象的module方法来声明一个模块,模块的名字和ng-app的值对应。关于如何声明、使用模块我们在后面会讲。现在我们只要明白用这种方式可以优雅的让ng运行起来就可以了。

    四、模板与数据的绑定

      首先需要明确一下模板的概念。在我还不知道有模板这个东西的时候,曾经用js拼接出很长的HTML字符串,然后append到页面中,这种方式想想真是又土又笨。后来又看到可以把HTML代码包裹在一个<script>标签中当作模板,然后按需要取来使用。在ng中,模板十分简单,它就是我们页面上的HTML代码,不需要附加任何额外的东西。在模板中可以使用各种指令来增强它的功能,这些指令可以让你把模板和数据巧妙的绑定起来。

      绑定这个东西可是ng中的大功臣了。在我们使用jQuery的时候,代码中会大量充斥类似这样的语句:var v = $(‘#id’).val();$(‘#id’).html(str);即频繁的DOM操作(读取和写入),其实我们的最终目的并不是要操作DOM,而是要实现业务逻辑。ng的绑定将让你摆脱DOM操作,只要模板与数据通过声明进行了绑定,两者将随时保持同步,最新的数据会实时显示在页面中,页面中用户修改的数据也会实时被记录在数据模型中。

      我构思了一个小例子,本篇文章的示例将围绕这个小例子来进行。我猜你已经厌倦了登录模块或者是购物车示例,我们来点新颖的。我已化身为一名教师,我要用一个web应用来为学生出一份在线试题。首先从一道题开始吧~

    <!DOCTYPE html>
    <html ng-app="MyApp">
    <head>
    <meta charset="utf-8" />  
    <title>模板数据绑定</title>
    <script type="text/javascript" src="../angular.js"></script>
    </head>
    <body>
    <div ng-controller="testC">
        <h1>{{newtitle}}</h1>
        题目:<input type="text" ng-model="name" /><br />
        分数:<input type="text" ng-model="fraction" /><br />
        <hr>
        <h1>{{previewtitle}}</h1>
        <b>{{name}}</b>({{fraction}}分)
    </div>
    <script type="text/javascript" charset="utf-8">
    var app = angular.module('MyApp', [], function(){console.log('started')});
    var testC = function($scope){
        $scope.newtitle = '新建试题';
        $scope.previewtitle = '预览试题';
        $scope.name = $scope.fraction = '';
    }
    </script>
    </body>
    </html>

      页面上有分别表示题目和分数的两个输入框,下面有一个实时预览区域,用来展示题目的最终输出效果。ng-controller=”textC”用来声明一个需要和数据进行绑定的模板区域,它的作用域就是这个div之内的东西,并起名为textC,js代码中定义了一个名为textC的函数与它对应,我们将在这个函数内完成绑定。函数传入一个参数$scope,表示这个作用范围。我们分别为作用范围内的newtitle、previewtitle、name、fraction四个变量赋值。此时<h1>标签内的{{}}便能得到相应的值了。

      通过{{}}只能完成数据向模板的单向绑定。要想进行双向绑定,我们需要用到ng-modle这个指令,我们使用它分别为题目和分数进行了双向绑定,这样当输入框内的值发生变化时,函数中的变量也会跟随变化,它的变化会实时反馈在下方的预览区域中,因为预览区域中也有一个name和fraction的绑定。

      试试在下面的输入框中进行编辑吧~

      我们并未进行任何DOM操作,框架自动完成了DOM的取值和赋值。在后面我将继续完善这个例子,并引入更多的新概念。

    五、模板中的一些控制方式

      我们在使用其他模板库时,一般都会有模板的循环输出、分支输出、逻辑判断等类似的控制。ng模板中都可以进行哪些控制呢?来一块探索之。

        1.循环输出

      继续上面的例子。试题光有题目和分数还不够,我想要出一道选择题,可以自己添加若干选项并编辑选项内容,代码该怎么写呢?先上代码:

      HTML部分:

    <div ng-controller="testC">
        <h1>{{question.newtitle}}</h1>
        题目:<input type="text" ng-model="question.name" /><br />
        分数:<input type="text" number ng-model="question.fraction" /><br />
        选项:<button ng-click="addOption()">增加选项</button><br />
        <ul>
            <li ng-repeat="o in question.options">
                <b>{{$index+1}}.</b>
                <input type="text" ng-model="o.content" value="o.content" />
                <a href="javascript:void(0);" ng-click="delOption($index)">删除</a>
            </li>
        </ul>
        <hr>
        <div >
            <h1>{{question.previewtitle}}</h1>
            <b>{{question.name}}</b>({{question.fraction}}分)
            <ul>
                <li ng-repeat="o in question.options">
                    <b>{{$index+1}}.</b>
                    <input type="radio" name="optcheck" />
                    {{o.content}}
                </li>
            </ul>
        </div>
    </div>

      js部分:

    var app = angular.module('MyApp', [], function(){console.log('started')});
    var questionModel = {
        newtitle : '新建试题',
        previewtitle : '预览试题',
        name : '',
        fraction : '',
        options : []
    };
    app.controller('testC',function($scope){
        $scope.question = questionModel;
        $scope.addOption = function(){
            var o = {content:''};
            $scope.question.options.push(o);
        };
        $scope.delOption = function(index){
            $scope.question.options.splice(index,1);
        };
    });

      请注意我的js代码有了一点变化,在数据绑定时没有直接写成字符串,而是将所有的数据写成了一个questionModel对象,这只是一个普遍的js对象,这样能够使我们的数据看起来像是“model”的样子,毕竟我们是MV*嘛。那么在模板中,我们使用name的地方也改成question.name,其他的值也同理。另外在controller部分,我没有用var textC = function(){}这样的写法,而是改成了app.controller(‘textC’,function(){}),这样明确指定了这个controller属于MyApp这个模块,我们的MV*代码看起来更专业、更一体了。

      在HTML部分我分别在新建区域和预览区域添加了一个列表,用来放置各选项。并添加了一个“增加选项”按钮。在<li>标签上使用了ng-repeat指令来进行循环输出,<li>标签将会根据$scopt中的options数组长度被复制多份。在循环范围内,可以使用$index获得当前循环的索引,可以认为这是一个公共变量,直接使用。同时,循环输出的每个<input>元素也使用ng-model与选项的内容进行了双向绑定。另外还输出一个删除链接,点击的时候调用$scope中的delOption方法。

      这样绑定好之后,模版中的列表与数据模型中的options数组便保持了同步,我们在页面上点击增加选项,数组中便会增加一个元素,数组中的每个元素也会实时反馈在模板中。所以在js代码中,我们只须操作options数组就够了,压根不需要在页面上append或removed节点。

      你可以在下面点击增加选项和删除试试~

      2.单个节点的控制

      在上面的例子中,你是不是发现了,我在处理按钮的点击时,使用了叫做ng-click的指令,为什么不直接用onclick呢?是因为ng根据自己的需要进行了封装。我们把addOption这个函数定义在了controller范围之内,用我们常规的onclick已经无法访问到。换言之,我们页面上的作用域,ng已经帮我们都规划好了,我们只需按照它提供的方式来使用就够了。

      通过onclick我们可以联想到,HTML节点还有好多其他属性,如class、style、href、checked、disabled等等,ng对这些都一一进行了封装,更厉害的是,除此之外ng还额外提供了许多更加详细的控制节点的指令。这些指令我以后会详细研究,在这里,我们先拿个其中一个应用到我们的例子中,看看效果先。

      我马上变回老师身份,哗~

      我新建试题的时候,不要局限于单选题,我想要在单选题和多选题之间能够切换,同时下方的预览区域内,选择框也进行单选框和多选框的切换。上代码:

      HTML部分:

    <div ng-controller="testC">
        <h1>{{question.newtitle}}</h1>
        题目:<input type="text" ng-model="question.name" /><br />
        分数:<input type="text" ng-model="question.fraction" /><br />
        类型:<select ng-model="question.type"><option value="1" selected>单选</option><option value="2">多选</option></select><br />
        选项:<button ng-click="addOption()">增加选项</button><br />
        <ul>
            <li ng-repeat="o in question.options">
                <b>{{$index+1}}.</b>
                <input type="text" ng-model="o.content" value="o.content" />
                <a href="javascript:void(0);" ng-click="delOption($index)">删除</a>
            </li>
        </ul>
        <hr>
        <div preview-panel>
            <h1>{{question.previewtitle}}</h1>
            <b>{{question.name}}</b>({{question.fraction}}分)
            <ul>
                <li ng-repeat="o in question.options">
                    <b>{{$index+1}}.</b>
                    <input type="radio" name="optcheck" ng-show="question.type==1" />
                    <input type="checkbox" ng-show="question.type==2" />
                    {{o.content}}
                </li>
            </ul>
        </div>
    </div>

      Js代码中,我只是在questionModel中新增了一项type:1,表示题的类型,1为单选,2为多选。并给默认值为1.

    var questionModel = {
        newtitle : '新建试题',
        previewtitle : '预览试题',
        name : '',
        fraction : '',
        type : '1',
        options : []
    };

      在HTML中,我新增了一个下拉框,并与question.type建立双向绑定。需要关注的是下面的预览区域。我又添加了一个checkbox控件来为多选题提供选择框。显然,radio与checkbox不能同时存在,所以我用ng-show这个指令来控制它们的显隐,ng-show接收boolean类型的值以及计算结果为boolean类型的表达式。请注意,ng-show以及其他所有指令的值不是简单的字符串(尽管看上去是那样),而是字符串表达式,拥有计算能力,我现在的理解是它将来会在框架的某个地方放进eval()或是类似的函数执行。{{}}里的内容也是一样。

      所以在这里我给radio的ng-show赋值为question.type==1,checkbox的ng-show赋值为question.type==2。当试题是单选题时,radio的ng-show便能得到值true,从而显示出来。在下面看一下效果:

      其他的节点控制指令也可以类似这样使用,思想就是根据你的业务逻辑,赋予它们一定的表达式。其他的控制还有事件绑定、表单控件等等,篇幅的限制在这里就不讲了,以后专门开一篇介绍。

      3.过滤器(filter)

      所谓过滤器是指对输出的内容进行格式化,如格式化为美元、日期等。框架自己提供一些过滤器,如排序、字符串内容筛选。我们也可以自定义过滤器。

      过滤器在{{}}中使用,表达式后用|隔开使用。拿日期过滤器举例,方式如下:

    $scope.nowTime = new Date().valueOf();
    {{nowTime | date : 'yyyy-MM-dd HH:mm:ss'}}

      便会输出格式化的日期。是不是很方便呢。

      接下来实战一下,哗~

      接上一步的例子,我想要在预览区域中的题目前面显示题型,像[单选题]这样。我们的questionModel中,type的值是1和2,所以我们要做的就是通过过滤器,把1和2显示为单选题和多选题。Go~

      在js中定义一个名为typeFilter的过滤器:

    app.filter('typeFilter',function(){
        var f = function(input){
            return input == '1' ? '单选题' : '多选题';
        }
        return f;
    });

      filter函数如何使用以及执行细节不是本篇的讨论内容,所以现在只要明白这样可以定义一个过滤器就可以了。结构姑且认为是固定写法,代码不难看懂。

    定义后这个filter后我们便可以在模板中使用了:

    <b>[{{question.type | typeFilter}}]{{question.name}}</b>({{question.fraction}}分)

      在题目的前面显示题型,并使用typeFilter,效果如下:

     

      单调的“1”已经华丽转身变为了“单选题”。这只是一个简单的过滤器。你可以发挥想象力编写更强大的过滤器。

    六、指令(directive)

      前面已经提到很多次指令了,现在来正式介绍一下它。指令是ng为HTML补充的语法扩展,用于增强HTML的表现力。像我们之前使用的ng-controller、ng-model等都属于指令。你也可以自定义指令。ng内部包含了一个强大的DOM解析引擎,所以这些新的标签或是标签属性可以像使用原生HTML那样很好的工作。

      听起来很牛的样子,那我们来试试自己定义一个指令吧。注意,我要变形了~

      我是教师,在新建试题输入分数的时候应该只能输入数字才对,输入其他内容是不合法的,而且我希望这个分数是1~10之间的数字。能否只在输入框上加一个属性就完成这个验证呢?就像使用HTML5新增的required一样。

      我们定义一个叫做fractionNum的指令如下:

    app.directive('fractionNum',function(){
        return {
            link : function(scope, elements, attrs, controller){
                elements[0].onkeyup = function(){
                    if(isNaN(this.value) || this.value<1 || this.value>10){
                        this.style.borderColor = 'red';
                    }
                    else{
                        this.style.borderColor = '';
                    }
                };
            }
        };
    });

      哇,代码好多层级呀,不要慌张,稳住阵脚!其实最后就是返回了带有link字段的对象,link的值是一个函数,用来定义指令的行为。从传入的参数中可以获取到当前元素,我们便可以拿当前元素开刀了。我在此处监听当前元素的keyup事件,获取元素的值,如果不是1~10之间的数字,则把输入框的边框颜色变为红色。这下这个指令就可以工作了。

      至于传入的四个参数到底都有什么玄机,我暂时还未研究,也不是本篇的重点,本篇只是做一个概览,先把ng拉出来溜溜的意思。现在姑且可以认为,一个指令的固定写法大概就是这个结构。

      定义好的指令就可以在模板中使用了,使用方法如下:

    分数:<input type="text" ng-model="question.fraction" fraction-num /><br />

      把它加在了分数输入框上,此处要特别小心一个写法,我定义的时候名字是fractionNum,用在模板中需要写成fraction-num,就是因为名字中含有大写字母的原因,感觉上跟使用css属性名称有点像。如果定义的时候没有大写字母,就不必担心这一点了。

      看效果,现在你可以去下面蹂躏那个分数输入框去了~

    七、依赖注入

      通过依赖注入,ng想要推崇一种声明式的开发方式,即当我们需要使用某一模块或服务时,不需要关心此模块内部如何实现,只需声明一下就可以使用了。在多处使用只需进行多次声明,大大提高可复用性。

      比如我们的controller,在定义的时候用到一个$scope参数。

    app.controller('testC',function($scope){});

      如果我们在此处还需操作其他的东西,比如与浏览器地址栏进行交互。我们只需再多添一个参数$location进去:

    app.controller('testC',function($scope,$location){});

      这样便可以通过$location来与地址栏进行交互了,我们仅仅是声明了一下,所需的其他代码,框架已经帮我们注入了。我们很明显的感觉到了这个函数已经不是常规意义上的javascript函数了,在常规的函数中,把形参换一个名字照样可以运行,但在此处若是把$scope换成别的名字,程序便不能运行了。因为这是已经定义好的服务名称。

      这便是依赖注入机制。顺理成章的推断,我们可以自己定义模块和服务,然后在需要的地方进行声明,由框架来替我们注入。

      对,我的小例子呢?我现在要想点需求来把依赖注入试验一下。我觉得试题全是文字太单调了,我希望题目中能含有图片/音视频,或者选项中可以含有图片/音视频。并且,我要更灵活一点,我要为试题提供若干套模板来选择,选择不同的模板可以新建不同样式的题,比如模板一为纯文字试题、模板二为题目中带图片/音视频的试题、模板三为选项中带图片/音视频的试题,等等。注意此处所说的模板跟ng的模板不是一个概念,是我自己试题的模板,不要混淆。这些模板应该是与试题相分离的,以后可以为其他题型例如填空题啊简答题啊同样使用。所以试题模板这个东西就需要做成服务了。

      不知道我表达清楚了没有,确实有点绕。来看下我们如何定义一个服务:

    app.factory('tpls',function(){
        return ['tpl1','tpl2','tpl3','tpl4'];
    });

      看上去相当简单,是因为我在这里仅仅是直接返回一个数组。在实际应用中,这里应该是需要向服务器发起一个请求,来获取到这些模板们。服务的定义方式有好几种,包括使用provider方法、使用factory方法,使用service方法。它们之间的区别暂且不关心。我们现在只要能创建一个服务出来就可以了。我使用了factory方法。一个需要注意的地方是,框架提供的服务名字都是由$开头的,所以我们自己定义的最好不要用$开头,防止发生命名冲突。

      定义好一个服务后,我们就可以在控制器中声明使用了,如下:

    app.controller('testC',function($scope,tpls){
        $scope.question = questionModel;
        $scope.nowTime = new Date().valueOf();
        $scope.templates = tpls; //赋值到$scope中
        $scope.addOption = function(){
            var o = {content:''};
            $scope.question.options.push(o);
        };
        $scope.delOption = function(index){
            $scope.question.options.splice(index,1);
        };
    });

      此时,若在模板中书写如下代码,我们便可以获取到服务tpls所提供的数据了:

    模板:<a href="javascript:void(0);" ng-repeat="t in templates">{{t}}&nbsp;&nbsp;</a><br />

      随着知识点的一点点增加,我的这个小例子也越来越丰满了,来看看完整版吧:

      查看完整代码请移步到runjs:http://runjs.cn/code/95wlwsfh

    八、总结一下

        乎~可以松一口气了。文章写到这里终于接近尾声了,不知我上面的陈述能否被大家理解。ng所包含的内容还是挺多的,以上的每个概念都可以再拆出几篇文章来讲解。所以我在这里只能是每一个都点到为止,不揪细节。这篇文章的思想也就是“先了解概念,例子能跑起来就行了”。在以后的文章中会随着我学习的深入进行探讨。

  • 相关阅读:
    day 66 ORM django 简介
    day 65 HTTP协议 Web框架的原理 服务器程序和应用程序
    jQuery的事件绑定和解绑 事件委托 轮播实现 jQuery的ajax jQuery补充
    background 超链接导航栏案例 定位
    继承性和层叠性 权重 盒模型 padding(内边距) border(边框) margin 标准文档流 块级元素和行内元素
    属性选择器 伪类选择器 伪元素选择器 浮动
    css的导入方式 基础选择器 高级选择器
    03-body标签中相关标签
    Java使用内存映射实现大文件的上传
    正则表达式
  • 原文地址:https://www.cnblogs.com/lvdabao/p/AngularJs.html
Copyright © 2011-2022 走看看