一.课程简介 (注意:这里的AngularJS指的是2.0以下的版本)
AngularJS的优点:
- 模板功能强大丰富
- 比较完善的前端MVC框架
- 引入了Java的一些概念:比如,依赖注入,单元测试等
AngularJS的一些问题:
- 性能
- 路由
- 作用域
- 表单验证
- JavaScript语言本身问题
- 学习成本高
Angular(指的4.0版本)新特性
- 全新的命令行工具 AngularCLI: 比如,生成一个新项目的骨架,或者生成我们需要的组件的基础代码;或者作为一个开发服务器供我们调试、编译、构建、部署代码、运行自动化的单元测试,等等.
- 服务器端渲染: 他可以使一个原本需要10s才能加载玩的单页应用,在1s之内呈现在用户面前,他可以使一个单页应用针对每一个视图去做
seo
优化. - 移动和桌面兼容: 可以使用
Ionic
,React-Native
之类的框架
AngularJS架构
Angular架构
Angular与其他流行前端框架的比较
- React优点
- 速度快: 虚拟dom (Angular也采用了类似的算法,现在速度与react不相上下了)
- FLUX架构 (Angular现在也已具备)
- 服务器渲染 (Angular现在也已具备)
- Vue对比 之 优点
- 简单
- 灵活
- 性能:(大小只有十几kb,Angular得有50多kb)
- Vue缺点
- 个人主导
- 只关注web
- 只能依靠第三方框架来实现服务器渲染
Angular 继承了AngularJS原有的优点,并且吸收了React
以及其他框架的优点,祛除了他们的缺点,形成了一套全新的框架.
Angular
二. 开始Angular开发
-
Angular程序架构
-
搭建环境
- 安装
node.js
node.js官网 - 安装 angular-cli
sudo cnpm install -g @angular/cli
>>>>由于npm总是安装不成功,故使用了淘宝的镜像 - 检查 angular-cli 是否安装成功
ng -v
angular/cli 安装成功 - 创建项目
ng new xxx项目名字
- 创建带路由的项目
ng new xxx --routing
- 启动项目
ng serve --open
- 创建组件
ng g component xxx组件
- 创建一个文件夹并创建组件
ng g component stock/stockManage
在stock文件夹下生成一个stockManage组件
- 安装
项目目录
项目目录
component
组件组成的必备元素
组件的必备元素
其他笔记
- 声明的数组,在
push
之前要初始化下
export class StarsComponent implements OnInit {
// 外面注入进来的
@Input()
rating: number = 0;
stars: boolean[];
constructor() {
}
ngOnInit() {
// 数组要初始化一下
this.stars = [];
for (let i = 1; i <= 5; i++) {
this.stars.push(i > this.rating);
}
}
}
三.使用Angular Route导航
- 路由的基础知识
新建一个导航的项目:ng new router --routing
- 在路由时传递参数
-
在查询参数中传递数据
<a routerLink="/stock" [queryParams]="{id:1}">股票详情</a>
当点击时,导航栏地址中就带上了
在路由时传递参数id
参数
在跳转到组件中接收这个参数export class StockComponent implements OnInit { private stockId: number; constructor(private routeInfo: ActivatedRoute) { } ngOnInit() { this.stockId = this.routeInfo.snapshot.queryParams['id']; } }
在html中调用
执行结果<p> 这里是股票信息组件 </p> <p> 股票ID是{{stockId}} </p>
-
在路由路径中传递参数
①. 在路由中进行配置// 路由配置 const routes: Routes = [ { path:'', component: HomeComponent }, { path:'stock/:id', component: StockComponent }, // 注意: 通配符路由的配置一定要放在最下面 { path:'**',// **(通配符)表示任何路径都可以来匹配 component: Code404Component } ];
②.修改路由连接
<a [routerLink]="['/stock',1]">股票详情</a>
③.修改要跳珠组件的
ts
文件export class StockComponent implements OnInit { private stockId: number; constructor(private routeInfo: ActivatedRoute) { } ngOnInit() { this.stockId = this.routeInfo.snapshot.params['id']; } }
④.显示效果
在路由路径中传递参数注意:当两个跳转到的组件一样时,会因为路由快照的原因,导致路由传递的参数在组件内部不能显示出来;
解决办法:在该组件的
ts
文件中export class StockComponent implements OnInit { private stockId: number; constructor(private routeInfo: ActivatedRoute) { } ngOnInit() { //解决路由快照的问题 //每当参数改变时,下面的方法都会被调用一次 this.routeInfo.params.subscribe((params: Params)=> this.stockId = params['id']); this.stockId = this.routeInfo.snapshot.params['id']; } }
-
在路由配置中传递参数
①.在路由配置中设置静态数据const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'stock/:id', component: StockComponent, // 配置一些静态数据 data: [{ isPro: true }] } ]
②.在要跳转到的组件
ts
文件中export class StockComponent implements OnInit { private stockId: number; private isPro: boolean; constructor(private routeInfo: ActivatedRoute) { } ngOnInit() { //解决路由快照的问题 //每当参数改变时,下面的方法都会被调用一次 this.routeInfo.params.subscribe((params: Params) => this.stockId = params['id']); this.stockId = this.routeInfo.snapshot.params['id']; this.isPro = this.routeInfo.snapshot.data[0]['isPro']; } }
③.
html
文件中<p> 这里是股票信息组件 </p> <p> 股票ID是{{stockId}} </p> <p> isPro是{{isPro}} </p>
④.显示效果
在路由配置中传递参数
-
- 重定向路由
在用户访问一个特定的地址时,将其重定向到另一个指定的地址.
比如:将http://localhost:4200
重定向到http://localhost:4200/home
const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' // pathMatch: full,路由器将应用重定向,当且仅当导航到“/”。 }, { path: 'home', component: HomeComponent } ]
- 子路由
①.在路由配置中,在children
中配置子路由
②. 在子路的上一级路由的组件中,设置{ path: 'stock/:id', component: StockComponent, // 配置一些静态数据 data: [{ isPro: true }], children:[ { path:'', component:BuyerListComponent }, { path:'seller/:id', // 传递参数 component:SellerListComponent } ] },
routerLink
和插槽
<!-- ./ : 在当前路由下面去找 --> <a [routerLink]="['./']">买家列表</a> <a [routerLink]="['./seller', 100]">卖家列表</a> <!--插座--> <router-outlet></router-outlet>
③.显示效果
子路由 - 辅助路由
具体参考路由demo - 路由守卫
-
CanActivate
: 处理导航到某路由的情况.
使用场景:是否有权限进入到某个路由组件
①.路由配置{ path: 'stock/:id', component: StockComponent, // 路由守卫 canActivate: [PermissionGuard] },
②.在
permission.guard.ts
文件中,配置权限import {CanActivate} from "@angular/router"; export class PermissionGuard implements CanActivate { canActivate() { var hasPermission: boolean = Math.random() < 0.5; if (!hasPermission){ console.log("用户无权限访问此股票详情"); } return hasPermission; // true 可以进入, false不可以进入 } }
当
hasPermission
为true时,可以跳转到stock
路由,为false
时,被拦截,不可以进入! -
CanDeactivate
: 处理从当前路由离开的情况.
具体参考路由demo. -
Resolve
: 在路由激活之前获取路由数据.
具体参考路由router2.
-
四. 依赖注入
-
什么是依赖注入模式及使用依赖注入的好处
依赖注入: Dependency Injection 简称 DI 。
控制反转: Inversion of Control 简称 IOC 。使用依赖注入的好处 : 依赖注入会帮你以一种松耦合的方式编写代码,使代码可测性和重用性更高!
-
介绍Angular的依赖注入实现:注入器和提供器
-
注入器的层级关系
-
<app-stars [rating]="stock?.rating"></app-stars>
//stock?.rating
=> 当stock
存在时,才去访问rating
属性
五. 数据绑定、响应式编程和管道。
-
数据绑定
数据绑定-
事件绑定
事件绑定
HTML属性和DOM属性的关系
- 少量 HTML 属性 和 DOM 属性 之间有着 1:1的映射,如
id
。 - 有些 HTML 属性 没有对应的 DOM 属性, 如
colspan
。 - 有些 DOM 属性 没有对应的 HTML 属性,如
textContent
。 - 就算名字相同, HTML 属性 和 DOM属性 也不是同一样东西。
- HTML 属性的值指定了初始值;DOM属性的值表示当前值。
DOM 属性 的值可以改变;HTML 属性的值不能改变。 - 模板绑定是通过 DOM属性 和事件来工作的,而不是 HTML 属性。
HTML属性绑定
-
基本Html属性绑定:
基本Html属性绑定.png -
CSS类绑定:
CSS类绑定 -
样式绑定
样式绑定
-
-
双向绑定
[(ngModel)]="name"
[()] 盒子里面装香蕉
<input [(ngModel)]="name">
- 响应式编程 :
异步数据流编程
- 观察者模式与
Rxjs
Observable : 被观察者
响应式编程 - 观察者模式与
先导入
import {Observable} from "rxjs";
//被观察者
Observable.from([1, 2, 3, 4])
.filter(e => e % 2 === 0)
.map(e => e * e)
//订阅 (观察者)
.subscribe(
//观察者
e => console.log(e), // 4 , 6
err => console.error(err),
() => console.log('结束了') // 结束了
);
观察者后两个回调方法是可选的
err => console.log(err),
() => console.log('结束了') // 结束了
-
模板本地变量
#xxxx
用#
来声明<input #myField (keyup)="onKey(myField.value)"> ts中代码 onKey(value) { console.log(value); }
等同下面的传统方式
//$event 事件对象 keyup键盘点击 <input (keyup)="onkey2($event)"> ts中代码 onkey2(event) { console.log(`我是来打怪兽的==${event.target.value}`); }
-
响应式编程栗子
html文件中<input [formControl]="searchInput"> // 绑定formControl属性
在
app.module.ts
中引入ReactiveFormsModule
imports: [ BrowserModule, ReactiveFormsModule ],
ts文件中
先导入import {Observable} from "rxjs"; import {FormControl} from "@angular/forms"; import 'rxjs/Rx';
具体实现
export class BindComponent implements OnInit { searchInput: FormControl = new FormControl(); constructor() { this.searchInput.valueChanges // 500ms没有收到新的事件,才往下执行 .debounceTime(500) .subscribe(stockCode => this.getStockInfo(stockCode)); } ngOnInit() { } getStockInfo(value) { console.log(value); } }
实现总结:当用户在搜索框中一直输入内容,此时输入框不会触发搜索功能,当用户输入停下来,超过500ms才会执行搜索功能!
-
管道 : 负责处理原始值到显示值的转换.比如(GMT时间转换)
管道符号:
|
Angular4中常用管道 摘抄参考文章
-
①. 大小写转换管道
uppercase
: 将字符串转换为大写
lowercase
: 将字符串转换为小写<p>将字符串转换为大写{{str | uppercase}}</p>
str:string = 'hello'
页面上会显示
将字符串转换为大写HELLO -
②. 日期管道
date。日期管道符可以接受参数,用来规定输出日期的格式。<p>现在的时间是{{today | date:'yyyy-MM-dd HH:mm:ss'}}</p>
today:Date = new Date();
页面上会显示现在的时间是2017年12月05日14时57分53秒
-
③. 小数管道
number管道用来将数字处理为我们需要的小数格式
接收的参数格式为{最少整数位数}.{最少小数位数}-{最多小数位数}
其中最少整数位数默认为1
最少小数位数默认为0
最多小数位数默认为3
当小数位数少于规定的{最少小数位数}时,会自动补0
当小数位数多于规定的{最多小数位数}时,会四舍五入<p>圆周率是{{pi | number:'2.2-4'}}</p>
pi:number = 3.14159;
页面上会显示
圆周率是03.1416 -
④. async 管道:处理异步流
-
⑤. 自定义管道
执行命令ng g pipe pipe/xxx
创建xxx管道
在xxx.pipe.ts
文件中:import {Pipe, PipeTransform} from '@angular/core'; // Pipe 管道装饰器 @Pipe({ name: 'multiple' // 管道的名字,可以任意定义 }) export class MultiplePipe implements PipeTransform { transform(value: number, args?: number): any { // value: 管道输入进来的原始的值 // args 可选参数,管道后面跟的 date:'yyyy-MM-dd HH:mm:ss'等 // 返回 原始值 乘以 参数值 if (!args) { args = 1; } return value * args; } }
在
html
中调用<p>自定义管道{{size | multiple:'4'}}</p>
执行结果为: 28
-
-
indexOf() 包含
return list.filter(item => { const itemFieldValue = item[field].toLowerCase(); return itemFieldValue.indexOf(keyword) >= 0; // indexOf()包含 });
六.组件间通讯
-
组件的输入输出属性
- 输入属性 : 适用于
父传子
需要使用@Input()
修饰
这样在父子组件嵌套的情况下,父组件给子组件传值export class StockSearchComponent implements OnInit { // @Input() 修饰 keyWord 属性是个输入属性 @Input() private keyWord: string; constructor() { } ngOnInit() { setInterval(() => { this.keyWord = 'xxxx'; }, 3000); } }
<div> <input placeholder="请输入搜索关键字" [(ngModel)]="search"> <app-stock-search [keyWord]="search" ></app-stock-search> </div>
- 输出属性: 适用于
子传父
在子组件中先引入
声明属性import { EventEmitter } from '@angular/core';
发射事件@Output() // 装饰器没有名字 searchResult: EventEmitter<StockInfo> = new EventEmitter(); @Output('lastPrice') // 也可以给装饰器起名字 searchResult: EventEmitter<StockInfo> = new EventEmitter();
在父组件中 事件绑定this.searchResult.emit(stockInfo);
(lastPrice)="xxx($event)"
在父组件ts文件中实现如果装饰器没有名字 <app-stock-search (searchResult)="searchResultHandle($event)"></app-stock-search> 装饰器有名字 lastPrice <app-stock-search (lastPrice)="searchResultHandle($event)"></app-stock-search>
在父组件中显示searchResultHandle(stockInfo: StockInfo) { this.currentPrice = stockInfo.price; }
<div> 当前价格是{{currentPrice | number:'2.2-2'}} </div>
- 输入属性 : 适用于
-
使用中间人模式传递数据
中间人模式
两兄弟间的数据传递,由这两个兄弟所在的父组件充当中间人的角色
如上图,son1
的一个事件想把值传递到兄弟组件son2
中,
现在son1
组件中声明事件属性@Output() addCart: EventEmitter<StockInfo> = new EventEmitter();
然后发送事件
buyStock() { this.addCart.emit(new StockInfo(this.keyWord, this.price)); }
在父组件
father
中,调用该组件,并实现addCartHandler
方法<son1 (addCart)="addCartHandler($event)"></son1 >
private stockInfo: StockInfo; addCartHandler(stockInfo: StockInfo) { this.stockInfo = stockInfo; }
father
组件再将数据传递给son2
<son2 [stockInfo]="stockInfo"></son2 >
以上就是中间人模式
-
组件生命周期以及angular的变化发现机制
生命周期: 红色部分只会被调用一次 -
生命周期的执行顺序
生命周期的执行顺序 -
ngOnChanges
钩子: 输入属性发生变化的时候调用;
当组件有@Input()
属性时,该属性的初始化是在ngOnChanges
中执行的.所以当组件依赖外部输入进来的属性时,它的初始化最好在ngOnInit
钩子里面,而不要写在构造函数里面.
在父组件中调用子组件的方法
-
方法一
在父组件中 ,声明一个模板变量
<app-child #child1></app-child>
在父组件ts
文件中,使用@ViewChild()
装饰器
export class AppComponent implements OnInit {
/**
* 在父组件 AppComponent 里面,获得子组件的引用
*/
@ViewChild('child1') // 通过模板变量的名字child1,找到了对应的子组件,并将child1赋值给了下面的child1变量
child1: ChildComponent;
ngOnInit(): void {
// 调用子组件的方法
this.child1.greeting('Tom');
}
}
-
方法二
在父组件的模板上调用子组件的方法
<!-- 先声明模板变量-->
<app-child #child2></app-child>
<!--在父组件的模板上调用子组件的方法-->
<button (click)="child2.greeting('Jerry')">调用child2的greeting方法</button>
-
ngAfterViewInit()
和ngAfterViewChecked()
钩子
ngAfterViewInit()
和ngAfterViewChecked()
生命周期是在组件的视图被组装完毕以后调用的;
如果组件有子组件, 那么只有当所有子组件的视图组装完毕以后,父组件的这两个方法才会被调用;
不要在这两个方法里改变视图中绑定的东西.如果想改他,也要写在一个定时器(setTimeout)里面,不然会抛出异常;
ngAfterViewInit() 先调用, ngAfterViewChecked()后调用 -
ng-content 投影
类似于
Vue
的插槽
在子组件定义一个投影<div style="border:1px solid red"> <!--使用 ng-content 组件标记投影点--> <ng-content></ng-content> </div>
当其父组件调用该子组件时,可以往投影上传递任意内容
<app-red-border> <div> 我是投影里的内容 </div> </app-red-border>
当然也传递多个投影,并指定每个投影的位置
在父组件调用子组件时,给投影设置
class
<app-red-border> <div class="red"> 我是红框里面的内容 </div> <div class="green"> 我是绿框里面的内容 </div> </app-red-border>
子组件中给设置多个投影位置,并给
ng-content
设置select
属性为父组件中投影的class
,<div style="border:1px solid red"> <!--使用 ng-content 组件标记投影点--> <ng-content select=".red"></ng-content> </div> <div style="border:1px solid green"> <ng-content select=".green"></ng-content> </div>
-
ngAfterContentInit()
和ngAfterContentChecked()
钩子
这两个钩子针对的是视图中父组件投影进来的那部分内容的;
ngAfterContentInit()
:投影进来的内容初始化完毕调用;
ngAfterContentChecked()
:专门针对投影进来的内容,做变更检测的; -
ngOnDestroy
组件销毁
当切换路由时,会调用该组件销毁钩子; -
小结:
①.父子组件之间应该避免直接访问彼此的内部,而应该通过输入输出属性来通讯;
②.组件可以通过输出属性发射自定义事件,这些事件可以携带任何你想携带的数据;
③.在没有父子关系的组件之间,尽量使用中间人模式进行通讯;
④.父组件可以在运行时投影一个或多个模板片段到子组件中;
每个Angular
组件都提供了一组生命周期钩子,供你在某些特定的事件发生时执行相应的逻辑;
⑤.Angular
的变更检测机制会监控组件属性的变化并自动更新视图.这个检测非常频繁并且默认是针对整个组件树的,所以实现相关钩子时要慎重;
⑥.你可以标记你的组件树中的一个分支,使其被排除在变更检测机制之外.
七. 表单处理
-
模板式表单 (需要引入:
FormsModule
)
表单的数据模型是通过组件模板的相关指令来定义的,因为使用这种方式定义表单的数据模型时,我们会受限于HTML的语法,所以,模板驱动方式只适用于一些简单的场景. -
响应式表单 (需要引入:
ReactiveFormsModule
)
使用响应式表单时,你通过编写TypeScript
代码而不是Html
代码来创建一个底层的数据模型,在这个模型定义好以后,你使用一些特定的指令,将模板上的html
元素与底层的数据模型连接在一起. -
响应式表单 和 模板式表单的不同点
- 不管是哪种表单,都有一个对应的数据模型来存储表单的数据. 在模板式表单中,数据模型是由
angular
基于你组件模板中的指令隐式创建的. 而在响应式表单中,你通过编码明确的创建数据模型,然后将模板上的html
元素与底层的数据模型连接在一起. - 数据模型并不是一个任意的对象, 它是一个由angular/forms模块中的一些特定类,如:
FormControl
,FormGroup
,FormArray
等组成的. 在模板式表单中, 你是不能直接访问到这些类的. - 响应式表单并不会替你生成
HTML
, 模板仍然需要你自己来编写.
- 不管是哪种表单,都有一个对应的数据模型来存储表单的数据. 在模板式表单中,数据模型是由
-
模板式表单例子
模板式表单的指令都是ng
开头的,如:ngForm
,ngModel
<form #myForm="ngForm" (ngSubmit)="createUser(myForm.value)">
<div>用户名:<input #myNickName="ngModel" ngModel name="nickname" type="text" pattern="[a-zA-Z0-9]+"></div>
<div>邮箱:<input ngModel name="email" type="email"></div>
<div>手机号:<input ngModel name="mobile" type="number"></div>
<div ngModelGroup="passwordInfo">
<div>密码:<input ngModel name="password" type="password"></div>
<div>确认密码:<input ngModel name="passwordConfirm" type="password"></div>
</div>
<button type="submit">注册</button>
</form>
<br>
<hr>
<br>
<div>
{{myForm.value | json}}
</div>
<br>
<div>
昵称的值是{{myNickName.value}}
</div>
模板式表单栗子
- 响应式表单
响应式表单的指令都是form
开头的,如:formGroup
,formControl
html
中代码
<!--
与后台的formModel绑定;
createUser() 不用传值;
-->
<form [formGroup]="formModel" (submit)="createUser()">
<div>昵称:<input formControlName="nickname" type="text" pattern="[a-zA-Z0-9]+"></div>
<div>邮箱:
<ul formArrayName="emails">
<li *ngFor="let email of formModel.get('emails').controls;let i = index">
<input [formControlName]="I">
</li>
</ul>
<input type="button" (click)="addEmail()" value="添加Email">
</div>
<div>手机号:<input formControlName="mobile" type="number"></div>
<!--与后台的passwordInfo绑定-->
<div formGroupName="passwordInfo">
<div>密码:<input formControlName="password" type="password"></div>
<div>确认密码:<input formControlName="passwordConfirm" type="password"></div>
</div>
<button type="submit">注册</button>
</form>
ts
中代码
import {Component, OnInit} from '@angular/core';
import {FormArray, FormControl, FormGroup} from "@angular/forms";
@Component({
selector: 'app-reactive-form',
templateUrl: './reactive-form.component.html',
styleUrls: ['./reactive-form.component.css']
})
export class ReactiveFormComponent implements OnInit {
// 整个表单所有数据
private formModel: FormGroup;
constructor() {
this.formModel = new FormGroup({
nickname: new FormControl(),
emails: new FormArray([
new FormControl()
]),
mobile: new FormControl(),
passwordInfo: new FormGroup({
password: new FormControl(),
passwordConfirm: new FormControl()
})
});
}
addEmail() {
const emails = this.formModel.get('emails') as FormArray;
emails.push(new FormControl());
}
createUser() {
console.log(this.formModel.value);
}
}
响应式表单
打印结果:
响应式表单打印结果
使用FormBuilder
可简化上面ts
文件中大代码
import {Component, OnInit} from '@angular/core';
import {FormArray, FormBuilder, FormControl, FormGroup} from "@angular/forms";
@Component({
selector: 'app-reactive-form',
templateUrl: './reactive-form.component.html',
styleUrls: ['./reactive-form.component.css']
})
export class ReactiveFormComponent implements OnInit {
// 整个表单所有数据
private formModel: FormGroup;
private fb: FormBuilder = new FormBuilder();
constructor() {
this.formModel = this.fb.group({
nickname: ['xxx'],
emails: this.fb.array([
['']
]),
mobile: [''],
passwordInfo: this.fb.group({
password: [''],
passwordConfirm: ['']
})
});
}
addEmail() {
const emails = this.formModel.get('emails') as FormArray;
emails.push(new FormControl());
}
createUser() {
console.log(this.formModel.value);
}
ngOnInit() {
}
}
网友评论