第一节:Angular 2.0 从0到1 (一)
第二节:Angular 2.0 从0到1 (二)
第三节:Angular 2.0 从0到1 (三)
第四节:Angular 2.0 从0到1 (四)
第五节:Angular 2.0 从0到1 (五)
第六节:Angular 2.0 从0到1 (六)
第七节:Angular 2.0 从0到1 (七)
第八节:Angular 2.0 从0到1 (八)
番外:Angular 2.0 从0到1 Rx—隐藏在Angular 2.x中利剑
番外:Angular 2.0 从0到1 Rx—Redux你的Angular 2应用

第六节:使用第三方样式库及模块优化

生产环境初体验

用angular-cli建立生产环境是非常简单的,只需输入ng build --prod --aot即可。—prod会使用生产环境的配置文件,—aot会使用AOT替代JIT进行编译。现在实验一下

仔细看一下命令行输出,我们应该可以猜到angular移除了一些没有用到的类库(Google称之为Shaking过程),对js和css等进行了压缩等优化工作。angular在我们的项目根目录下建立了一个dist文件夹,用于生产环境的文件就输出在这个文件夹了。

我们安装一个http-server,npm i -g http-server,然后在dist目录键入http-server .。打开浏览器进入http://localhost:8080,我们会看到网页打开了。但如果打开console,或者试着登录一下,你会发现存在很多错误。

这是由于angular-cli当前的bug产生的,目前需要对路由做hash处理。

...
@NgModule({
imports: [
RouterModule.forRoot(routes, { useHash: true })
],
exports: [
RouterModule
]
})
...

只需在app-routing.module.ts中为RouterModule配置{ useHash: true }的属性即可。这样的话angular会在url上加上一个#,比如login的url现在是http://localhost:8080/#/login。这样改动后,功能又好用了。以后我们项目如果需要发布到生产环境的,大家利用angular-cli可以很方便的处理了。然后下面我们回到开发环境,请关掉8080端口的http服务器,并删掉dist。

第三方样式库

之前我们使用的是自己为各个组件写样式,其实angular团队有一套官方的符合Material Design的内建组件库:https://github.com/angular/material2(这个库还属于早期阶段,很多控件不可用,所以大家可以关注,但现阶段不建议在生产环境中使用)。除了官方之外,目前有大量的比较成熟的样式库,比如bootstrap,material-design-lite等。我们这节课以material-design-lite来看一下怎么使用。Material Desing Lite是Google为web开发的一套基于Material Design的样式库。由于是Google开发的,所以你要去访问之前要科学上网。我们当然可以直接使用官方的css样式库,但是有好心人已经帮我们封装成了比较好用的组件模块了,组件模块的好处是可以使模板写起来更简洁,而且易于扩展。现在打开一个terminal输入npm install --save angular2-mdl。然后在你需要使用MDL组件的模块中引入MdlModule。我们首先希望改造一下我们的AppComponent,目前它只有一句简陋的文字输出。

<mdl-layout mdl-layout-fixed-header mdl-layout-header-seamed>
<mdl-layout-header>
<mdl-layout-header-row>
<mdl-layout-title>Awesome Todos</mdl-layout-title>
<mdl-layout-spacer></mdl-layout-spacer>
<!-- Navigation. We hide it in small screens. -->
<nav class="mdl-navigation">
<a class="mdl-navigation__link">Logout</a>
</nav>
</mdl-layout-header-row>
</mdl-layout-header>
<mdl-layout-drawer>
<mdl-layout-title>Title</mdl-layout-title>
<nav class="mdl-navigation">
<a class="mdl-navigation__link">Link</a>
</nav>
</mdl-layout-drawer>
<mdl-layout-content class="content">
<router-outlet></router-outlet>
</mdl-layout-content>
</mdl-layout>

这段代码里面mdl开头的标签都是我们刚引入的组件库封装的组件,具体的用法可以去 http://mseemann.io/angular2-mdl/ 参考文档资料。<mdl-layout></mdl-layout>是一个布局组件,mdl-layout-fixed-header是一个可以让header固定在页面顶部的属性,mdl-layout-header-seamed是要header没有阴影。mdl-layout-header是一个顶部组件,mdl-layout-header-row是在顶部组件中形成一行的容器。mdl-layout-spacer是一个占位的组件,它会把组件剩余位置占满,防止出现错位。mdl-layout-drawer是一个抽屉组件,和Android的标准应用类似,点击顶部菜单图标会从侧面滑出一个菜单。别忘了在AppModule中引入

...
import { MdlModule } from 'angular2-mdl';
...
@NgModule({
...
imports: [
...
MdlModule,
...
],
bootstrap: [AppComponent]
})
export class AppModule { }

我们为了使用,还需要对颜色做个定制,这个定制需要使用一种CSS的预编译技术叫SASS,需要建立一个src\styles.scss,然后定义Material Design的颜色,具体颜色名字的定义是在Google调色板类中定义的,可以去这里查看

@import "~angular2-mdl/scss/color-definitions";

$color-primary: $palette-blue-500;
$color-primary-dark: $palette-blue-700;
$color-accent: $palette-amber-A200;
$color-primary-contrast: $color-dark-contrast;
$color-accent-contrast: $color-dark-contrast; @import '~angular2-mdl/scss/material-design-lite';

由于我们使用的CLI并不知道我们采用了预编译的css,所以需要改一下angular-cli.json,把styles改写成下面的样子

"styles": [
"styles.scss"
],

保存后打开浏览器看一下效果:

我们接下来改造一下login的模板

<div>
<form (ngSubmit)="onSubmit()">
<mdl-textfield
type="text"
label="Username..."
name="username"
floating-label
required
[(ngModel)]="username"
#usernameRef="ngModel"
>
</mdl-textfield>
<div *ngIf="auth?.hasError" >
{{auth?.errMsg}}
</div>
<mdl-textfield
type="password"
label="Password..."
name="password"
floating-label
required
[(ngModel)]="password"
#passwordRef="ngModel">
</mdl-textfield>
<button
mdl-button mdl-button-type="raised"
mdl-colored="primary"
mdl-ripple type="submit">
Login
</button>
</form>
</div>

由于采用了符合Material Design的组件,我们就不需要原来的用于验证的div了。

下面看一下Todo,原来我们在css中用了svg来改写复选框的样子,现在我们试试用mdl来做。在todo-list.component.html中把ToggleAll改写成下面的样子

<mdl-icon-toggle class="toggle-all" [mdl-ripple]="true" (click)="onToggleAllTriggered()">expand_more</mdl-icon-toggle>

这个标签是把一个图标做成可复选框的效果,这里用到了Google的icon font,所以需要在index.html中引入

<!doctype html>
<html>
<head>
...
<link rel="stylesheet" href="https://fonts.lug.ustc.edu.cn/icon?family=Material+Icons">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>

我们用了科大的镜像,因为Google的产品,你懂的。
当然TodoItem模板中的checkbox也需要改造成

<mdl-icon-toggle (click)="toggle()" [(ngModel)]="isChecked">check_circle</mdl-icon-toggle>

Todo变成下面的样子,也还不错啊~~

模块优化

现在仔细看一下我们的各个模块定义,发现我们不断地重复引入了CommonModuleFormsModuleMdlModule,这些如果在大部分的组件中都会用到话,我们不妨建立一个SharedModule (src\app\shared\shared.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MdlModule } from 'angular2-mdl'; @NgModule({
imports: [
CommonModule,
FormsModule,
MdlModule
],
exports: [
CommonModule,
FormsModule,
MdlModule
]
})
export class SharedModule { }

这个模块的作用是把常用的组件和模块打包起来(虽然现在没有组件,只是把常用的模块导入又导出),这样在其他模块中只需引入这个模块即可,比如TodoModule现在看起来是下面的样子:

...
import { SharedModule } from '../shared/shared.module';
...
@NgModule({
imports: [
SharedModule,
...
],
declarations: [
TodoComponent,
...
],
providers: [
{provide: 'todoService', useClass: TodoService}
],
})
export class TodoModule {}

多个不同组件间的通信

下面我们要实现这样一个功能:在用户未登录时,顶部菜单中只有Login一个链接可见,用户登录后,顶部菜单中有三个链接,一个是Todo,一个是用户个人信息,另一个是Logout。按这个需求将顶部菜单改造成如下:

<!--src\app\app.component.html-->
<mdl-layout mdl-layout-fixed-header mdl-layout-header-seamed>
<mdl-layout-header>
<mdl-layout-header-row>
<mdl-layout-title>{{title}}</mdl-layout-title>
<mdl-layout-spacer></mdl-layout-spacer>
<!-- Navigation. We hide it in small screens. -->
<nav class="mdl-navigation" *ngIf="auth?.user?.username !== null">
<a class="mdl-navigation__link" routerLink="todo">Todos</a>
</nav>
<nav class="mdl-navigation" *ngIf="auth?.user?.username !== null">
<a class="mdl-navigation__link" routerLink="profile">{{auth.user.username}}</a>
</nav>
<nav class="mdl-navigation">
<a class="mdl-navigation__link" *ngIf="auth?.user?.username === null" (click)="login()">
Login
</a>
<a class="mdl-navigation__link" *ngIf="auth?.user?.username !== null" (click)="logout()">
Logout
</a>
</nav>
</mdl-layout-header-row>
</mdl-layout-header>
<mdl-layout-drawer>
<mdl-layout-title>{{title}}</mdl-layout-title>
<nav class="mdl-navigation">
<a class="mdl-navigation__link">Link</a>
</nav>
</mdl-layout-drawer>
<mdl-layout-content class="content">
<router-outlet></router-outlet>
</mdl-layout-content>
</mdl-layout>

这样改造完后的页面结构是顶部菜单只加载一次,底下的内容随着不同路由显示不同内容。但如果我们要在login后顶部菜单也随之改变的话,我们一定要实现某种通信机制。前面我们讲过EventEmiiter,当然我们可以将整个页面当成父控件,顶部菜单是子控件的形式,但这时你发现由于我们是用路由插座(<router-outlet></router-outlet>) l来显示内容的,所以无法采用子控件的形式传递信息。

这种情况就要引入Rx了,rx的学习门槛较高,也不是本教程的重点,但我还是这里尝试着解释一下。Rx是响应式编程的利器,它的学习门槛来自于思维方式的转变,从传统的编程思维转成流式思维:Rx总体来看是一个数据流或信号流,所有的操作符都是为了对这个流进行控制。写Rx时要对系统数据或信号的完整逻辑流程先想清楚,然后就比较好写了。

其实在Angular2中,Rx是无处不在的,还记得我们之前总用到toPromise()这个方法吗?其实这个方法是给不太熟悉Rx的同学用的,Angular本身返回的就是Observable。我们现在把UserService改成Rx版本

import { Injectable } from '@angular/core';

import { Http, Headers, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map'; import { User } from '../domain/entities'; @Injectable()
export class UserService { private api_url = 'http://localhost:3000/users'; constructor(private http: Http) { } getUser(userId: number): Observable<User> {
const url = `${this.api_url}/${userId}`;
return this.http.get(url)
.map(res => res.json() as User);
}
findUser(username: string): Observable<User> {
const url = `${this.api_url}/?username=${username}`;
return this.http.get(url)
.map(res => {
let users = res.json() as User[];
return (users.length>0) ? users[0] : null;
});
}
}

大家可能注意到了,其实有没有Promise都无所谓,大概的写法也是类似的,只不过返回的是Observable。这里改了之后,相关调用的地方都要改一下,比如LoginComponent:

import { Component, Inject } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { Auth } from '../domain/entities';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent { username = '';
password = '';
auth: Auth;
constructor(@Inject('auth') private service, private router: Router) { } onSubmit(){
this.service
.loginWithCredentials(this.username, this.password)
.subscribe(auth => {
this.auth = Object.assign({}, auth);
if(!auth.hasError){
this.router.navigate(['todo']);
}
});
}
}

AuthService也需要改写一下

import { Injectable, Inject } from '@angular/core';
import { Http, Headers, Response } from '@angular/http'; import { ReplaySubject, Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import { Auth } from '../domain/entities'; @Injectable()
export class AuthService {
auth: Auth = {hasError: true, redirectUrl: '', errMsg: 'not logged in'};
subject: ReplaySubject<Auth> = new ReplaySubject<Auth>(1);
constructor(private http: Http, @Inject('user') private userService) {
}
getAuth(): Observable<Auth> {
return this.subject.asObservable();
}
unAuth(): void {
this.auth = Object.assign(
{},
this.auth,
{user: null, hasError: true, redirectUrl: '', errMsg: 'not logged in'});
this.subject.next(this.auth);
}
loginWithCredentials(username: string, password: string): Observable<Auth> {
return this.userService
.findUser(username)
.map(user => {
let auth = new Auth();
if (null === user){
auth.user = null;
auth.hasError = true;
auth.errMsg = 'user not found';
} else if (password === user.password) {
auth.user = user;
auth.hasError = false;
auth.errMsg = null;
} else {
auth.user = null;
auth.hasError = true;
auth.errMsg = 'password not match';
}
this.auth = Object.assign({}, auth);
this.subject.next(this.auth);
return this.auth;
});
}
}

这里注意到我们引入了一个新概念:Subject。Subject 既是Observer(观察者)也是Observable(被观察对象)。这里采用Subject的原因是我们在Login时改变了Auth的属性,但由于这个Login方法是Login页面显性调用的,其他需要观察Auth变化的地方调用的是getAuth()方法。这样的话,我们需要在Auth发生变化时推送变化出去,我们在loginWithCredentials方法中以this.subject.next(this.auth);写入其变化,在getAuth()中用return this.subject.asObservable();将Subject转换成Observable。

Auth:{}     Auth{user: {id: 1...}}     没有Auth数据发射了
|============|===========================|=====
登录前 登录后 todo路由守卫激活

但为什么是ReplaySubject呢?我们在执行登录时,如果鉴权成功,会导航到某个路由(这里是todo),这时会引发CanActivate的检查,而此时最新的Auth已经发射完毕,CanActivate检查时会发现没有Auth数据。这种情况下我们需要缓存最近的一份Auth数据,无论谁,什么时间订阅,只要没有更新的数据,我们就推送最近的一份给它,这就是ReplaySubject的意义所在。

下面我们改写路由守卫

import { Injectable, Inject } from '@angular/core';
import {
CanActivate,
CanLoad,
Router,
Route,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map'; @Injectable()
export class AuthGuardService implements CanActivate, CanLoad { constructor(
private router: Router,
@Inject('auth') private authService) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
let url: string = state.url; return this.authService.getAuth()
.map(auth => !auth.hasError);
}
canLoad(route: Route): Observable<boolean> {
let url = `/${route.path}`; return this.authService.getAuth()
.map(auth => !auth.hasError);
}
}

这里你会发现多了一个canLoad方法,canActivate是用于是否可以进入某个url,而canLoad是决定是否加载某个url对应的模块。所以需要再改下路由

import { NgModule }     from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { AuthGuardService } from './core/auth-guard.service'; const routes: Routes = [
{
path: '',
redirectTo: 'login',
pathMatch: 'full'
},
{
path: 'todo',
redirectTo: 'todo/ALL',
canLoad: [AuthGuardService]
}
]; @NgModule({
imports: [
RouterModule.forRoot(routes, { useHash: true })
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}

现在打开浏览器欣赏一下我们的成果。

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

第一节:Angular 2.0 从0到1 (一)
第二节:Angular 2.0 从0到1 (二)
第三节:Angular 2.0 从0到1 (三)
第四节:Angular 2.0 从0到1 (四)
第五节:Angular 2.0 从0到1 (五)
第六节:Angular 2.0 从0到1 (六)
第七节:Angular 2.0 从0到1 (七)
第八节:Angular 2.0 从0到1 (八)
番外:Angular 2.0 从0到1 (八)
番外:Angular 2.0 从0到1 Rx—Redux你的Angular 2应用

Angular 2.0 从0到1 (六)的更多相关文章

  1. Angular 2.0 从0到1:Rx--隐藏在Angular 2.x中利剑

    第一节:Angular 2.0 从0到1 (一)第二节:Angular 2.0 从0到1 (二)第三节:Angular 2.0 从0到1 (三)第四节:Angular 2.0 从0到1 (四)第五节: ...

  2. Angular 2.0 从0到1 (七)

    第一节:Angular 2.0 从0到1 (一)第二节:Angular 2.0 从0到1 (二)第三节:Angular 2.0 从0到1 (三)第四节:Angular 2.0 从0到1 (四)第五节: ...

  3. Angular 2.0 从0到1 (四)

    第一节:Angular 2.0 从0到1 (一)第二节:Angular 2.0 从0到1 (二)第三节:Angular 2.0 从0到1 (三)第四节:Angular 2.0 从0到1 (四)第五节: ...

  4. Angular 2.0 从0到1 (五)

    第一节:Angular 2.0 从0到1 (一)第二节:Angular 2.0 从0到1 (二)第三节:Angular 2.0 从0到1 (三)第四节:Angular 2.0 从0到1 (四)第五节: ...

  5. ASP.NET2.0自定义控件组件开发 第六章 深入讲解控件的属性

    原文:ASP.NET2.0自定义控件组件开发 第六章 深入讲解控件的属性 深入讲解控件的属性持久化(一) 系列文章链接: ASP.NET自定义控件组件开发 第一章 待续 ASP.NET自定义控件组件开 ...

  6. NHibernate.3.0.Cookbook第一章第六节Handling versioning and concurrency的翻译

    NHibernate.3.0.Cookbook第一章第六节Handling versioning and concurrency的翻译   第一章第二节Mapping a class with XML ...

  7. Oracle Database 11g Release 2(11.2.0.3.0) RAC On Redhat Linux 5.8 Using Vmware Workstation 9.0

    一,简介 二,配置虚拟机 1,创建虚拟机 (1)添加三块儿网卡:   主节点 二节点 eth0:    公网  192.168.1.20/24   NAT eth0:    公网  192.168.1 ...

  8. Oracle_RAC数据库GI的PSU升级(11.2.0.4.0到11.2.0.4.8)

    Oracle_RAC数据库GI的PSU升级(11.2.0.4.0到11.2.0.4.8) 本次演示为升级oracle rac数据库,用GI的psu升级,从11.2.0.4.0升级到11.2.0.4.8 ...

  9. paip.jdk1.4 1.5(5.0) 1.6(6.0) 7.0 8.0特点比较与不同

    paip.jdk1.4 1.5(5.0)  1.6(6.0) 7.0   8.0特点比较与不同 作者Attilax ,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地 ...

随机推荐

  1. Tomcat参数配置

    一.调整JVM参数 JAVA_OPTS= -server -Xms256m -Xmx1024m 注: -server: 启用服务器模式 一定要作为第一个参数,如果CPU多,服务器机建议使用此项 -Xm ...

  2. ALAsset,ALAssetsLibrary,ALAssetsgroup常见属性及用法

    转载自  http://www.cnblogs.com/javawebsoa/archive/2013/07/19/3201246.html ALAssetsgroup --------------- ...

  3. [置顶] 函数传递不定参数理解-c语言

    感性认识 Typedef char *va_list;/*这个在<stdatg.h>中有定义*/ #define va_start(ap,p) (ap=(char*)(&(p)+1 ...

  4. 存储过程[st_MES_RptInspectShipment]

    USE [ChangHong_612]GO/****** Object: StoredProcedure [dbo].[st_MES_RptInspectShipment] Script Date: ...

  5. java读取properties的工具类PropertiesUtil

    package org.properties.util; import java.io.FileInputStream; import java.io.FileOutputStream; import ...

  6. CSS关键字

    1.initial initial 关键字用于设置 CSS 属性为它的默认值. initial 关键字可用于任何 HTML 元素上的任何 CSS 属性. 版本: CSS3 JavaScript 语法: ...

  7. Excel设置数据有效性实现单元格下拉菜单的3种方法(转)

    http://blog.csdn.net/cdefu/article/details/4129136 一.直接输入: 1.选择要设置的单元格,譬如A1单元格: 2.选择菜单栏的“数据”→“有效性”→出 ...

  8. 微信公众平台Token验证失败的解决办法

    微信公众平台Token验证失败的解决办法 1.可查看url和token是否正确 2.查看服务器端口是否为80端口 3.你可以通过记录log日志来判断是否接受到微信提交过来的信息 1.$fp=fopen ...

  9. python常用功能总结

    经常写python,但很多小的点都记不住,每用必查,总结下来,下次查自己的吧. 1.时间获取: import time print  time.strftime("%Y-%m-%d %H:% ...

  10. C++ ADO 数据查询

    ADO 数据查询 关键点 上1条 下1条 第1条 最后1条 实现过程 // stdafx.h : include file for standard system include files, #im ...