Angularjs2 学习笔记

时间:2022-12-11 19:49:55

angularjs2 学习笔记(一) 开发环境搭建

 

开发环境,vs2013 update 5,win7 x64,目前最新angular2版本为beta 17

第一步:安装node.js

安装node.js(https://nodejs.org/en/),为的是能够使用npm获得angular2.0的开发包

验证是否安装成功

cmd下输入 node -v

npm -v

第二步:在vs2013上安装typescript

安装完成后在项目中可以添加typescript项目了,并且在项目属性栏中会有typescript页

第三步:创建项目

可以将没用的都删除

第四步:添加package.json文件用于获取angularjs2包及依赖项

编辑文件,添加内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
 
   "name""angular2demo",
 
   "version""1.0.0",
 
   "license""ISC",
 
   "dependencies": {
 
     "angular2""2.0.0-beta.17",
 
     "systemjs""0.19.27",
 
     "es6-promise""^3.2.1",
 
     "es6-shim""^0.35.0",
 
     "reflect-metadata""0.1.2",
 
     "rxjs""5.0.0-beta.6",
 
     "zone.js""0.6.12"
 
   },
 
   "devDependencies": {
 
     "typescript""^1.7.5"
 
   }
 
}

  

对于所需要的包可通过npm查询最新版本,如

npm info angular2

下一步:配置package.config使用npm获取angular2相关包

在右键项目,选择power command 下的cmd下执行npm install

如果出现错误,需要安装“tsd”包的话,需要执行

npm install tsd -g

然后再进行安装

安装完成后

下一步:创建个人应用,注意在入口处需要添加browser.d.ts引用

新建一个app目录,并添加app.component.ts,main.ts

App.component.ts中添加angularjs模块

import {Component} from 'angular2/core';
 
@Component({
    selector: 'angular2-demo',
    template: '<h1>这是我的第一个应用</h1>'
})
export class AppComponent { }
此时编译会出现错误“connot find promise”等

这时需要在App.component.ts顶部添加引用

///<reference path="../node_modules/angular2/typings/browser.d.ts"/>

完整如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
///<reference path="../node_modules/angular2/typings/browser.d.ts"/>
 
  
 
import {Component} from 'angular2/core';
 
@Component({
 
    selector: 'angular2-demo',
 
    template: '<h1>这是我的第一个应用</h1> <h2>太神奇了</h2>'
 
})
 
export class AppComponent { }

  

 

在mian.ts中添加app模块

import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component'
 
bootstrap(AppComponent);
 

下一步:添加index.html页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<html>
<head>
    <title>Angular 2 Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  
    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
    <script src="node_modules/es6-shim/es6-shim.min.js"></script>
    <script src="node_modules/systemjs/dist/system-polyfills.js"></script>
  
    <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <script src="node_modules/rxjs/bundles/Rx.js"></script>
    <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
  
    <!-- 2. Configure SystemJS -->
    <script>
        System.config({
            packages: {
                app: {
                    format: 'register',
                    defaultExtension: 'js'
                }
            }
        });
        System.import('app/main').then(null, console.error.bind(console));
    </script>
  
</head>
  
<!-- 3. Display the application -->
<body>
    <angular2-demo>Loading...</angular2-demo>
</body>
  
</html>

  

 

下一步:更改typescript编译选项,修改项目文件

如果此时编译会出现错误,错误信息“connot find name promise"

此时需要修改项目属性,如下选择system,同时修改项目文件在PropertyGroup中的debug和release中同时添加

<TypeScriptEmitDecoratorMetadata>true</TypeScriptEmitDecoratorMetadata>

<TypeScriptModuleResolution>Node</TypeScriptModuleResolution>

<TypeScriptExperimentalDecorators>true</TypeScriptExperimentalDecorators>

 
最后,文件编译成功,在app下的main.ts和app.component.ts会自动编译成.js文件,终于在浏览器上看到了第一个angular2的页面了,这中间好多坑,实验了很多个版本,各个版本出现的错误都不是一样的,毕竟还是beta版,先拿这个版本学习了!

angularjs2 学习笔记(二) 组件

 

angular2 组件

首先了解angular2 组件的含义

angular2的应用就是一系列组件的集合

我们需要创建可复用的组件供多个组件重复使用

组件是嵌套的,实际应用中组件是相互嵌套使用的

组件中的数据调用可以使用inputs和outputs

一个组件可以是一种指令

一个组件可以包含前端表现及后端逻辑

一个组件可以是一个代码片段,能够独立运行

进一步理解指令

一个指令就是一个组件

一个指令可以装饰指令,用于改变DOM

一个指令可以是模板指令,可以改变element

一个实际例子

一辆车有门、方向盘、窗等等,假设车就是母组件,方向盘就是子组件

在angular2中组件是可以多级次的,那么他们间的交流就是数据交换,这种机制就是数据流

在当前angular2版本的数据绑定是单向的,父组件数据向子组件数据传递,再向子子组件数据传递

子组件可以使用event向父组件传递数据

所以我们可以说有两种方式数据绑定

我们能够通过判断ngmodel来实现这两种数据绑定方式

实例分析

myApp.ts

import {bootstrap} from 'angular2/platform/browser';

import {carComponent} from "./car.component";

bootstrap(carComponent);

这里首先说明angular入口,即我们需要启动的组件

这里看到首先要引入bootstrap,用于下面加载组件,其次引入我们需要的组件carComponent,也就是我们下面要定义的一个class

car.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
///<reference path="../node_modules/angular2/typings/browser.d.ts"/>
 
import {Component} from 'angular2/core';
 
import {door} from './door.component';
 
@Component({
 
    selector: "carTag",
 
    template: `
 
              <h3 class="titles">Mother Car Component</h3>
 
              <input type ="text" #textInput bind-value="text" />
 
              <button on-click="onCarChange(textInput.value)">Change</button>
 
               
 
              <div class="child-style">
 
                <door [textLevel1]="text" (changed)="onCarChange($event)">
 
                </door>
 
              </div>
 
    `,
 
    directives: [door],
 
    styles:[ `
 
          .titles {
 
                    color:#0099FF
 
             }
 
            .child-style {
 
                     background-attachment: initial; <br>            background-size: initial; <br>            background-origin: initial; <br>            background-clip: initial;   <br>            background-position: initial; <br>            background-repeat: initial;">            <br>              }
 
           `]
 
  
 
})
 
export class carComponent {
 
    text: string="输入文字";
 
    onCarChange(value) {
 
        this.text ="向子组件传递数据:"+value;
 
    }
 
}

  

door.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
///<reference path="../node_modules/angular2/typings/browser.d.ts"/>
 
import {Component, EventEmitter} from 'angular2/core';
 
 
@Component({
 
    selector: "door",
 
    template: `
 
<h2>Child Component</h2>
 
                <input type ="text" #textInput value="{{textLevel1}}">
 
                <button (click)="onDoorChange(textInput.value)">Change</button>
 
`,
 
    inputs: ['textLevel1'],
 
    outputs: ['changed']
 
})
 
export class door {
 
    textlevel: string;
 
    changed = new EventEmitter();
 
    onDoorChange(value) {
 
        this.textlevel ="向父组件传递数据:"+ value;
 
        this.changed.emit(value);
 
    }
}
 
}

  

Car.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!DOCTYPE html>
 
<html>
 
<head>
 
    <script>document.write('<base href="' + document.location + '" />');</script>
 
  
 
    <meta name="viewport" content="width=device-width, initial-scale=1">
 
    <!-- CSS file -->
 
     
 
    <link href="css/bootstrap.min.css" rel="stylesheet" />
 
    <!-- IE polyfills, keep the order please -->
 
    <script src="node_modules/es6-shim/es6-shim.min.js"></script>
 
    <script src="node_modules/systemjs/dist/system-polyfills.js"></script>
 
  
 
    <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
 
    <script src="node_modules/systemjs/dist/system.src.js"></script>
 
    <script src="node_modules/rxjs/bundles/Rx.js"></script>
 
    <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
 
  
 
    <script src="node_modules/typescript/lib/typescript.js"></script>
 
    <!-- Agular 2 Router -->
 
    <script src="node_modules/angular2/bundles/router.dev.js"></script>
 
    <!-- Config Agular 2 and Typescript -->
 
    <script>
 
    System.config({
 
        transpiler: 'typescript',
 
        typescriptOptions: { emitDecoratorMetadata: true },
 
        packages: {'app': {defaultExtension: 'ts'}}
 
    });
 
System.import('app/myApp')
 
    .then(null, console.error.bind(console));
 
    </script>
 
  
 
</head>
 
  
 
<!-- Run the application -->
 
<body>
 
    <carTag>Loading Sample...</carTag>
 
</body>
 
  
 
</html>

  

组件引用

第一行,目前版本的bug,所有自定义组件在当前版本下需要加入

下面引入Component,@Companent是typescript的一种注解语言,用于配置我们当前组件,具体里面有很多的参数,后面细说。

引入我们需要的组件(import)

组件配置

先看看selector,即标签,也就是我们在html中要使用的标签名称,类似于标准的html标签(如<div>,<h1>),我们知道一个component可以是一个指令,carComponent 就是一个director,通过定义selector来标识这个指令。

template:该组件的模板,可以理解为该组件的视图,在template中就可以使用刚刚定义的标签,也就是在selector中定义标签名称,方法就像使用标准html的标签一样。此处是一个内嵌的模板,使用一对“`”符号来标记模板中的内容。

在该模板中可以看到input标签中定义了一个局部变量,Angular2提供一种简单的语法将元素 映射为局部变量:添加一个以#var-开始的属性,后续的部分表示变量名, 这个变量对应元素的实例。在input的value处我们看到该值绑定了carComponent组件的一个属性,使用{{}}绑定,这是angular的一种插值绑定语法,另一种方式可使用使用一对中括号将HTML元素或组件的属性绑定到组件模型的某个表达式, 当表达式的值变化时,对应的DOM对象将自动得到更新如<input type ="text" #textInput [value]="text" />注意value后的text是carComponent的一个属性,而value是input对象的属性,还有一种表达方式你也可以使用bind-前缀进行属性绑定如<input type ="text" #textInput bind-value="text" />

该模板button中(click)代表着事件 ,使用一对小括号包裹事件名称,并绑定 到表达式,也可使用事件名称前加on-前缀如

<button on-click="onCarChange($event)">Change</button>
textInput即是前面在input中定义的局部变量代表着input的实例,onCarChange是carComponent中定义的方法,接收 一个参数,这里是为了触发text属性变化来通知子组件,当然也可以直接在input中进行定义,如

<input type ="text" #textInput bind-value="text" (change)="onCarChange(textInput.value)" />

效果是一样的。
该模板中使用了door标签,也就是我们在door组件中定义的标签,其中的textLevel1和changed是在door组件中定义的输入和输出项

组件嵌套及沟通

在car组件中的template中使用door标签,就意味着需要加入door组件,故形成了嵌套关系,那么首先需要import 引入door组件import {door} from './door.component';

在模板中我们使用door组件的door标签,那么就需要配置directives指令集

directives:指令集,注意是一个集合,也就是说在该组件中嵌入多个组件(需要先import),由于上面在模板中使用了door指令所以在该集合中必须加入door指令

我们看到在car组件中的模板是这样使用car组件的

<door [textLevel1]="text" (changed)="onCarChange($event)">

这里加入了door标签,而[textLevel1]是door标签的一个属性,可以理解为input标签的value属性 ,那么这个属性的定义需要在door组件中的inputs中加入
inputs: ['textLevel1']
表示door标签使用textLevel1来接收上级传来的数据,[textLevel1]="text"表示door组件的textlevel1属性绑定了car组件的text属性,通过这样设计就实现了父组件向子组件的数据传递。那么子组件又是如何向父组件传递数据的呢?就是使用outputs,outputs提供了一个事件的集合,利用子组件定义的事件来向上级传递数据,如(changed)=" onCarChange ($event)"中所示,(changed)是door组件定义的事件并在outputs配置中做了声明 ,此处注意调用的是父组件的事件处理函数。
outputs: ['changed']

$event是该事件相关的数据,这里就是textlevel1的值

组件定义

为了能够让其他组件能够使用所以需要export该组件,组件的定义和class一样,使用typescirpt语法,组件中可以定义属性,方法,可以有公共的也可以是私有的,如果是私有的需要加入private声明,需要注意的是如果使用了事件需要引入EventEmitter。如import {Component, EventEmitter} from 'angular2/core';
事件定义使用

changed = new EventEmitter();

使用Emit 传递事件如this.changed.emit(value);
 

组件的数据交互

Angular2相比angular1有一个很大的改变就是默认情况下的绑定是单向绑定,也是就是说视图上的数据变更不能直接更新model了,需要程序化进行更新,这样做的目的是为了提高效率,当然目前版本还保留了[(ngModel)]这种绑定方式,可以实现双向绑定。那么组件间的数据相互如何传递呢,父-〉子,子-〉父,子-〉子?

父到子的数据传递

在父组件中使用子组件就是简单的使用子组件标签方式,那么可以将子组件的一个属性绑定到父组件的一个属性上就实现了父组件数据向子组件传递。简单理解下

在父组件中定义一个子组件<child [childProperty]=”parentProperty”></child>

这很类似于在父组件中使用<input [value]=”parentProperty” ></input>

通过这种方式子组件就能够获取父组件的数据了,需要注意的是子组件需要将用于绑定的属性公布出去,即使用inputs:[‘childProperty’],这里是个数组,可以公布多个属性供外部绑定。

总结来说就是可以通过将父组件的一个属性绑定到子组件中公布的一个属性上就实现了父组件向子组件传递数据的效果。

子到父的数据传递

子组件向父组件传递数据是通过事件的方式,首先需要在子组件中定义事件,子组件的事件被触发时引发父组件的事件响应,同时将事件参数传递给父组件的响应函数,这样就完成了子组件向父组件传递数据。如下所示

<childLevel1 [parentMsg]="parentMsg" (childLevel1Changed)="OnChildLevel1MsgChanged($event)"> </childLevel1>

[parentMsg]是子组件的一个属性,而值来自父组件的parentMsg,这样实现了父组件向子组件传递parenMs数据,另外(childLevel1Changed)是子组件中定义的事件,而OnChildLevel1MsgChanged是父组件的一个事件响应函数,而$event代表着事件源。从这个例子我们分析看看数据流。下面是子组件的事件定义:

1
2
3
4
5
6
outputs: ['childLevel1Changed']
<button (click)="OnChildLevel1MsgChanged(childLevel1Text.value)" >changeChild1</button>
OnChildLevel1MsgChanged(event) {
        this.childLevel1Msg = event;
        this.childLevel1Changed.emit(event);
    }

  

事件的发生顺序由子组件的按钮引发交由事件处理程序,事件的参数是childLevel1Text. Value,事件处理程序将子组件定义的事件广播出去,并传递数据源,父组件检测到子组件的事件并交给父组件的事件处理程序来处理。这样就将子组件的数据传递给了父组件。

子到子的数据传递

了解了上面的方式后,那么子到子就简单了,分成子到父,再父到子。父组件在中间起到协调作用

最后,看到这些有些疑惑了,一个真实应用会有很多的数据交互,如果使用这种机制,那很难去维护这里的关系了,难道没有更好的方式去实现组件间的数据交互吗?答案是服务!

angularjs2 学习笔记(三) 服务

 

在anglar2中服务是什么?

如果在项目中有一段代码是很多组件都要使用的,那么最好的方式就是把它做成服务。

服务是一种在组件*享功能的机制,当我们使用标签将多个组件组合在一起时我们需要操作一些数据或是需要做一些运算时,我们需要做一个服务;服务能帮我们引入外部的数据。那么如何创建一个服务呢?

创建服务

创建一个简单的服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {Injectable} from 'angular2/core';
 
import {PLANETS} from './planets-data';
 
@Injectable()
 
export class PlanetService {
 
    getPlanets(){
 
        return Promise.resolve(PLANETS);
 
    }
 
}

  

首先需要一个Injectable的指令,这个指令能告诉其他引用的组件这个服务是可注入的。

然后定义一个方法用来返回数据。

服务调用

服务调用也很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import {Component} from "angular2/core";
 
import {PlanetService} from "./planet.service"//1
 
import {PlanetComponent} from "./planet.component";
 
import {Planet} from "./planet";
 
@Component({
 
    selector: "my-app",
 
    templateUrl: "app/planet-list.component.html",
 
    directives: [PlanetComponent],
 
    providers:[PlanetService], //2
 
    styles:[`
                body{
                    padding:0;
                    margin:0;
                    font-family:Roboto,Arial,sans-serif;
                    font-size:16px
                }
                .title {
                    color:#018EFA;
                }
                .clicked{
                    color:white;
                }
                .labels{
                    color: white;
                }
    `]
})
 
export class PlanetListComponent{
 
    public planets : Planet[];
 
    public selectedPlanet = {};
 
    public onNameClick(planet){
 
        this.selectedPlanet = planet;
    }
 
    constructor(private _planetService: PlanetService){  // 3
 
        this.getPlanets();
    }
 
    // make a call to our planet service
 
    getPlanets(){  //4
 
        this._planetService.getPlanets().then((planets:Planet[])=>this.planets = planets);
    }
}

  

1、  引入服务(import)

2、  使用providers 来注入服务

3、  创建一个服务的示例

使用promise调用服务

angularjs2 学习笔记(四) 路由

 

angular2路由是管理angular2应用内部导航的一个重要内容,在angular应用中,很多的组件是通过组合完成一个复杂的应用,不可避免的是我们常会在视图间切换,那么这是就需要使用路由来管理视图间的转换。

路由定义

先看一个简单的路由定义

///<reference path="../node_modules/angular2/typings/browser.d.ts"/>

import {Component} from 'angular2/core';

import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';

import {parentCmp} from "./parent.component";

import {planetList} from "./planet-list.component";

@Component({

    selector: 'my-app',

    template: `

    <h3 class="title">Angular 2 Router Example</h3>

    <nav>

      <a [routerLink]="['Parent']">parent</a>

      <a [routerLink]="['PlanetList']">planetList</a>

    </nav>

    <router-outlet></router-outlet>

  `,

    directives: [ROUTER_DIRECTIVES]

})

@RouteConfig([
{ path: '/contact', name: 'Parent', component: parentCmp, useAsDefault: true },
{ path: '/planetList', name: 'PlanetList', component: planetList } ]) export class RouteExampleAppComponent { }

  

从这个例子可以看出路由定义的过程

1、  引入路由组件import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'

2、  引入我们所需的导航组件

import {parentCmp} from "./parent.component";

import {planetList} from "./planet-list.component";

3、  定义组件,配置模板,定义路由

<a [routerLink]="['Parent']">parent</a>定义了一个路由,其中的Parent是我们的一个的一个路由标签,来自下面的路由配置,注意使用驼峰的表达方式。

<router-outlet></router-outlet>定义了视图的显示位置,即导航的模块显示区域

directives: [ROUTER_DIRECTIVES],引入路由指令

@RouteConfig:用于进行路由配置,其中path只路由的路径,在url中能够看到;name指路由的名称,和上面导航一致;component路由的组件即路由指向的组件。

这样一个简单的路由组件就基本完成

路由使用

import {bootstrap} from 'angular2/platform/browser';

import {ROUTER_PROVIDERS} from 'angular2/router';

import {RouteExampleAppComponent} from "./myRoute";

bootstrap(RouteExampleAppComponent, [ROUTER_PROVIDERS]);

这里和前面的bootstrap组件调用不同的是除了引入自定义的组件外还需要注入angular的路由服务。

父子路由

在我们的实际应用中还常遇到这样的一个场景,父组件中包含了一些路由配置,在子组件中同样还有路由配置,有些情况下还需要在父组件通过路由传递数据到子组件中,那么这些情况如何实现呢?

看一个例子

myRoute.ts

///<reference path="../node_modules/angular2/typings/browser.d.ts"/>

import {Component} from 'angular2/core';

import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';

import {home} from "./home";

import {childRoute1} from "./childRoute1";

import {childRoute1Detail} from './ChildRoute1Detail'

@Component({

    selector: 'my-app',

    template: `

    <h3 class="title">Angular 2 Router Example</h3>

    <nav>

      <a [routerLink]="['Home']">Home</a>

      <a [routerLink]="['ChildRoute1']">子路由</a>

    </nav>

    <router-outlet></router-outlet>

  `,
directives: [ROUTER_DIRECTIVES] }) @RouteConfig([
{ path: '/', name: 'Home', component: home, useAsDefault: true },
{
path: '/childRoute1', name: 'ChildRoute1', component: childRoute1 }, {
path: '/childRoute1Detail', name: 'ChildRoute1Detail', component: childRoute1Detail
}
]) export class RouteExampleAppComponent { }

  

这是一个父组件,里面加入了路由配置,其中ChildRoute1是一个子路由,而'ChildRoute1Detail'是一个子路由的子路由即孙子路由,这些路由都在父组件中进行了设置。

childRoute.ts

import {Component} from 'angular2/core';

import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';

import {Router, RouteParams} from 'angular2/router';

import {childRoute1Detail} from './ChildRoute1Detail'

@Component({

    selector: "childRoute1",

    template: ` this is a route

<a (click)="onSelect()">点击</a>

<a [routerLink]="['ChildRoute1Detail']">子路由明细1</a>
`,
directives: [ROUTER_DIRECTIVES]
})
export class childRoute1 { constructor(private _router: Router, private _routeParams: RouteParams) { }
onSelect() { this._router.navigate(['ChildRoute1Detail']);
}
};

  

这是一个子组件,里面没有路由配置,使用的是父组件中的路由配置,这里的两个链接实现的功能是一样的,都是将孙子组件加载到父组件的router-outlet中。

childRoute1Detail.ts

import {Component} from 'angular2/core';

import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';

@Component({

    selector: "childRoute1Detail",

    template: `<h1>子路由1明细</h1>`

})
export class childRoute1Detail { };

 

这是一种应用场景,但这种应用场景显然不是好的选择,因为实际开发中各个模块可能都是单独开发,父组件也可能不知道究竟会有多少个子组件路由,子组件路由交给子组件来配置可能是最好的方式了,那么如何实现呢?

我们需要将父组件myRoute中的不属于它的路由'ChildRoute1Detail'移除,并放在子组件childRoute中,注意父组件不能和子组件有相同的路由配置,否则会出现下面的错误。

在子组件childRoute加入'ChildRoute1Detail'路由时还需要使用useAsDefault: true配置

@RouteConfig([
{
path: '/childRoute1Detail', name: 'ChildRoute1Detail', component: childRoute1Detail, useAsDefault: true }
])

  

否则会出现

同时在父组件的路由路径中需要对子路由加入“…”来标识这是个子路由,否则会出现下面错误

@RouteConfig([
{
path: '/', name: 'Home', component: home, useAsDefault: true },
{
path: '/childRoute1/...', name: 'ChildRoute1', component: childRoute1 }
])

  

完整的childRoute1.ts

import {childRoute1Detail} from './ChildRoute1Detail'

@Component({

    selector: "childRoute1",

    template: `

 this is a route

<a (click)="onSelect()">点击</a>

<a [routerLink]="['ChildRoute1Detail']">子路由明细1</a>

<router-outlet></router-outlet>

`,
directives: [ROUTER_DIRECTIVES]
}) @RouteConfig([
{ path: '/childRoute1Detail', name: 'ChildRoute1Detail', component: childRoute1Detail, useAsDefault: true } ]) export class childRoute1 { constructor(private _router: Router, private _routeParams: RouteParams) { }
onSelect() { this._router.navigate(['ChildRoute1Detail']); } };

  

路由参数

通过在routelink或route.navigate中可以指定参数,如下

<a [routerLink]="['ChildRoute1Detail', {param1: param1}]">子路由明细1</a>

this._router.navigate(['ChildRoute1Detail', {param1:this.param1}]);

这是两种方式传递param1参数

接受路由参数使用RouteParams或RouteData

this.param1 = routeParams.get("param1");

使用routedata方式则需要在路由配置中加入data标签,如下

@RouteConfig([

    {

        path: '/childRoute1Detail',

        name: 'ChildRoute1Detail',

        component: childRoute1Detail,

        data: { param1:"routedata " },

        useAsDefault: true

    }

])

  使用时

constructor(data: RouteData) {

this.param1 = data.get("param1");

}

这里要注意的是路由参数通常情况下我们不建议传递复杂类型的参数,我们可以传递id参数,然后在通过一个service注入并通过这个id找出该复杂类型的实例 。

另外,在使用路由参数时不能忘记引用

import {Router, RouteParams} from 'angular2/router';

import { RouteData} from 'angular2/router';

angularjs2 学习笔记(五) http服务

 

angular2的http服务是用于从后台程序获取或更新数据的一种机制,通常情况我们需要将与后台交换数据的模块做出angular服务,利用http获取更新后台数据,angular使用http的get或put进行后台调用采用的是ajax方式,跨域问题需要单独处理。下面来看一个例子,演示从后台web api中获取数据并进行页面加载。

1、由于要使用http服务,所以我们一定要在我们的web页面需要引入<script src="node_modules/angular2/bundles/http.dev.js"></script>,这步很关键,我之前发生的找不到http服务的原因就在此,浪费了很多时间在此。

2、在angular入口还需引入HTTP_PROVIDERS,并注入,同时由于要使用map,subscribe等所以需要使用rxjs库,那么就需要提前在入口程序中引入import 'rxjs/Rx',血的教训

1
2
3
4
5
6
7
8
9
10
import {bootstrap} from 'angular2/platform/browser';
 
import {HTTP_PROVIDERS} from 'angular2/http';
 
import {myFrame} from "./frame/component/myFrame.component";
 
import 'rxjs/Rx';
 
  
bootstrap(myFrame, [ HTTP_PROVIDERS]);

  

3、创建服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
///<reference path="../../../node_modules/angular2/typings/browser.d.ts"/>
 
  
 
import {Injectable} from 'angular2/core';
 
import {Http } from 'angular2/http';
 
  
 
@Injectable()
 
export class channelService {
 
private _carsUrl: string = "http://localhost:6611/api/Chanel";
 
constructor(private _http: Http) {
        }
 
getChannelList() {
    return this._http.get(this._carsUrl).map(responce => responce.json())
}

  

在这个服务中使用了http中的get来获取数据,这里get的url(web api)是与我目前的anuglar应用在一个域内。作为服务我们需要申明该服务是可注入的@Injectable()

4、服务调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
///<reference path="../../../node_modules/angular2/typings/browser.d.ts"/>
 
import {Component} from 'angular2/core';
 
  
 
import {appService} from './../service/appsetting.service'
 
import {channelService} from './../service/channel.service'
 
import {Channel} from './../model/channel'
 
  
@Component({
 
    selector: 'topNav',
 
    templateUrl: '../app/frame/template/topNav.html',
 
    providers: [appService, channelService]
})
 
export class topNav {
 
    webTitle: string;
 
    public items: Channel[];
 
    constructor(private _appService: appService,private _channelService:channelService) { 
        this.getWebTitle();
        this.getChannelList();
    }
 
    getWebTitle() {
        this.webTitle = this._appService.AppSetting.webTitle;
    }
 
    getChannelList() {
         this._channelService.getChannelList().subscribe(res => { this.items=res});
    }
}

  

这里就和普通服务调用没什么区别了,需要先import再在providers中申明,然后在构造函数中注入就行了。

这个例子中有个需要注意的是我们前端model和后端model有可能不一致,那么需要在获取数据后再进行转换,如果类型字段都一致那么可以直接使用,由于是json格式,系统会自动将后台model转换为我们前端使用的model

Web api:

1
2
3
4
5
6
7
8
public class ChanelController : ApiController
    {
        // GET api/<controller>
        public IEnumerable<Chanel> Get()
        {
            return new Chanel[] { new Chanel{ ID="1", ChanelName="组织机构"},new Chanel{ ID="2",ChanelName="通知公告"} };
        }
}

  

注:web api 可以使用Swashbuckle 进行测试,安装 PM> Install-Package Swashbuckle,使用时只需在路径后加入swagger,如http://localhost:6611/swagger/ui/index

学习到这里了,逐步开始向实际应用转换,中间的每一步都是血泪史。

angularjs2 学习笔记(六) Form

 

Angular 2 Form表单

在angular2 form表单中我们需要了解表单数据绑定、数据验证、数据提交等内容,在下面的示例中并没有实际提交到后台,这部分内容在今后webapi中加以练习。

表单建立

<form (ngSubmit)="onSubmit(planetForm.value)" #planetform="ngForm">

</form>

这里是一个空的表单ngSubmit是一个事件,用于提交数据,数据则是整个form表单的内容,通常情况下我们的表单会绑定到我们的一个model中,那么提交的数据最后也是以json格式返回到后台来处理。

在表单中我们可以使用标准的html5控件来处理用户输入。

数据绑定

<div class="form-group">

        <label class="col-xs-4 control-label" for="id">id : </label>

        <div class="col-xs-8">

            <input type="text" style="width: 300px" class="form-control" required

                   [(ngModel)]="myPlanet.id"

                   ngControl="id"

                   #planetid="ngForm">

            <div [hidden]="planetid.valid || planetid.pristine" class="alert alert-danger">

                The Name of green car is required !

            </div>
</div>
</div>

  

这个一个表单输入的内容其中使用的是bootstrap的表单样式,在input中我们使用[(ngModel)](注意大小写)实现双向绑定,ngControl用于检测数据变化对应的是model中的字段,设置input的变量为ngForm来告诉angular 这个输入是这个表单内容。在vs中由于默认设置在粘贴html文本时会自动将大写字母变化成小写字母,所以angular的某些标签很容易发生错误,所以需要关闭这个自动转换,方法就是在vs的选项中将文本编辑器中的html高级选项下的粘贴时设置格式设置为false。

数据验证

html5内置的数据验证包括Required、minLength、maxLength、pattern ,我们可以将这些标签应用到我们的输入控件上,如

<input type="text" style="width: 300px" class="form-control" required maxlength="10" minLength="4"

                   [(ngModel)]="myPlanet.id"

                   ngControl="name"

                   #planetid="ngForm">

            <div [hidden]="planetid.valid || planetid.pristine" class="alert alert-danger">

                The id is required !

            </div>

  

这里的"planetid.valid || planetid.pristine是验证这个input输入是否有效,或者数据是否改变。

Formbuilder

Formbuilder是我们可以采用的另一种方式来创建Form,这种方式是以模型驱动完成的,更适合我们进行编码控制,有利于我们将逻辑与视图分离。

Angular2 form 作用机制由两个主要的组件Controls和Controls group。

Control:包含值和验证状态,一个control能够被邦定可以包含三个可选参数(缺省值,验证,异步验证),例如

this.username = new Control('Default value', Validators.required, usernameValidator.checkIfAvailable);

在html应用时使用ngControl标签绑定这个控件

<input required type=”text” ngControl=”username” />

这里定义的username与input中ngControl指定的名称要一致。

Control Groups:form定义的一部分,通过将多个cotrol组合在一起形成组。

class App {

 name: Control;
username: Control;
email: Control; form: ControlGroup; constructor(private builder: FormBuilder) {
this.name = new Control('', Validators.required);
this.email = new Control('', Validators.required);
this.username = new Control('', Validators.required); this.form = builder.group({
name: this.name,
email: this.email,
username: this.username
});
}
};

  

html应用时加入ngFormModel来标识。

<form [ngFormModel]=”form”>

自定义验证

除了内置验证外,我们还可以自定义验证,例子如下

import {Control} from 'angular2/common';

interface ValidationResult
{
[key: string]: boolean;
}
export class UsernameValidator
{
static startsWithNumber( control: Control ): ValidationResult
{
if ( control.value !="" && !isNaN( control.value.charAt( 0 ) ) ){
return { "startsWithNumber": true };
}
return null;
}
}

  

这个自定义验证如果输入的值中首字母是数字则验证无效,返回null则验证通过。使用方法

首先import {UsernameValidator} from './customValidate';

然后在我们需要验证的控件上加入自定义验证

this.name = new Control( this.myPlanet.name, UsernameValidator.startsWithNumber );

错误提示

<div *ngIf="name.dirty && !name.valid">

  <p *ngIf="name.errors.startsWithNumber">

    Your name can't start with a number

  </p>

</div>

  

这里的errors. startsWithNumber就是我们自定义返回的key值。最好的方式是使用hasError,因为如果返回的startsWithNumber是null的话会引发异常

<p *ngIf="name.hasError('startsWithNumber')">

异步验证

如果我们需要使用service去到后台获取数据并验证,则我们需要使用异步验证方式,这里的例子使用promise模拟。

static usernameTaken( control: Control ): Promise<ValidationResult>

    {
let q = new Promise(( resolve, reject ) =>
{
setTimeout(() =>
{
if ( control.value !== 'oldkingsir') { resolve( { "usernameTaken": true});
} else {
resolve( null );
}
}, 1000 )
});
return q;
}

  

真实应用可能是

class ProductValidator {

    static productExists(control: Control): {[key: string]: any} {

        return new Promise( resolve => {

            var productService = new ProductService();

            var product: Product;

            productService.getProduct(control.value)

            .then(productFound => {

                if (productFound == null) {

                    // need to return something if not ok

                    resolve({productExists: false});

                } else {

                    // need to return null if ok

                    resolve(null);

                }
});
});
}
}

  

下面需要使用验证的第三个参数,方式如下

this.name = new Control( this.myPlanet.name,UsernameValidator.startsWithNumber, UsernameValidator.usernameTaken );

html

<div *ngIf="name.dirty && !name.valid">

                <span *ngIf="name.pending">Checking istaken...</span>

                  <p *ngIf="name.hasError('startsWithNumber')">

                    Your name can't start with a number

                 </p>

                <p *ngIf="name.hasError('usernameTaken')">

                    Your name is already taken

                 </p>

            </div>

  

这里使用了pending来友好提示验证中。

组合验证

如果需要进行多个验证可以使用compose组合验证,如下

this.name = new Control('', Validators.compose([Validators.required, Validators.minLength(4)]));

最后如果整个表单验证不通过我们不提交则可以在提交按钮上加以处理,如

<button type="submit" class="btn btn-default" [disabled]="!form.valid">Submit</button>