美文网首页Angular2大叔精选Angular2
Angular 从0到1(九):查缺补漏大合集(下)

Angular 从0到1(九):查缺补漏大合集(下)

作者: 接灰的电子产品 | 来源:发表于2017-01-09 16:59 被阅读2573次

    第一节:初识Angular-CLI
    第二节:登录组件的构建
    第三节:建立一个待办事项应用
    第四节:进化!模块化你的应用
    第五节:多用户版本的待办事项应用
    第六节:使用第三方样式库及模块优化用
    第七节:给组件带来活力
    Rx--隐藏在Angular 2.x中利剑
    Redux你的Angular 2应用
    第八节:查缺补漏大合集(上)
    第九节:查缺补漏大合集(下)

    第九章:查缺补漏大合集(下)

    Angular2 动画再体验

    State和Transition

    我写文章的习惯是先试验再理论,所以我们接下来梳理下Angular2提供的动画技能。还是从最简单的例子开始,一个非常简单的模版:

    <div class="traffic-light"></div>
    

    同样非常简单的样式(其实就是画一个小黑块):

    .traffic-light{  
      width: 100px;  
      height: 100px;  
      background-color: black;
    }
    

    现在的效果就是这个样子,如图所示,一点都不酷啊,没关系,我们一点点来,越简单的越容易弄懂概念。

    小黑块变成小绿块

    这说明什么?我们的state的样式附着在div上了。为什么呢?因为 [@signal]="'go'" 定义了trigger的状态是go。但这一点也不酷是吗?是的,暂时是这样,还是那句话,不要急。
    接下来,我们再加一个状态 stop,在stop激活时我们要把小方块的背景色设为红色,那么我们需要把animations改成下面的样子:

    animations: [
        trigger('signal', [
          state('go', style({
            'background-color': 'green' 
          })),
          state('stop', style({
              'background-color':'red'
          }))
        ])
      ]
    
    

    同时我们需要给模板加两个按钮Go和Stop。现在的模版看起来是下面的样子

    <div
      [@signal]="signal"
      class="traffic-light">
    </div>
    <button (click)="onGo()">Go</button>
    <button (click)="onStop()">Stop</button>
    
    

    当然你看得到,我们点击按钮时需要处理对应的点击事件。在这里我们希望点击Go时,方块变绿,点击Stop时方块变红。如果要达成这个目的,我们需要一个叫signal的成员变量,在点击的处理函数中更改相应的状态。

    export class PlaygroundComponent {
    
      signal: string;
    
      constructor() { }
    
      onGo(){
        this.signal = 'go';
      }
      onStop(){
        this.signal = 'stop';
      }
    }
    
    

    现在打开浏览器,试验一下,我们会发现点击Go变绿,而点击Stop变红。但是还是没动起来啊,是的,这是因为我们还没加transition呢,我们只需把animations改写一下,你分别点Go和Stop就能看到动画效果了。为了让效果更明显一些,我们为两种状态指定一下高度。

    import { 
      Component, 
      OnDestroy,
      trigger,
      state,
      style,
      transition,
      animate
    } from '@angular/core';
    
    @Component({
      selector: 'app-playground',
      templateUrl: './playground.component.html',
      styleUrls: ['./playground.component.css'],
      animations: [
        trigger('signal', [
          state('void', style({
            'transform':'translateY(-100%)'
          })),
          state('go', style({
            'background-color': 'green', 
            'height':'100px'
          })),
          state('stop', style({
              'background-color':'red',
              'height':'50px'
          })),
          transition('void => *', animate(5000))
        ])
      ]
    })
    export class PlaygroundComponent {
      
      signal: string;
    
      constructor() { }
    
      onGo(){
        this.signal = 'go';
      }
      onStop(){
        this.signal = 'stop';
      }
    }
    

    那么 transition('* => *', animate(500)) 这句什么意思呢?前面那个 '* => *' 是一个状态迁移表达式,* 表示任意状态,所以这个表达式告诉我们,只要有状态的变化就会激发后面的动画效果。后面的就是告诉Angular做500毫秒的动画,这个动画默认是从一个状态过渡到另一个状态。现在大家打开浏览器体验一下,分别点击Go和Stop,会发现我们的小方块从一个正方形变成一个长方形,红色变成绿色的过程。体验完之后再来看这句话:动画其实就是由若干个状态组成,由transition定义状态过渡的步骤。

    有了形状和颜色变化的动画

    那么下面我们介绍一个void 状态(空状态),为什么会有void状态呢?其实刚刚我们也体验了,只不过没有定义这个void 状态而已。我们在组件中并没有给signal赋初始值,这就意味着一开始trigger的状态就是void。我们往往在实现进场或离场动画时需要这个void状态。void状态就是描述没有状态值时的状态。

    animations: [
        trigger('signal', [
          state('void', style({
            'transform':'translateY(-100%)'
          })),
          state('go', style({
            'background-color': 'green', 
            'height':'100px'
          })),
          state('stop', style({
              'background-color':'red',
              'height':'50px'
          })),
          transition('* => *', animate(500))
        ])
      ]
    
    

    上面代码定义了一个void状态,而且样式上有一个按Y轴做的-100%的位移,其实这就是一开始让小方块从场景外进入场景内,这样就是实现了一种进场动画,大家可以在浏览器中试验一下。

    用void状态实现的进场动画

    奇妙的animate函数

    上面的我们的实验中,你会发现transition中有个animate函数,可能你认为它就是指定一个动画的时间的函数。它的身手可不止那么简单呢,我们来仔细挖掘一下。
    首先呢,我们来对上面的代码做一个小改造,把animations数组改成下面的样子:

    animations: [
        trigger('signal', [
          state('void', style({
            'transform':'translateY(-100%)'
          })),
          state('go', style({
            'background-color': 'green', 
            'height':'100px'
          })),
          state('stop', style({
              'background-color':'red',
              'height':'50px'
          })),
          transition('* => *', animate('.5s 1s'))
        ])
      ]
    
    

    我们其实只对animate中的参数做了一点小改动,就是把animate(500) 改成animate('.5s 1s')。那么.5s表示动画过渡时间为0.5秒(其实和上面设置的500毫秒是一样的),1s表示动画延迟1秒后播放。现在我们打开浏览器,看看效果如何吧。

    当然还有更狠的大招,这个字符串表达式还可以变成 '.5s 1s ease-out',后面的这个ease-out是一种缓动函数,它是可以让动画效果更真实的一种方式。
    现实世界中物体照着一定节奏移动,并不是一开始就移动很快的,也不可能是一直匀速运动的。怎么理解呢?当皮球往下掉时,首先是越掉越快,撞到地上后回弹,最终才又碰触地板。而缓动函数可以使动画的过渡效果按照这样的真实场景抽象出的对应函数来进行绘制。ease-out只是众多的缓动函数的其中一种,我们当然可以指定其他函数。
    另外需要说明的一点是诸如ease-out只是真实函数的一个友好名称,我们当然可以直接指定背后的函数:cubic-bezier(0, 0, 0.58, 1) 。我们下个小例子不用这个ease-out,因为效果可能不是特别明显,我们找一个明显的,使用 cubic-bezier(0.175, 0.885, 0.32, 1.275) 。现在我们打开浏览器,你仔细观察一下是否看到了小方块回弹的效果

    animations: [
        trigger('signal', [
          state('void', style({
            'transform':'translateY(-100%)'
          })),
          state('go', style({
            'background-color': 'green', 
            'height':'100px'
          })),
          state('stop', style({
              'background-color':'red',
              'height':'50px'
          })),
          transition('* => *', animate('.5s 1s cubic-bezier(0.175, 0.885, 0.32, 1.275)'))
        ])
      ]
    
    
    加上了缓动函数的进场动画

    关于缓动函数的更多资料可以访问 http://easings.net/zh-cn 在这里可以看到各种函数的曲线和效果,以及cubic-bezier函数的各种参数

    关键帧实现的入场动画

    现在我们来分析一下代码,这个入场动画是5秒的时间,我们给出6个关键帧,也就是0s,1s,2s,3s,4s和5s这几个。对于每个关键帧,我们给出的样式都是放缩,而放缩的比例逐渐加大,而且是先快后慢,也就是说我们可以模拟出缓动函数的效果。

    如果我们不光做放缩,而且在style中还指定位置的话,这个动画就会出现边移动边变大的效果了。把入场动画改成下面的样子试试看吧。

    transition('void => *', animate(5000, keyframes([
            style({'transform': 'scale(0)', 'padding': '0px'}),
            style({'transform': 'scale(0.1)', 'padding': '50px'}),
            style({'transform': 'scale(0.5)', 'padding': '100px'}),
            style({'transform': 'scale(0.9)', 'padding': '120px'}),
            style({'transform': 'scale(0.95)', 'padding': '135px'}),
            style({'transform': 'scale(1)', 'padding': '140px'})
    ]))),
    
    加上位移的效果

    最后的结果可能还是不酷,但是这样的话利用关键帧我们如果结合好CSS样式,就会做出比较复杂的动画了。

    方便的管道--PIPE

    我们一直没有提到的一点就是管道,虽然我们的例子中没有用到,但其实这是Angular 2中提供非常方便的一个特性。这个特性可以让我们很快的将数据在界面上以我们想要的格式输出出来。还是拿例子说话,比如我们在页面上显示一个日期,先建立一个简单的模版:

    <p> Without Pipe: Today is {{ birthday }} </p>
    <p> With Pipe: Today is {{ birthday | date:"MM/dd/yy" }} </p>
    

    再来建立对应的组件文件:

    import { Component, OnDestroy } from '@angular/core';
    
    @Component({
      selector: 'app-playground',
      templateUrl: './playground.component.html',
      styleUrls: ['./playground.component.css']
    })
    export class PlaygroundComponent {
      birthday = new Date();
      constructor() { }
    
    }
    
    无管道和有管道的日期输出无管道和有管道的日期输出

    上面的例子可能还没太明显,我们 进一步改造一下模板:

    <p> Without Pipe: Today is {{ birthday }} </p>
    <p> With Pipe: Today is {{ birthday | date:"MM/dd/yy" }} </p>
    <p>The time is {{ birthday | date:'shortTime' }}</p>
    <p>The time is {{ birthday | date:'medium' }}</p>
    
    
    同一数据可以显示成不同样子同一数据可以显示成不同样子

    而且更牛的是多个Pipes可以串起来使用,比如说上图中最下面那个日期我们希望把Dec大写,就可以这样使用:

    <p>The time is {{ birthday | date:'medium' | uppercase }}</p>
    
    多个Pipe连用多个Pipe连用

    自定义一个Pipe

    那么自己写一个Pipe是怎样的体验呢?创建一个Pipe非常简单,我们来体会一下。首先创建一个 src/app/playground/trim-space.pipe.ts 的文件:

    import { Pipe, PipeTransform } from '@angular/core';
    @Pipe({
      name: 'trimSpace'
    })
    export class TrimSpacePipe implements PipeTransform {
      transform(value: any, args: any[]): any {
        return value.replace(/ /g, '');
      }
    }
    

    在Module文件中声明这个Pipe:declarations: [PlaygroundComponent, TrimSpacePipe] 以便于其他控件可以使用这个Pipe:

    import { NgModule } from '@angular/core';
    import { SharedModule } from '../shared/shared.module';
    import { PlaygroundRoutingModule } from './playground-routing.module';
    import { PlaygroundComponent }   from './playground.component';
    import { PlaygroundService } from './playground.service';
    import { TrimSpacePipe } from './trim-space.pipe';
    
    @NgModule({
        imports: [
            SharedModule,
            PlaygroundRoutingModule
        ],
        providers:[
            PlaygroundService
        ],
        declarations: [PlaygroundComponent, TrimSpacePipe]
    })
    export class PlaygroundModule { }
    

    然后在组件的模板文件中使用即可 {{ birthday | date:'medium' | trimSpace}}

    <p> Without Pipe: Today is {{ birthday }} </p>
    <p> With Pipe: Today is {{ birthday | date:"MM/dd/yy" }} </p>
    <p>The time is {{ birthday | date:'shortTime' }}</p>
    <p>The time is {{ birthday | date:'medium' | trimSpace}} with trim space pipe applied</p>
    <p>The time is {{ birthday | date:'medium' | uppercase }}</p>
    

    打开浏览器看一下效果,我们看到应用了trimSpace管道的日期的空格被移除了,如图所示:

    自定义一个移除空格的Pipe自定义一个移除空格的Pipe

    内建的Pipe

    Decimal Pipe

    DatePipe和UpperCase Pipe我们刚刚已经见识过了,现在我们看一看内建的其他Pipe。首先是用于数字格式化的DecimalPipe。DecimalPipe的参数是以 {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits} 的表达式形式体现的。其中:

    1. minIntegerDigits 是最小的整数位数,默认是1。
    2. minFractionDigits 表示最小的小数位数,默认是0。
    3. maxFractionDigits 表示最大的小数位数,默认是3。
    <p>pi (no formatting): {{pi}}</p>
    <p>pi (.5-5): {{pi | number:'.5-5'}}</p>
    <p>pi (2.10-10): {{pi | number:'2.10-10'}}</p>
    <p>pi (.3-3): {{pi | number:'.3-3'}}</p>
    

    如果我们在组件中定义 pi: number = 3.1415927; 的话,上面的数字会被格式化成下图的样子

    Decimal Pipe用于数字的格式化Decimal Pipe用于数字的格式化

    Currency Pipe

    顾名思义,这个Pipe是格式化货币的,这个Pipe的表达式形式是这样的: currency[:currencyCode[:symbolDisplay[:digitInfo]]],也就是说在currency管道后用分号分隔不同的属性设置:

    <p>A in USD: {{a | currency:'USD':true}}</p>
    <p>B in CNY: {{b | currency:'CNY':false:'4.2-2'}}</p>
    

    上面的代码中 USDCNY 表面货币代码,truefalse 表明是否使用该货币的默认符号,后面如果再有一个表达式就是规定货币的位数限制。这个限制的具体规则和上面Decimal Pipe的类似,如下图所示。

    Currecy Pipe用于格式化货币Currecy Pipe用于格式化货币

    Percent Pipe

    这个管道当然就是用来格式化百分数的,百分数的整数位和小数位的规则也和上面提到的Decimal Pipe和Currency Pipe一致。如果在组件中定义 myNum: number = 0.1415927; 下面的代码会输出成下图的样子:

    <p>myNum : {{myNum | percent}}</p>
    <p>myNum (3.2-2) : {{myNum | percent:'3.2-2'}}</p>
    
    
    Percent Pipe用来格式化百分数Percent Pipe用来格式化百分数

    Json Pipe

    这个管道个人感觉更适合在调试中使用,它可以把任何对象格式化成JSON格式输出。如果我们在组件中定义了一个对象:

    object: Object = {
      foo: 'bar', 
      baz: 'qux', 
      nested: {
        xyz: 3, 
        numbers: [1, 2, 3, 4, 5]
      }
    }; 
    

    那么下面的模板会输出下图的样子,在调试阶段,这个特性很好帮助你输出可读性很强的对象格式。当然如果你使用了现代化的IDE,这么使用的意义就不是很大了:

    <div>
      <p>Without JSON pipe:</p>
      <pre>{{object}}</pre>
      <p>With JSON pipe:</p>
      <pre>{{object | json}}</pre>
    </div>
    
    
    Json Pipe用于以Json形式格式化对象Json Pipe用于以Json形式格式化对象

    指令——Directive

    另一个我们一直没有提到的重要概念就是指令了,但这个虽然我们没提到,却已经用过了。比如 *ngFor*ngIf 等,这些都叫做结构性指令,而像 *ngModel 等属于属性型指令。
    Angular 2中的指令分成三种:结构型(Structural)指令和属性型(Attribute)指令,还有一种是什么呢?就是Component,组件本身就是一个带模板的指令。
    结构型指令可以通过添加、删除DOM元素来更改DOM树的布局,比如我们前面使用 *ngFor在todo-list的模板中添加了多个todo-item。而属性型指令可以改变一个DOM元素的外观或行为,比如我们利用 *ngModel 进行双向绑定,改变了该组件的默认行为(我们在组件中改变某个变量值,这种改变会直接反应到组件上,这并不是组件自身定义的行为,而是我们通过 *ngModel 来改变的)。
    Angular 2中给出的内建结构型指令如下表所示:

    名称 用法 说明
    ngIf <div*ngIf="canShow"> 基于canShow表达式的值移除或重新创建部分DOM树。
    ngFor <li *ngFor="let todo of todos"> 把li元素及其内容转化成一个模板,并用它来为列表中的每个条目初始化视图。
    ngSwitch, ngSwitchCase, ngSwitchDefault <div [ngSwitch]="someCondition"></div> 基于someCondition的当前值,从内嵌模板中选取一个,有条件的切换div的内容。

    自定义一个指令也很简单,我们动手做一个。这个指令非常简单就是使任何控件加上这个指令后,其点击动作都会在console中输出 “I am clicked”。由于我们要监视其宿主的click事件,所以我们引入了 HostListener,在onClick方法上用 @HostListen(‘click’) ,表明在检测到宿主发生click事件时调用这个方法。

    import {
      Directive,
      HostListener
    } from '@angular/core';
    
    @Directive({
        selector: "[log-on-click]",
    })
    export class LogOnClickDirective {
    
        constructor() {}
        @HostListener('click')
        onClick() { console.log('I am clicked!'); }
    }
    

    在模板中简单写一句就可以看效果了

    <button log-on-click>Click Me</button>

    自定义指令使得点击按钮会log一条消息自定义指令使得点击按钮会log一条消息

    代码: https://github.com/wpcfan/awesome-tutorials/tree/master/angular2/ng2-tut

    慕课网 Angular 视频课上线: http://coding.imooc.com/class/123.html?mc_marking=1fdb7649e8a8143e8b81e221f9621c4a&mc_channel=banner

    纸书出版了,比网上内容丰富充实了,欢迎大家订购!
    京东链接:https://item.m.jd.com/product/12059091.html?from=singlemessage&isappinstalled=0

    Angular从零到一Angular从零到一

    第一节:初识Angular-CLI
    第二节:登录组件的构建
    第三节:建立一个待办事项应用
    第四节:进化!模块化你的应用
    第五节:多用户版本的待办事项应用
    第六节:使用第三方样式库及模块优化用
    第七节:给组件带来活力
    Rx--隐藏在Angular 2.x中利剑
    Redux你的Angular 2应用
    第八节:查缺补漏大合集(上)
    第九节:查缺补漏大合集(下)

    相关文章

      网友评论

      • 0c45406e8da8:看到你的文章,觉得写得很不错,也很有分享精神。我们侠课岛正好在找远程录制课程视频或图文教程的朋友,我们会给到课程的需求大纲,每一节课程需要你来详细展开写一些代码举例和讲解清楚,对经验积累和创新能力有一定的要求。有兴趣联系我。微信:zhimadt
      • klower:Hi,大神 能写一篇关于 怎么使用bootstrap的文章吗 我是angular初学者 谢谢了
      • 另一种灿烂生活09:学angular,可以从2开始学吗?
      • _信仰zmh:那个动画的效果的图片,是张gif的图片还是video呢?前辈
        在简书里怎么写
        接灰的电子产品:gif啊,markdown中怎么引用图片就怎么做了
      • Trinityuan:请问这本书讲的是NG2还是4呀?
        Trinityuan: @接灰的电子产品 好的,谢谢。 正需要一本引导书籍。
        接灰的电子产品:ng2,但ng4差别不大,动画这个需要改一下,但改的也不多
      • 97c8870576cc:期待更多更新!感谢
        接灰的电子产品:@老子牛逼 顶一下

      本文标题:Angular 从0到1(九):查缺补漏大合集(下)

      本文链接:https://www.haomeiwen.com/subject/ytshbttx.html