angularjs2学习教程

WhyAngular2

Angular1.x显然非常成功,那么,为什么要剧烈地转向Angular2


性能的限制

AngularJS当初是提供给设计人员用来快速构建HTML表单的一个内部工具。随着时间的推移,各种特性被加入进去以适应不同场景下的应用开发。然而由于最初的架构限制(比如绑定和模板机制),性能的提升已经非常困难了。

快速变化的WEB

在语言方面,ECMAScript6的标准已经完成,这意味着浏览器将很快支持例如模块、类、lambda表达式、 generator等新的特性,而这些特性将显著地改变JavaScript的开发体验。

在开发模式方面,Web组件也将很快实现。然而现有的框架,包括Angular1.xWEB组件的支持都不够好。

移动化

想想5年前......现在的计算模式已经发生了显著地变化,到处都是手机和平板。Angular1.x没有针对移动应用特别优化,并且缺少一些关键的特性,比如:缓存预编译的视图、触控支持等。

简单易用

说实话,Angular1.x太复杂了,学习曲线太陡峭了,这让人望而生畏。Angular团队希望在Angular2中将复杂性封装地更好一些,让暴露出来的概念和开发接口更简单

ES6工具链

要让Angular2应用跑起来不是件轻松的事,因为它用了太多还不被当前主流浏览器支持的技术。所以,我们需要一个工具链

Angular2面向未来的科技,要求浏览器支持ES6+,我们现在要尝试的话,需要加一些 垫片来抹平当前浏览器与ES6的差异:

  • systemjs - 通用模块加载器,支持AMD、CommonJS、ES6等各种格式的JS模块加载
  • es6-module-loader - ES6模块加载器,systemjs会自动加载这个模块
  • traceur - ES6转码器,将ES6代码转换为当前浏览器支持的ES5代码。systemjs会自动加载 这个模块。

初识Angular2

写一个Angular2HelloWorld应用相当简单,分三步走:

1. 引入Angular2预定义类型

1.  import{Component,View,bootstrap}from"angular2/angular2";

importES6的关键字,用来从模块中引入类型定义。在这里,我们从angular2模块库中引入了三个类型: Component类、View类和bootstrap函数。

2. 实现一个Angular2组件

实现一个Angular2组件也很简单,定义一个类,然后给这个类添加注解

1.  @Component({selector:"ez-app"})

2.  @View({template:"

Hello,Angular2

"})

3.  classEzApp{}

class也是ES6的关键字,用来定义一个类。@Component@View都是给类EzApp附加的元信息,被称为注解/Annotation

@Component最重要的作用是通过selector属性(值为CSS选择符),指定这个组件渲染到哪个DOM对象上。 @View最重要的作用是通过template属性,指定渲染的模板。

3. 渲染组件到DOM

将组件渲染到DOM上,需要使用自举/bootstrap函数:

1.  bootstrap(EzApp);

这个函数的作用就是通知Angular2框架将EzApp组件渲染到DOM树上。

简单吗?我知道你一定还有疑问,别着急,我们慢慢把缺失的知识点补上!

@Componentselector属性改为"ez-app",还应该改哪里可以获得和示例同样的结果?

注解/Annotation

你一定好奇@Component@View到底是怎么回事。看起来像其他语言(比如python装饰器,是这样吗?

ES6规范里没有装饰器。这其实利用了traceur的一个实验特性:注解。给一个类加注解,等同于设置这个类的annotations属性:

1.  //注解写法

2.  @Component({selector:"ez-app"})

3.  classEzApp{...}

等同于:

1.  classEzApp{...}

2.  EzApp.annotations =[newComponent({selector:"ez-app"})];

很显然,注解可以看做编译器(traceur)层面的语法糖,但和python装饰器不同, 注解在编译时仅仅被放在annotation里,编译器并不进行解释展开- 这个解释的工作是 Angular2完成的:

据称,注解的功能就是Angular2团队向traceur团队提出的,这不是traceur的默认选项,因此你看到,我们配置systemjs在使用traceur模块时打开注解

1.  System.config({

2.    map:{traceur:"lib/traceur"},

3.    traceurOptions:{annotations:true}

4.  });

修改示例代码中的EzApp组件,不用注解实现同样的功能。

小结

如果你了解一点Angular1.xbootstrap,可能隐约会感受到Angular2bootstrap的一些变化 - 我指的并非代码形式上的变化。

以组件为核心

Angular1.x中,bootstrap是围绕DOM元素展开的,无论你使用ng-app还是手动执行bootstrap() 函数,自举过程是建立在DOM之上的。

而在Angular2中,bootstrap是围绕组件开始的,你定义一个组件,然后启动它。如果没有一个组件,你甚至都没有办法使用Angular2

支持多种渲染引擎

组件而非DOM为核心,意味着Angular2在内核隔离了对DOM的依赖 - DOM仅仅作为一种可选的渲染引擎存在:

上面的图中,DOM Render已经实现,Server Render正在测试,iOS RenderAndroidRender 是可预料的特性,虽然我们看不到时间表。

这有点像React了。

最简单的模板

组件的View注解用来声明组件的外观,它最重要的属性就是template - 模板。 Angular2的模板是兼容HTML语法的,这意味着你可以使用任何标准的HTML标签编写组件模板。

所以,在最简单的情况下,一个Angular2组件的模板由标准的HTML元素构成,看起来就是一段HTML码流。Angular2原封不同地渲染这段模板:

有两种方法为组件指定渲染模板:

1. 内联模板

可以使用组件的View注解中的template属性直接指定内联模板

1.  @View({

2.      template:`

hello

3.                  

...
`

4.  })

ES6中,使用一对`符号就可以定义多行字符串,这使得编写内联的模板轻松多了。

2. 外部模板

也可以将模板写入一个单独的文件:

1. 

2. 

hello

3. 

...

然后在定义组件时,使用templateUrl引用外部模板

1.  @View({

2.      templateUrl :"ezcomp-tpl.html"

3.  })

在示例的模板中,增加一个输入文本框和一个按钮!

示例文件1.

        

   template - standard HTML

   

   

   

        

   

directives- 使用组件

Angular2中,一个组件的模板内除了可以使用标准的HTML元素,也可以使用自定义的组件!

这是相当重要的特性,意味着Angular2无偏差地对待标准的HTML元素和你自己定义的组件。这样,你可以建立自己的领域建模语言了,这使得渲染模板和视图模型的对齐更加容易,也使得模板的语义性更强:

声明要在模板中使用的组件

不过,在使用自定义组件之前,必需在组件的ViewAnnotation中通过directives属性声明这个组件:

1.  @View({

2.      directives :[EzComp],

3.      template:""

4.  })

你应该注意到了,directives属性的值是一个数组,这意味着,你需要在这里声明所有你需要在模板中使用的自定义组件。

修改示例代码:
1.
增加一个EzLogo组件
2.
EzCard组件的模板中使用这个组件

示例文件2

        

   template - component

   

   

   

        

   

{{model}} - 文本插值

在模板中使用可以{{表达式}}的方式绑定组件模型中的表达式,当表达式变化时, Angular2将自动更新对应的DOM对象:

上图的示例中,模板声明了h1的内容将绑定到组件实例的title变量。Angular2 框架将实时检测title的变化,并在其变化时自动更新DOM树中h1的内容。

修改模板,将新闻来源字段及内容移动到文章尾部

示例3

=================================================

      

    template - bindmodel

   

   

   

      

   

[property]- 绑定属性

在模板中,也可以使用一对中括号HTML元素或组件的属性绑定到组件模型的某个表达式当表达式的值变化时,对应的DOM对象将自动得到更新:

等价的,你也可以使用bind-前缀进行属性绑定:

1.  @View({template:``})

很容易理解,通过属性,应用相关的数据流入组件,并影响组件的外观与行为。

需要注意的是,属性的值总是被当做调用者模型中的表达式进行绑定,当表达式变化时,被调用的组件自动得到更新。如果希望将属性绑定到一个常量字符串,别忘了给字符串加引号,或者,去掉中括号:

1.  //错误,Angular2将找不到表达式 Hello,Angular2

2.  @View({template:``})

3.  //正确,Angular2识别出常量字符串表达式 'Hello,Angular2'

4.  @View({template:`

`})

5.  //正确,Angular2识别出常量字符串作为属性textContent的值

6.  @View({template:``})

修改示例代码,使EzApp组件的标题颜色每秒钟随机变化一次!

      

    template - bindpropery

   

   

   

      

   

   

(event)- 监听事件

在模板中为元素添加事件监听很简单,使用一对小括号包裹事件名称,并绑定到表达式即可:

上面的代码实例为DOM对象h1click事件添加监听函数onClick()

另一种等效的书写方法是在事件名称前加on-前缀:

1.  @View({template:`HELLO`})

修改示例代码,点击EzApp组件的h1标题时,自动变换名称!

      

    template - bindpropery

   

   

   

      

   

   

================================

#var - 局部变量

有时模板中的不同元素间可能需要互相调用,Angular2提供一种简单的语法将元素映射为局部变量:添加一个以#var-开始的属性,后续的部分表示变量名这个变量对应元素的实例。

在下面的代码示例中,我们为元素h1定义了一个局部变量v_h1,这个变量指向该元素对应的DOM对象,你可以在模板中的其他地方调用其方法和属性:

1.  @View({

2.      template:`

3.          hello

4.          test

5.      `

6.  })

如果在一个组件元素上定义局部变量,那么其对应的对象为组件的实例:

1.  @View({

2.      directives:[EzCalc],

3.      template:""

4.  })

在上面的示例中,模板内的局部变量c指向EzCalc的实例。

为示例代码的变色按钮添加事件监听,点击该按钮时,将EzApp组件的h1标题变为黑色背景,白色字体!

        

   template - local var

   

   

   

        

   

   

使用条件逻辑

有时我们需要模板的一部分内容在满足一定条件时才显示,比如右边示例中的EzReader组件,对于试用用户,它将在正文之上额外显示一个广告:

这是指令NgIf发挥作用的场景,它评估属性ngIf的值是否为真,来决定是否渲染 template元素的内容:

1.  @View({

2.      template:`

3.                  

4.                      

5.                  

6.                  

7.                  

...

` })

Angular2同时提供了两种语法糖,让NgIf写起来更简单,下面的两种书写方法和上面的正式语法是等效的:

1.  //使用template attribute

2.  <img src="ad.jpg"template="ng-iftiral==true">

3.  //使用*前缀

4.  <img src="ad.jpg"*ng-if="tiral==true">

看起来,显然*ng-if的书写方法更加有人情味儿,不过无论采用哪种书写方法,都将转换成上面的正式写法,再进行编译。

需要指出的是,NgIfAngular2预置的指令/Directive,所以在使用之前,需要:

  1. 从angular2库中引入NgIf类型定义
  2. 在组件的ViewAnnotation中通过属性directives声明对该指令的引用

修改示例代码中EzApp组件的模板,将trial属性设置为false,运行看有什么不同。

        

   Interpolation

   

   

        

        

   

使用分支逻辑

如果组件的模板需要根据某个表达式的不同取值展示不同的片段,可以使用NgSwitch系列指令来动态切分模板。比如右边示例中的广告组件EzPromotion,需要根据来访者性别的不同推送不同的广告:

NgSwitch包含一组指令,用来构造包含多分支的模板:

NgSwitch

NgSwitch指令可以应用在任何HTML元素上,它评估元素的ngSwitch属性值,并根据这个值决定应用哪些template的内容(可以同时显示多个分支):

1.  [ng-switch]="expression">...

NgSwitchWhen

NgSwitchWhen指令必须应用在NgSwitch指令的子template元素上,它通过属性ngSwitchWhen指定一个表达式,如果该表达式与父节点的NgSwitch指令指定的表达式值一致,那么显示这个template的内容:

1.  [ng-switch]="...">

2.      

3.       [ng-switch-when]="variable">...

4.      

5.      ng-switch-when="constant">...

6. 

NgSwitchDefault

NgSwitchDefault指令必须应用在NgSwitch指令的子template元素上,当没有NgSwitchWhen指令匹配时,NgSwitch将显示这个template的内容:

1.  [ng-switch]="...">

2.      ng-switch-default>...

3. 

需要注意的是,NgSwitch系列指令都是Angualr2的预置指令,在模板中使用之前,需要

  1. 从Angular2库中引入NgSwitch系列指令
  2. 通过ViewAnnotation的directives属性进行声明

思考下,NgSwitchNgIf的应用场景有什么区别?

        

   Interpolation

   

   

        

        

   

NgFor- 循环逻辑

如果希望利用一组可遍历的数据动态构造模板,那么应当使用NgFor指令。例如右边示例中的EzStar组件,用来展示演员的作品列表:

迭代

NgFor指令应用在template元素上,对ngForOf属性指定的数据集中的每一项实例化一个template的内容:

1.  ng-for [ng-for-of]="items">

2.      

  • ----------
  • 3. 

    如果items数据集有3条记录,那么会生成3li对象,就像这样:

    1. 

  • ----------
  • 2. 

  • ----------
  • 3. 

  • ----------
  • 不过这没多大用。

    使用数据项

    好在我们还可以为数据集的每一项声明一个局部变量,以便在模板内引用:

    1.  ng-for [ng-for-of]="items" #item>

    2.      

  • {{item}}
  • 3. 

    假如items数据集是一个数组:["China","India","Russia"],那么现在生成的结果就有点用了:

    1. 

  • China
  • 2. 

  • India
  • 3. 

  • Russia
  • 使用数据项索引

    有时还需要数据项在数据集中的索引,我们也可以为数据集的每一项的索引声明一个局部变量,以便在模板内引用:

    1.  ng-for [ng-for-of]="items" #item #i="index">

    2.      

  • [{{i+1}}]{{item}}
  • 3. 

    现在生成的结果更规矩了:

    1. 

  • [1] China
  • 2. 

  • [2] India
  • 3. 

  • [3] Russia
  • 语法糖

    NgIf类似,Angular2也为NgFor提供了两种语法糖:

    1.  //使用template attribute

    2.  <ANY template="ng-for #item of items;#i=index">...ANY>

    3.  //使用*前缀

    4.  <ANY *ng-for="#item ofitems;#i=index">...ANY>

    毫无疑问,应当尽量使用*ng-for的简便写法,这可以提高模板的可读性。

    修改示例代码,在每一部影片名称之前,显示其序号!

            

       NgFor

       

       

            

            

       

    styles -设置模板样式

    组件既然处于UI层,就应当好看些,好看是构造良好用户体验的一部分。Angular2组件模板基于HTML,那么显然,我们需要通过样式表/CSS来调整组件的外观

    和模板类似,我们有两种方法为组件设置CSS样式:

    1. 内联样式

    可以使用组件View注解styles属性来设置内联样式

    1.  @View({

    2.      styles:[`

    3.          h1{background:#4dba6c;color:#fff}    

    4.      `]

    5.  })

    2. 外部样式

    也可以把样式定义在单独的文件中:

    1.  /*ez-greeting.css*/

    2.  h1{background:#4dba6c;color:#fff}    

    然后使用View注解styleUrls属性来引入外部样式

    1.  @View({

    2.      styleUrls:["ez-greeting.css"]

    3.  })

    将示例组件的标题栏设置为文本居中!

            

       template- styles

       

       

       

            

       

    ShadowDom - 封装私有样式

    Angular2采用ShadowDom作为组件的渲染基础,这意味着组件被渲染到独立的 Shadow Tree上,这很好,可以实现DOM对象和样式的良好封装

    但问题是,除了Chrome之外的大多数的浏览器目前还不支持ShadowDom,因此,Angular2 提供了三种将模板渲染到DOM策略

    以下面的模板为例,我们看三种策略下的渲染结果差异:

    1.  @View{
    2.      template:"

    hello

    "
    ,
    3.      styles:["h1{color:red}"]
    4.  }

    全局仿真策略/EmulatedUnscopedShadowDomStrategy

    采用这个策略时,Angular2将模板直接插入DOM树,并将模板的样式原封不动地插入head头部。这意味着不同组件之间的样式可能冲突 : 在右边的示例代码中,你可以清楚的看到,EzApp组件的h1样式污染了其他的 h1元素,所有的h1都变成红色了。

    示例代码渲染后的DOM如下:

    这个策略不需要浏览器原生支持ShadowDom,是当前版本(alpha.28)的默认策略。

    作用域仿真策略/EmulatedScopedShadowDomStrategy

    采用这个策略时,Angular2将模板直接插入DOM树,对模板的样式重新定义CSS选择符插入head头部。由于样式进行了重命名,所以不同组件之间的样式不会冲突。

    示例代码在这个策略下的渲染结果是:

    这个策略也不需要浏览器原生支持ShadowDom

    原生策略/NativeShadowDomStrategy

    采用这个策略时,Angular2将在组件的宿主DOM对象上建立一个ShadowDom树,这颗树与主DOM树是隔离的,所以,这是实现Web组件的理想方案:

    如果浏览器原生支持ShadowDom,那么应当使用这个策略。

            

       template - shadowdom strategy

       

       

       

            

    我是H1,我在组件外

            

       

       

    设置ShadowDom策略

    Angular2中,ShadowDom的三种策略对应于三个类,这三个类继承ShadowDomStrategy

    Angular2的内核引用的是父类ShadowDomStrategy,我们只需要从三个继承类中选择一个与之绑定就可以实现不同策略的选择。下面的示例意味着选择NativeShadowDomStrategy

    1.  bind(ShadowDomStrategy).toFactory(function(doc){

    2.          returnnewNativeShadowDomStrategy(doc.head);

    3.      },[DOCUMENT_TOKEN])

    上面的表达式用来提交给Angular2注入器/DI,可以理解为:如果注入器需要实例化一个ShadowDomStrategy 实例,应当以DOCUMENT_TOKEN为参数,调用一个匿名的工厂函数,而这个工厂函数将返回一个 NativeShadowDomStrategy类的实例。

    ES6支持lambda表达式,因此我们可以写的简单一些:

    1.  bind(ShadowDomStrategy).toFactory(doc =>newNativeShadowDomStrategy(doc.head),[DOCUMENT_TOKEN])

    补丁包

    Angular2alpha.28版本的官方发行包中,没有包含默认ShadowDom策略之外的另两种策略实现模块,因此我们单独打了一个包render.dev.js

    如果你采用的是Chrome浏览器,将示例代码中的ShadowDom策略改为NativeShadowDomStrategy

            

       template - scoped&shadowdom strategy

       

       

       

       

            

    我是H1,我在组件外

            

       

       

    属性声明 - 暴露成员变量

    属性是组件暴露给外部世界的调用接口,调用者通过设置不同的属性值来定制 组件的行为与外观:

    Angular2中为组件增加属性接口非常简单,只需要在Component注解 properties属性中声明组件的成员变量就可以了:

    1.  //EzCard

    2.  @Component({

    3.      properties:["name","country"]

    4.  })

    上面的代码将组件的成员变量namecountry暴露为同名属性,这意味着在EzApp 的模板中,可以直接使用中括号语法来设置EzCard对象的属性:

    1.  //EzApp

    2.  @View({

    3.      directives :[EzCard],

    4.      template:"雷锋'"[country]="'中国'">"

    5.  })

    提醒:如果要在模板中使用自定义的指令(组件是一种指令),必须在View注解directives 属性中提前声明!

    修改示例代码中EzApp组件的模板,为EzCard调用添加namecountry属性!

            

       Property

       

       

       

            

       

       

    事件声明 - 暴露事件源

    属性相反,事件从组件的内部流出,用来通知外部世界发生了一些事情:

    Angular2中为组件增加事件接口也非常简单:定义一个事件源/EventEmitter然后通过Component注解events接口包括出来:

    1.  //EzCard
    2.  @Component({
    3.      events:["change"]
    4.  })
    5.  classEzCard{
    6.      constructor(){
    7.          this.change =newEventEmitter();
    8.      }
    9.  }

    上面的代码将组件EzCard的事件源change暴露为同名事件,这意味着在调用者 EzApp组件的模板中,可以直接使用小括号语法挂接事件监听函数:

    1.  //EzApp
    2.  @View({
    3.      template:"onChange()">"
    4.  })

    每次EzCard触发change事件时,EzApponChange()方法都将被调用。

            

       hello,angular2

       

       

       

            

       

       

    NgForm -表单指令

    NgForm指令为表单元素/form建立一个控件组对象,作为控件的容器NgControlName指令为则为宿主input元素建立一个控件对象,并将该控件加入到NgForm 指令建立的控件组中:

    局部变量

    通过使用#符号,我们创建了一个引用控件组对象(注意,不是form元素!)的局部变量f这个变量最大的作用是:它的value属性是一个简单的JSON对象,键对应于input元素的 ng-control属性,值对应于input元素的值:

    声明指令依赖

    NgForm指令和NgControlName指令都包含在预定义的数组变量formDirectives中,所以我们在 组件注解directives属性中直接声明formDirectives就可以在模板中直接使用这些指令了:

    1.  //angular2/ts/src/forms/directives.ts

    2.  exportconst formDirectives = CONST_EXPR([

    3.    NgControlName,

    4.    NgControlGroup,

    5.   

    6.    NgFormControl,

    7.    NgModel,

    8.    NgFormModel,

    9.    NgForm,

    10.  

    11.   NgSelectOption,

    12.   DefaultValueAccessor,

    13.   CheckboxControlValueAccessor,

    14.   SelectControlValueAccessor,

    15.  

    16.   NgRequiredValidator

    17. ]);

    为示例代码中的select元素也使用NgControlName指令,并在反馈中显示所选择的搜索类别!

            

       NgForm

       

       

       

            

       

    NgControlName - 命名控件指令

    如前所述,NgControlName指令必须作为NgFormNgFormModel的后代使用,因为这个指令需要将创建的控件对象添加到祖先(NgFormNgFormModel)所创建控件组中。

    NgControlName指令的选择符是[ng-control],这意味着你必须在一个HTML元素上定义ng-control属性,这个指令才会起作用。

    属性:ngControl

    NgControlName指令为宿主的DOM对象创建一个控件对象,并将这个对象以ngControl属性指定的名称绑定到DOM对象上:

    1.   #f="form">
    2.      type="text"ng-control="user">
    3.      type="password"ng-control="pass">
    4.  

    在上面的代码中,将创建两个Control对象,名称分别为userpass

    属性/方法:ngModel

    除了使用控件组获得输入值,NgControlName指令可以通过ngModel实现模型与表单的双向绑定:

    1.  
    2.      type="text"ng-control="user" [(ng-model)]="data.user">
    3.      type="password"ng-control="pass" [(ng-model)]="data.pass">
    4.  `

    ngModel即是NgControlName指令的属性,也是它的事件,所以下面的两种写法是等价的:

    1.  type="text"ng-control="user" [(ng-model)]="data.user">
    2.  //等价于
    3.  type="text"ng-control="user" [ng-model]="data.user" (ng-model)="data.user">

            

       NgControlName

       

       

       

            

       

     

    NgCongrolGroup - 命名控件组

    NgControlGroup指令的选择符是[ng-control-group],如果模板中的某个元素具有这个属性, Angular2框架将自动创建一个控件组对象,并将这个对象以指定的名称DOM对象绑定。

    控件组可以嵌套,方便我们在语义上区分不同性质的输入:

    NgControlName指令一样,NgControlGroup指令也必须作为NgFormNgFormModel后代使用,因为这个指令需要将创建的控件组对象添加到祖先(NgFormNgFormModel)所创建控件组中。

    在示例代码中再增加一个控件组,采集关于用户的兴趣爱好方面的信息!

            

       NgControlGroup

       

       

       

            

       

    NgFormControl- 绑定已有控件对象

    NgControlName指令不同,NgFormControl将已有的控件/Control对象绑定到DOM元素上。当需要对输入的值进行初始化时,可以使用NgFormControl指令。

    下面的代码中,使用NgFormControl指令将DOM元素绑定到组件EzComp的成员变量movie上,我们需要在构造函数中先创建这个Control对象:

    1.  @View({

    2.      //将输入元素绑定到已经创建的控件对象上

    3.      template:``

    4.  })

    5.  classEzComp{

    6.      constructor(){

    7.          //创建控件对象

    8.          this.movie =newControl("Matrix II -Reload");

    9.      }

    10. }

    控件/ControlAngular2中对表单输入元素的抽象,我们使用其value属性,就可以获得对应的输入元素的值。

    NgControlName指令的另一个区别是,NgFormControl不需要NgFormNgFormModel的祖先。

    为示例代码增加采集用户工作单位的输入项,并在调试信息中显示!

            

       NgFor

       

       

       

            

       

    NgFormModel- 绑定已有控件组

    NgFormModel指令类似于NgControlGroup指令,都是为控件提供容器。但区别在于, NgFormModel指令将已有的控件组绑定到DOM对象上:

    1.  @View({

    2.      template:`

    3.          

    4.          

    5.              

    6.              

    7.          

    `

    8.  })

    9.  classEzComp{

    10.     constructor(){

    11.         //创建控件组及控件对象

    12.         this.controls =newControlGroup({

    13.             name :newControl("Jason"),

    14.             age :newControl("45")

    15.         });

    16.     }

    17. }

    NgFormModel指令可以包含NgControlGroup指令,以便将不同性质的输入分组。

    将示例代码中的地址和电话输入元素,使用NgControlGroup指令放入单独的一个分组中。

            

       NgFor

       

       

       

            

       

    服务 - 封装可复用代码

    Angular2中,服务用来封装可复用的功能性代码。比如Http服务,封装了ajax 请求的细节,在不同的组件中,我们只需要调用Http服务的API接口就可以给组件增加 ajax请求的功能了:

    Angular2实现一个服务非常简单直接定义一个,然后,它就是服务了:

    1.  classEzAlgo{

    2.      add(a,b){return a+b;}

    3.      sub(a,b){return a-b;}

    4.  }

    上面的代码定义了一个相当弱智的算法服务EzAlgo,它有两个API - add()用来计算两个数的,sub()用来计算两个数的 。在示例中,组件EzApp依赖于这个服务:

    修改右边示例代码,实现一个减法计算器!

            

       Service

       

       

       

            

       

    注入 - appInjector

    在前一节的示例代码中,组件EzAlgo直接在构造函数中实例化了一个EzAlog对象,这造成了EzAppEzAlgo的强耦合,我们可以使用Angular2注入器/Injector进行解耦:

    注入器就像婚姻介绍所,男方在婚介所登记心仪的女性特点,约好见面地点,然后,坐等发货即可。比如上图:

    EzApp组件(男方)使用Component注解appInjector属性向Angular2框架(婚介所)声明其依赖于EzAlgo(登记心仪的女性特点),并在其构造函数的参数表中使用Inject注解声明注入点(约好见面地点),然后,剩下的事儿Angular2(婚介所)就办了:

    1.  @Component({

    2.      selector :"ez-app",

    3.      //声明依赖

    4.      appInjector :[EzAlgo]

    5.  })

    6.  @View(...)

    7.  classEzApp{

    8.      //Angular2框架负责注入对象

    9.      constructor(@Inject(EzAlgo) algo){

    10.         //已经获得EzAlgo实例了!

    11.     }

    12. }

    1. 不使用appInjector,在bootstrap函数中注入EzAlgo也是可行的,你试试看!
    2.
    思考一下,appInjector注入和bootstrap时注入,分别在什么情况下适用?

            

       appInjector

       

       

       

            

       

    注入一个复杂的服务

    EzAlgo相当简单,使用new或者使用Injector来获得一个实例看起来差别不大。那如果我们EzApp组件要使用Http服务呢?

    第一眼看上去,Http服务显然是一个真正有用的服务 - 因为看起来相当的复杂Http依赖于XHRBackendBaseRequestOptions,而XHRBackend又依赖于BrowserXHR

    我们可以有两种方法获得一个Http的实例,以便通过它获得网络访问的功能:

    1. 使用new进行实例化

    如果我们使用传统的new方式创建Http实例,看起来应该是这样:

    1.  var xhrbe =newXHRBackend(BrowserXHR);
    2.  var options =newBaseRequestOptions();
    3.  var http =newHttp(xhrbe,options);

    这没什么新奇的,就是繁琐一点。

    2. 使用注入器/Injector

    如果使用注入器,我们需要向Angular2框架声明这些层叠的依赖关系:

    1.  @Component({
    2.      appInjector :[
    3.        bind(BrowserXHR).toValue(BrowserXHR),
    4.        XHRBackend,
    5.        BaseRequestOptions,
    6.        Http
    7.      ]
    8.  })

    bind(BrowserXHR).toValue(BrowserXHR)的意思是,如果需要注入BrowserXHR类型的变量,注入这个类本身而非其实例。

    原理是这样,不过Angular2已经提供了一个定义好的变量httpInjectables,我们直接引用就可以了。

    Observable

    Observable是一种异步编程模式,与Promise不同,Observable等多的是从数据而非行为的角度来封装异步代码。

    Http服务的get()方法返回一个Observable对象,可以把Observable对象看做一个可订阅数据流,你通过subscribe()方法订阅以后,每当数据流中有新的数据,你都会得到通知。

            

       appInjector

       

       

       

            

       

    路由 - 初体验

    一个Web应用通常需要切割为多个不同的组件进行实现,然后根据用户的交互行为(通常是点击),动态地载入不同的组件,根据用户行为选择组件的过程就是路由

    由于Angular2是面向组件的,所以,Angular2的路由其实就是组件之间的路由

    点击示例页面的videomusic,体会Angular2路由的作用!

            

       Router

       

       

       

       

       

            

       

    路由 - 应用步骤

    Angular2中使用路由的功能,有三个步骤:

    1. 配置路由

    为组件注入Router对象并通过config()方法配置路由:

    1.  router.config([

    2.    {path:"/video", component:EzVideo},

    3.    {path:"/music", component:EzMusic}])

    上面的代码中,配置了两条路由:

    • 如果用户请求路径为/video,那么将在路由出口中激活组件EzVideo
    • 如果用户请求路径为/music,那么将在路由出口中激活组件EzMusic

    2. 设置路由出口

    路由出口是组件激活的地方,使用RouterOutlet指令在组件模板中声明出口:

    1.  @View({

    2.      directives:[RouterOutlet],

    3.      template:``

    4.  })

    5.  classEzApp{...}

    3. 执行路由

    使用Routernavigate()方法可以执行指定路径的路由,在示例中,当用户点击时我们调用这个方法进行路由:

    1.  @View({

    2.      template:`

    3.          video |

    4.          music

    5.          `

    6.  })

    我们向navigate()方法传入的路径,就是我们通过config()方法配置的路径。这样, Router就根据这个路径,找到匹配的组件,在RouterOutlet上进行激活。


    在真正开始使用路由的功能之前,我们需要做一些准备工作:


    1. 引用路由包

    Angular2的路由模块单独打包在router.dev.js,因此首先应该引用这个包:

    1.  type="text/javascript"src="lib/router.dev.js">

    2. 引入路由相关的预定义类型

    Angular2的路由模块名为angular2/router,我们从这里引入常用类型:

    1.  import{LocationStrategy,Router,RouterOutlet,routerInjectables}from"angular2/router";

    3. 声明路由相关依赖类型

    在启动组件时,我们需要声明路由相关的依赖类型(即变量:routerInjectables),以便根注入器可以解析对这些类型的请求:

    1.  bootstrap(EzApp,[routerInjectables]);

            

       Router - steps

       

       

       

       

       

            

       

    RouteConfig- 路由配置注解

    除了使用Routerconfig()方法进行路由配置,Angular2还提供了路由注解,允许我们注解的方式为组件添加路由:

    1.  @RouteConfig([

    2.      {path:"/video", component:EzVideo},

    3.      {path:"/music", component:EzMusic}

    4.  ])

    5.  classEzApp{...}

    RouteConfigAnnotation的构造函数参数与Routerconfig()方法参数一致,都是一个包含若干配置项的数组。

    事实上,它的确只是一个语法糖  Angular2bootstrap一个组件时,会检查组件是否存在 RouteConfig注解,如果存在,则利用这个信息调用Routerconfig()方法进行路由配置:

    不过这样写起来,感觉会流畅一些,声明式的氛围更强烈一些。

    修改示例代码:

    1. 增加一个组件EzSport
    2.
    EzApp的路由配置注释中添加指向EzSport的路由,路径为"/sport"
    3.
    EzApp的模板中添加指向路径"/sport"的路由入口

            

       RouteConfig

       

       

       

       

       

            

       

    RouterLink- 路由入口指令

    除了使用Routernavigate()方法切换路由,Angular2还提供了一个指令用来将一个DOM对象增强为路由入口

    1.  @View({

    2.      directives:[RouterOutlet,RouterLink]

    3.      template:`

    7.                  `

    8.  })   

    RouterLink指令为宿主DOM对象添加click事件监听,在触发时调用Router navigate()方法进行路由。

    路由项别名

    需要指出的是,RouterLink并不能直接使用路由项的路径,router-link属性的值是一个路由项的别名,我们需要在路由配置时通过as属性,为路由项设定别名:

    1.  @RouteConfig([

    2.      {path:"/video", component:EzVideo,as:"video"},

    3.      {path:"/music", component:EzMusic,as:"music"}

    4.  ])

    在组件EzApp的模板中增加一个button,点击这个button则切换到EzMusic组件

            

       RouterLink

       

       

       

       

       

            

       

    RouteRegistry- 路由注册表

    Router通过config()进行的路由配置,保存在路由注册表中;而Router通过 navigate()执行路由时,利用的就是路由注册表中的信息:

    在路由注册表中,保存有三种类型的路由表:

    匹配表/matchers

    匹配表是最基本的路由决策表,它使用正则表达式实现路径组件的匹配。

    1.  @RouteConfig([

    2.      {path:"/video", component:EzVideo},

    3.      {path:"/music", component:EzMusic}

    4.  ])

    对应生成的匹配表以正则表达式为键值,映射到汇算后的路由项

    1.  /^\/video$/g =>{path:"/video",...}

    2.  /^\/music$/g =>{path:"/music",...}

    重定向表/redirects

    如果在配置时指定了路径重定向,那么将在重定向表中生成路由项。

    1.  @RouteConfig([

    2.      {path:"/video", component:EzVideo},

    3.      {path:"/music", component:EzMusic},

    4.      {path:"/test", redirectTo:"/video"}

    5.  ])

    对应生成的重定向表以原始路径为键,映射到目标路径

    1.  /test => /video

    名称表/names

    如果在配置路由时,通过as属性为路由项设置了别名,那么将为每个别名建立一个路由项。我们已经知道,指令RouterLink需要使用名称表。

    1.  @RouteConfig([

    2.      {path:"/video", component:EzVideo,as:"video"},

    3.      {path:"/music", component:EzMusic,as:"music"}

    4.  ])

    对应生成的名称表以别名为键值,映射到汇算后的路由项

    1.  video =>{path:"/video",...},

    2.  music =>{path:"/music",...}

    修改示例代码:

    1. EzApp的路由配置中增加路径"/test"的路由项,该路径重定向为"/video"
    2.
    EzApp的模板中增加指向路径"/test"的路由入口,请使用navigate()方法

            

       RouteRegistry

       

       

       

       

       

            

       

     

    你可能感兴趣的:(angularjs2学习教程)