最近在看angular4,随手记的一些angular的随笔。
新上手或者准备学angular的可以用作参考,第一次写博客,笔记不算完整,若老司机发现有不足指出请指出。
技术交流群:513590751
Angular程序架构
组件
是Angular应用的基本构件块、可以把一个组件理解为一段带有业务逻辑和数据的html
指令 详细指令速查
允许向html添加自定义行为
模块
用来将应用中不同的部分组成一个Angular框架可以理解的单元
服务
用来封装可重用的业务逻辑
组件
在组件上声明模版变量
#varName
组件元数据装饰器
用来元数据附加到typescript的类上面 从而让angular把这个类识别为组件
## selecter:css的选择器,标志可以根据该选择器作为html标签
## templateUrl:指定了html文件作为组件的模版
## styleUrls:指定了组件模版中的样式
控制器
控制器是指被@component()装饰的类
包含与模版相关的所有的属性与方法
与页面相关的大部分逻辑都是编写在控制器里面
模块
@NgModule装饰器
用@NgModule装饰器生成一个模块
## 用declarations声明了模块中有什么东西(只能声明组件、指令和管道)
## 用imports声明了模块依赖的其他模块
## 用providers声明模块中提供什么服务(此处只能声明服务)
## 用bootstrap声明了模块的主组件
## 在模块中声明服务,在所有组件中都可以使用,在组件中声明服务,只能在组件中使用(每个想要被注入的服务都需要在服务提供其中声明)
新建一个Angualr项目
用angular-cli创建一个angular项目
创建:ng new my-app
启动:ng serve [--open]
--open:在浏览器打开
--routing:生成一个带route的项目
生成组件:ng g component [componentName]
生成服务:ng g service [serviceName]
生成管道:ng g pipe [pipeName]
生成指令:ng g directive [directiveName]
Angular router
名称 | 简介 |
---|---|
Routes | 路由配置,保存着哪个URL对应展示哪个组件,以及在哪个RouterOutlet中展示组件 |
Routerlet | 在HTML中标记路由内容呈现位置的占位符指令 |
Router | 负责在运行时执行路由的对象,可以通过调用其navigate()和navigateByUrl()方法来导航到一个指定的路由 |
RouterLink | 在HTML中声明路由导航用的指令 |
ActivatedRoute | 当前激活的路由对象,保存着当前路由的信息,如路由地址,路由参数等 |
路由配置
path:路由URL
component:路由对应的组件
{path:"**",component:Page404Component} //404页面,放到所有路由的最后
children:[{path:"aaa",component:AaaComponent}]子路由
redirectTo:重定向的URL
pathMatch:指定匹配方式,匹配path中的 '',(full完全匹配,prefix匹配前缀)
loadChildren:延迟加载
canActivate:[class] //此处调用的类需要在providers里面声明,该类实现了CanActivate类 implements CanActivate
canDeactivate:[class] //此处调用的类需要在providers里面声明,该类实现了CanDeactivate类 implements CanDeactivate<component>
resolve:{param1:value1,params2:value2} //此处调用的类需要在providers里面声明,该类实现了Resolve类 implements Resolve<component>
路由时传递参数
传参:
方式1:/product?id=1&name=2
方式2:{path:/product/:id} => /product/1
方式3:{path:/product,component:ProductComponent,data:[{flag:true}]}
取值:ActivatedRoute.queryParams[id] //适用于方式1
ActivatedRoute.params[id] //适用于方式2
ActivatedRoute.data[0][flag] //适用于方式3
参数快照
export class ProductComponent implements OnInit {
private name;
private id;
constructor(private activatedRoute: ActivatedRoute) {
}
ngOnInit() {
// 订阅方式 如果会有组件调用本身的情况就使用订阅方式
this.activatedRoute.params.subscribe((params: Params ) => this.id = params["id"] );
// 快照方式
this.name = this.activatedRoute.snapshot.queryParams["name"];
this.id = this.activatedRoute.snapshot.params["id"];
}
}
辅助路由
<router-outlet></router-outlet>
<router-outlet name="aux"></router-outlet>
{path:"xxx",component:XxxComponent,outlet:"aux"}
{path:"yyy",component:YyyComponent,outlet:"aux"}
<a [routerLink]="[{outlets:{aux:null}}]">xxx</a>
<a [routerLink]="[{outlets:{primary:'home',aux:'yyy'}}]">yyy</a>
primary:控制主路由
路由守卫
CanActivate:处理导航到某路由的情况。
CanDeactivate:处理当前路由离开的情况。
Resolve:在路由激活之前获取路由数据。
依赖注入
在providers中注册接口
在需要引用的类的构造方法的参数中注入
constructor(private params1:Params){}
当使用同一个接口不同的实现类时,在providers中声明providers: [{
provide: Params,
useClass: AnotherParams
}]
使用工厂类确定使用哪个类时,在providers中声明providers:[{
provide: Params,
useFactory: () => {
let depClass = new DepClass(); // 此处工厂方法和DepClass类耦合
let isLogin = Math.random() > 0.5; // 可能还依赖一个外部的变量来判断需要创建哪个类
//在这里返回要使用的类型 注意:工厂方法只会在创建第一个需要注入的对象的时候被调用一次
}
},DepClass]
// 为工厂方法解耦合
使用deps属性来声明工厂方法依赖的类和变量的provide属性,在providers中声明providers:[{
provide: Params,
useFactory: (depClass:DepClass,isLogin) => {
//在这里返回要使用的类型
},
deps: [DepClass,"IS_LOGIN_ENV"]
},DepClass,{
provide:"IS_LOGIN_ENV",
useValue:false
}]
useValue的值可以是基本类型,也可以是对象类型
angular会吧工厂方法依赖的DepClass类注入到工厂方法
数据绑定
使用插值表达式
<h1>{{productTitle}}</h1>
使用方括号将html标签的一个属性绑定到一个表达式上
<img [src]="imgUrl" />
<img src="{{imgUrl}}" />
使用小括号将组件控制器的一个方法绑定为模版上一个事件的处理器 $event(浏览器事件对象)指向触发事件的dom元素
<button (click)="clickEvent($event)">绑定事件</button>
dom属性
事件中使用 event.target.value 获取的值是html元素的dom属性(dom属性的值是会发生变化的)
html属性
使用 event.target.getAttribute("value") 获取的值是html元素的初始值(html属性是不会发生变化的)
<div class="aaa bbb" [class]="ccc">这种方式会替换原class属性的值</div>
<div class="aaa bbb" [class.ccc]="true">这种方式不会替换原class属性的值</div>
<div [ngClass]="{aaa:isTrue,bbb:isShow}">这种方式可以控制多个属性的显示</div>
双向数据绑定
<div [(ngModel)]="name">双向数据绑定</div>
管道
{{name | filter}}
eg:{{birthday | data}}// 将生日转化成日期格式
eg:{{birthday | data:'yyyy-MM-dd HH:mm:ss'}}// 将生日转化成指定日期格式
自定义管道
在项目中生成一个管道,然后在管道类的transform方法中对值进行操作。
export class PipeName implements PipeTransform {
transform(value: any, arg: any): any {
// value 是要在管道做处理的值
// arg 是管道后面跟着的参数
}
}
响应式编程
1、在项目模块中引入ReactiveFormsModule模块
2、在组件中声明FormControl类型的字段
eg:formControlName
3、在页面中控件上声明[formControl]="formControlName"
4、在组件中订阅formControlName的valueChanges事件
eg:this.formControlName.valueChanges.subscribe(value => this.keyword = value);
组件间通讯
组件的输入输出属性
输入属性
在需要注入的属性上用@Input注解
在父组件中用[propName]="value"来赋值
输出属性
在子组件属性上用@Output解属性类型为EventEmitter<DataType>类型
@Output()
prop:EventEmitter<DataType> = new EventEmitter();
用 prop.emit(dataTypeObj)
在父组件中声明dataTypeObj:DataType = DataType();//用来存放子组件Output出来的属性
在父组件引用子组件的标签上用事件订阅来订阅自组建发射的事件
用<child-comp (prop)="propHandler($event);"></child-comp>
// 监控的事件名 prop 和@Output中的参数一致,不传参时默认和属性名一致
在父组件中声明
propHandler(event:DataType){
// 把子组件Output出来的属性赋值到父组件的属性上
this.dataTypeObj = event;
}
使用中间人模式传递数据
两个子组件,通过@Output数据到父组件和@Input从父组件接收数据来实现组件间通讯,父组件为中间人
组件生命周期以及angular的变化发现机制
组件钩子:
想要使用这些钩子,需要先实现对应的接口
被调用一次的钩子
constructor(组件构造方法,调用该方法时,组件的输入属性没有值)
ngOnInit(初始化组件或指令,调用该方法时,OnChanges方法已被调用,组件的输入属性有值)
ngAfterContentInit(和angular的内容投影相关的)
ngAfterViewInit(和angular的视图初始化和检查相关的,在此方法不可修改组件的属性)
ngOnDestroy(组件销毁,在路由到其他组件时当前组件被销毁)
被调用多次的钩子
ngOnChanges(父组件初始化或修改子组件的输入属性的值的时候被调用,如果一个方法没有输入属性,则该方法不会被调用)
ngDoCheck(用来检测,在每个angular的变更检测周期调用)
ngAfterContentChecked(和angular的内容投影相关的)
ngAfterViewChecked(和angular的视图初始化和检查相关的,在此方法不可修改组件的属性)
调用顺序:
constructor、ngOnChanges、ngOnInit、ngDoCheck、ngAfterContentInit、ngAfterContentChecked、ngAfterViewInit、ngAfterViewChecked、ngAfterContentChecked、ngAfterViewChecked
父组件调用子组件的方法
#在父组件的模块中调用子组件的方法
1、在子组件上声明模版变量 #childName
2、在父组件中声明一个类型为 ChildeComponent 的变量 child
3、用 @ViewChild("childName") 注解声明的变量 child
4、在代码块中用 this.child.methodName(args) 来调用子组件的方法
#在父组件的模版中调用子组件的方法
1、在子组件上声明模版变量 #childName
2、在父组件的模版中绑定事件 (click)="childName.methodName('args')"
父组件内容投影到子组件中
#父组件
<div>
<child-comp>
<div class="header">这是头部</div>
<div class="footer">这是底部</div>
</child-comp>
</div>
#子组件
<div>
<ng-content select=".header"></ng-content>
<ng-content select=".footer"></ng-content>
</div>
定义单个投影点时可以不写class属性和select属性
表单处理
模版式表单
表单的数据模型是通过组件模版中的相关指令来定义的,因为使用这种方式定义表单的数据模型时,我们会受限与HTML的语法,所以,末班驱动方式只是用于一些简单的场景
可用指令
NgForm
NgModel
NgModelGroup
用#myForm来声明表单的模版变量,在表单的onSubmit="onSubmit(myForm.value,myForm.valid)"来传入表单的值和表单是否通过验证
生成指令
@Directive({
selecter: "[dirName]", // 在html上作为属性使用
providers: [{provide: NG_VALIDATORS, useValue: mobileValidator, multi: true}] // mobileValidator是已经声明的验证器
})
响应式表单
使用响应式表单时,通过编写TypeScrtipt代码而不是HTML代码来创建一个底层的数据模型,在这个模型定义好以后,使用一些特定的指令,将模版上的HTML元素与底层的数据模型连接在一起
可用对象
FormControl
userinfo: FormControl = new FormControl("aaa");
FormGroup
FormGroup是多个FormControl的集合
FormArray
使用
方式一、
formModel: FormGroup = new FormGroup({
username: new FormControl("初始值"),
password: new FormControl(),
email: new FormArray([
"a",
"b"
])
});
方式二、
constructor(fb: FormBuilder){
this.formModel = fb.group({
username: ["初始值", Validators.required], // 第二个参数是校验器,可以传一个数组,第三个参数是异步校验器
password: [""],
email: fb.array([
"a",
"b"
])
});
}
<!-- 使用 novalidator 阻止浏览器默认的表单验证如:(required) -->
<form [formGroup]="formModel" novalidator>
<!-- required 验证表单是否为空 -->
<input type="text" ngModel required formControlName="username"/>
<!-- 第一个参数是校验器返回的错误对象的key,第二个参数是校验的字段 -->
<div [hidden]="formModel.hasError('required','username')">用户名不能为空</div>
<input type="text" formControlName="password"/>
<div *ngFor="let e of email;let i = index;" formArrayName="email">
<input type="text" [formControlName]="i"/>
</div>
</form>
表单验证
校验器
funcName(control: AbstractControl) {[key: string]: any} {
return null;
}
eg:
mobileValidator(control: FormControl) any {
let valid = true;
return valid ? null : {mobile : true};
}
constructor(fb: FormBuilder){
this.formModel = fb.group({
mobile: ["", this.mobileValidator]
});
}
onSubmit() {
let isValid:boolean = this.formModel.get("username").valid; // 获取字段是否通过验证
let error:any = this.formModel.get("username").errors; //获取字段未通过验证的错误信息
this.formModel.valid; // 用来判断表单是否合法
}
异步校验器
异步校验器作为字段构造的第三个参数传入
funcName(control: AbstractControl) {[key: string]: any} {
// 返回的对象用Observable.of方法包裹,delay延迟5s
return Observable.of({}).delay(5000);
}
eg:
mobileAnsycValidator(control: FormControl) any {
let valid = true;
return Observable.of(valid ? null : {mobile : true}).delay(5000);
}
constructor(fb: FormBuilder){
this.formModel = fb.group({
mobile: ["", this.mobileValidator, this.mobileAnsycValidator]
});
}
与服务器通讯
引入Http模块:
1、import Http from "@angular/http";
2、在cunstructor方法中注入:cunstructor(http: Http)
3、使用this.http.get("url",data)获取数据,并且用.map(res => res.json())把获取到的数据转换成json格式
4、用Observable类型的变量接收数据
5、订阅Observable类型的变量,并且用obs.subscribe(data => this.product = data)来赋值给变量
proxy.conf.json配置
// 意指当前请求是以/api开头时,把请求转发到http://loacalhost:8000
{
"/api": {
"target": "http://loacalhost:8000"
}
}
// 在package.json中配置
ng serve 命令添加参数 --proxy-config proxy.conf.json
使用websocket协议与后台通信
service
import {Injectable} from '@angular/core';
import {Observable} from "rxjs/Observable";
@Injectable()
export class WebSocketService {
ws: WebSocket;
constructor() {
}
// 根据传入的url创建一个websocket协议
createObservableScoket(url: string): Observable<any> {
// 创建websocket服务
this.ws = new WebSocket(url);
return new Observable(observer => {
// 返回成功时执行的方法
this.ws.onmessage = event => observer.next(event.data);
// 返回错误时执行的方法
this.ws.onerror = event => observer.error(event);
// 关闭websocket流时执行的方法
this.ws.onclose = event => observer.complete();
});
}
sendMessage(msg: string) {
this.ws.send(msg);
}
}
component
export class SearchComponent implements OnInit {
constructor(private wsService: WebSocketService) {
}
ngOnInit() {
// 订阅websocket返回的值
this.wsService.createObservableScoket("ws://localhost:8085").subscribe(
data => console.log(data),
error => console.log(error),
() => console.log("webSocket已结束!")
);
}
}