基础
官网连接(中文):
https://angular.cn/start
简介:angular是由Google维护的一款开源javaScript。Anguar1.5叫做angularJs,Angular4.0称为Angular,Angular1.5到Angular4.0是完全重写。(我们要学的是Angular)
安装、创建及运行:
要求node大于10.9,查看node版本:
node -v
安装angular命令:npm install -g @angular/cli
查看安装的版本命令:ng version
创建项目命令:ng new my-angular
运行项目:ng serve
或者npm start
运行的端口是:http://localhost:4200/
组件component:
创建组件的方法:可以手动新建文件、命令快捷键组件。
其中命令快创建组件的:ng g component 组件名
例如在components目录下创建一个test01的组件:ng g component /components/test01
//test01.component.ts文件内容
// 装饰器 Decorator 用于指定class的用途
// 创建组件class,还得去注册组件class
import { Component } from "@angular/core";
@Component({
// selector: "[test01]", //属性<div test01="xxx"/>
// selector: ".test01", //class使用 <div class="test01">
selector: 'test01', //组件使用 <test01/>
template: '<h1>我自己定义的组件</h1>'
})
export class test01 {
title = "我是自己定义的组件"
}
app.module.ts文件里进行组件的注册:
import { Mycomponent01 } from './mycomponent01'; //引入
然后就可以来到app.component.html使用这个组件了:
<test01></test01>
生命周期:
ngOnchanges:
如果组件绑定过输入属性,那么在 ngOnInit() 之前以及所绑定的一个或多个输入属性的值发生变化时都会调用。
注意,如果你的组件没有输入属性,或者你使用它时没有提供任何输入属性,那么框架就不会调用 ngOnChanges()
ngOnInit:
其实每次routerLink都会执行。(从其他页面跳过来)在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。
在第一轮 ngOnChanges() 完成之后调用,**只调用一次**。
而且即使没有调用过 ngOnChanges(),也仍然会调用 ngOnInit()(比如当模板中没有绑定任何输入属性时)。
ngOnInit() 是组件获取初始数据的好地方,相当于mounted。
ngDoCheck:
检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。
紧跟在每次执行变更检测时的 ngOnChanges() 和 首次执行变更检测时的 ngOnInit() 后调用。
ngAfterContentInit:
当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用。
ngDoCheck() 之后调用,**只调用一次**。
ngAfterContentChecked:
每当 Angular 检查完被投影到组件或指令中的内容之后调用。
ngAfterContentInit() 和每次 ngDoCheck() 之后调用.
例如:
<mycom01><p>{{name}}</p></mycom01>
ngAfterViewInit:
当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用。
ngAfterContentChecked() 之后调用,**只调用一次。**
ngAfterViewChecked:
每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。
ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。
例如:
Template:’<p>{{name}}</p>”
ngOnDestroy:
每当 Angular 每次销毁指令/组件之前调用并清扫。
把清理逻辑放进 ngOnDestroy() 中,这个逻辑就必然会在 Angular 销毁该指令之前运行。
这里是释放资源的地方,这些资源不会自动被垃圾回收。如果你不这样做,就存在内存泄漏的风险。
取消订阅可观察对象和 DOM 事件。
停止 interval 计时器。
反注册该指令在全局或应用服务中注册过的所有回调。
ngOnDestroy() 方法也可以用来通知应用程序的其它部分,该组件即将消失。
数据的绑定:
插值表达式:
可以:
{{age+2}}
{{age>20?true:false}}
逻辑运运算符: || &&
{{ageArr.length}}
{{anme.toUpperCase()}}
不可以:
{{new Date()}}
{{JSON.pase(name)}}
属性绑定:
<p title=”{{name}}”></p> //就像小程序一样
<p [title] = “name” ></p> //或者这样
<p [title]=”’哈哈哈:’+name”>
图片的绑定:
(相对路径:不推荐)
<Img [src]=”’../../assets/img/’+imglist[1].src”>
事件绑定:
<button (click)="handleFn()"> //记住**事件一定要加上括号**
change、blur、keydown、keyup、mouseenter、mousedown、mouseup、dbclick、drag、copy、keyup.enter等
带参数的函数可以,例如:
html文件:
<input type="search" placeholder="请输入要搜索的数据" (keyup.enter)="handleFocus($event)" />
ts文件:
handleFocus(e:any){
console.log(e.target.value)
}
指令绑定:
循环*ngfor:
<!-- html文件 -->
<!-- 例如
testLists = ["html","css","js","ts","nodejs","angular"]
-->
<ol>
<li *ngfor="let item of testLists;index as i;">
<span>下标:{{i}}---值:{{item}}</span>
<span>第一条数据:{{first}}---最后一条数据{{last}}</span>
<span>还有even:boolean偶数就为true、odd:boolean奇数就为true</span>
</li>
</ol>
*ngif:
if判断:
<!-- if -->
<div *ngif="age>=18">你成年了</div>
<!--if else -->
<div *ngif="age>=18;else testChild">你成年了</div>
<ng-template #testChild>你还是小屁孩</ng-template>
if与for不可以一起使用,所以推荐方法container与div一起使用:
<!--
lists = ["wumiao","五秒","加油"]
-->
<ng-container *ngFor="let item of lists;let i = index">
<div *ngIf="i%2==0">
{{item}}-{{i}}
</div>
</ng-container>
ngSwitch:
<!-- switch -->
<!-- 例如:
public a:number = 2
-->
<!-- ---分割线-- -->
<ul [ngSwitch]="a">
<li *ngSwitchCase="1">我是1</li>
<li *ngSwitchCase="2">我是2</li> <!-- 只显示了他 -->
<li *ngSwitchCase="3">我是3</li>
<li *ngSwitchDefault>我是默认的</li> <!-- 没有显示 -->
</ul>
样式绑定ngStyle:
绑定的样式必须是个对象。
<!-- 例如 -->
<p [ngStyle]="testStyleObj">我的颜色</p>
<!-- ------分割线---- -->
<!--
testStyleObj = { color:'#db1000',backgroundColor:'#383','border-color':'#252'}
-->
当然单个样式的话可以:
<p [style.color]="'#db1000'" [style.border]="'1px solid #db1000'">一窜字符串</p>
<p [style.font-size.px]="fontSize">一窜字符串</p>
<!-- 分割线 -->
<!--
fontSize = 16
-->
动态绑定ngClass:
<!-- 绑定一个class -->
<button [ngClass]="className">提交</button>
<!-- 分割线 -->
<!--
className = {
btn:true
}
-->
<!-- 绑定多个class -->
<button [ngClass]="className2">提交</button>
<!-- 分割线 -->
<!--
className2 = {
btn:true,
'btn-success':true,
'btn-danger':false
}
-->
或者:
<button type="button" class="btn" [class.btn-success]="isTrue==true">提交</button>
<!-- 分割线 -->
<!--
isTrue:boolean = false
-->
双向数据绑定ngModel:
需要注意:
使用ngModel需要导入FormsModel模块:
//来到app.module.ts文件
import {FormsModel} from "@angular/forms"
@ngModule({
import:[
BrowserModule,
FormsModel //导入才能使用ngModel,
]
})
然后简单使用:
<input type="test" [(ngModel)]="userName" value=""/>
<!-- 分割线 -->
<!--
public userName:string = ""
-->
管道Pipe:
预定管道:
angular有10几个预定管道,就是angular自己提供的,可以直接拿来使用。
- List item
- LowerCase:全部小写
- UpperCase:全部大写
- TitleCase:首字母大写
- Slice:切片 //slice: start[:end]
- Json:将转为json字符串,解决了JSON的undefault
- Number:将字符串转为数字 //number:‘4.1-4’ 整数4位,小数1到4位
- Currency:金额(货币格式)
- date:时间管道
例如:
<ol>
<li>WuMiao 全部小写后为:{{'WuMiao'|LowerCase}}</li>
<li>WuMiao 全部大写后为:{{'WuMiao'|UpperCase}}</li>
<li>wumiao 切片下标2到最后:{{'wumiao'|Slice:2}}</li>
<li>12345.98765 整数位4位,小数位2位后:{{12345.98765|Number:'4.2'}}</li>
<li>1652843665414 将时间戳转为指定格式:{{'1652843665414'|date:'yyyy-MM-dd HH:mm:ss'}}</li>
</ol>
自定义管道:
创建管道的命令:ng g pipe 管道名
例如:在文件夹pipes里新建一个名为sex管道:ng g pipe /pipes/sex
<!-- 来到SexPipe管道文件 -->
import {Pipe} from “angular/core”;
@Pipe({
Name:sex
})
export class SexPipe{
transform(val){ //管道的函数代码只能在transform里面 //转换、变形
if(val==1){
return 男
}else if(val == 2){
return 女
}else{
return 保密
}
}
}
来到html使用;
<p>我的性别为:{{userSex | sex}}</p>
<!-- 分割线 -->
<!--
public sex:number = 2
-->
当然指定一管道也可以有默认参数:
export class sexPipe{
transform(sex,age="18"){ // xxxx}
}
使用的话就:(给默认值参数传参使用冒号)
<p>{{userSex | sex:'19'}}</p>
依赖注入service:
将组件与服务分开、可以服用、将数据访问的智者交给服务,
而数据就从充当着数据访问、逻辑吃力的功能,
而服务存在的目的就是对各个component之间的交互提供了一个途径。
感觉跟vue里的vuex有点相似。
创建服务service的命令:ng个service 服务文件名
例如:在文件夹services里新建一个inputForm服务:ng g service inputForm
input-form.service.ts文件:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class InputFormService {
username:string = ""
constructor() { }
getName(): string{
return this.username
}
setName(name: string): void{
this.username = name
}
}
然后来到app.module.ts全局引入:
import { InputFormService } from './services/input-form.service';
declarations: [
// ...
],
imports: [
BrowserModule,
// ...
],
providers: [InputFormService],
})
或者局部组件引入依赖:(来到要使用组件的ts文件)
使用就是:
import { Component, OnInit } from '@angular/core';
import { InputFormService } from 'src/app/services/input-form.service';
@Component({
selector: 'app-c',
templateUrl: './c.component.html',
styleUrls: ['./c.component.less'],
// providers:[InputFormService] //局部依赖,如果是全局的话就不需要这一步
})
export class CComponent implements OnInit {
constructor(private inputFormService:InputFormService) { }
ngOnInit(): void {
// 使用它service
this.inputFormService.setName("wumiao")
console.log(this.inputFormService.getName()) //打印 wumiao
}
}
路由和导航
路由定义与跳转
路由是先到先得,所以静态的推荐放在前面的。404、通配符放在最后面的。
angular单页面医用效果
例如有新建两个组件:
ng g component /components/testa
ng g component /components/testb
然后来到app.modules.ts文件:
import { RouterModule } from '@angular/router';
import AppComponent from "../xxxxx省略"
import TestaComponent from "xxx省略"
// ....省略
//路由自定义,注意注册不可以以斜杠开头
let routes = [
{path: "", pathMatch:"full",redirectTo:"index"}, //首页
{path:"index",component:AppComponent },
{path: "testa", component: TestaComponent,children:[
{path:"testb/:id",component:testbComponet},
]},
{path:"**",component:ErrorComponet} //404页面
]
imports: [
BrowserModule,
RouterModule.forRoot(routes) //导入路由模块,并注册路由词典,用于根模块中。
],
来到html组件页面使用:
声明式:
<button routerLink="index?name='wumiao'">跳到index组件页面</button>
<a routerLink="/testa"> 跳到testa组件页面</a>
<p routerLink="testa">依旧跳到testa组件页面</p>
<div routerLink="testa/testb/12">跳到testb组件页面</div>
<!-- 路由的出口 -->
<router-outlet></router-outlet>
编程式:
<button (click)="handleTo()">点击进行组件跳转</button>
import { Router } from '@angular/router';
constructor(private router: Router) { }
public id:number = 12
handleTo() {
this.router.navigateByUrl("/index")
//this.router.navigateByUrl('/testa?id=' + id) //参数
// this.router.navigate(['/testa/testb', id]); //动态路由
}
}
route获取当前路由的信息:
// 例如:来到testb.component.ts
import { ActivatedRoute } from '@angular/router';
constructor(private route: ActivatedRoute) { }
ngOnInit(): void {
//activatedRoute获取当前路由信息
//第一种:不用订阅的情况,获取初始值
// Const params = this.route.snapshot.params
// 第二种:推荐
this.route.params.subscribe((data) => { //可以去看看paramsMap
console.log(data) //{id:12}
})
}
路由守卫CanActivate:
新建路由守卫:ng 个guard 守卫文件名
例如新建一个登陆userLogin守卫:ng g guard userLogin
userLogin.guard.ts文件:
import { Injectable } from "@angular/core";
import { CanActivate, } from "@angular/router";
@Injectable({
providedIn: "root" //路由守卫都是“可注入的”服务对象
})
export class LoginCuard implements CanActivate {
private isLogin = true;
canActivate(): boolean {
if (!this.isLogin) {
return true //可以激活后续的组件
} else {
return false //不可以激活后续的组件
}
}
}
然后将守卫使用到路由上:
来到app.module.ts文件:
import { userLoginCuard } from './userLogin.guard';
//路由自定义,添加上守卫
let routes = [
{path: "usercenter", component: UserCenterComponent, canActivate: [userLoginCuard] },
{ path: "**", component: Err01Component }
]
附加笔记:你们还可以了解Can’ActivateChild
Form表单:
注意要在app.modules.ts引入FormsModel
表单的数据绑定
可以简单的:
<form>
<input [(ngModel)]="value" name="name"/>
<input [(ngModel)]="value" [(ngModelOptions)="{name:'name'}"]/>
<input [(ngModel)]="value" [ngModelOptions]="{standalone:true}"/>
</form>
使用formGroup:
例如:
<!-- 数据为:
checkoutForm = {userName:'',userPasswd:''}
-->
<form [formGroup]="checkoutForm" (ngSubmit)="subfn(checkoutForm.value)">
<label for="userName">用户名:</label>
<input type="text" formControlName="userName" id="userName" />
<hr>
<label for="userPasswd">用户密码:</label>
<input type="password" formControlName="userPasswd" id="userPasswd" />
<button type="submit">提交</button>
</form>
<!-- ---分割线--- -->
<!--
subfn(val){
console.log(val)
}
-->
FormBuilder与表单验证:
import { FormBuilder, Validators } from '@angular/forms';
constructor(private formBuilder: FormBuilder) {
}
public rulesList = this.formBuilder.group({
userName: ["",[Validators.required, Validators.minLength(2)]] //必填,最小长度为2
})
get userName() {
return this.rulesList.get("userName")
}
handleCls(val){
console.log("提交了数据",val)
}
<form [formGroup]="rulesList" (ngSubmit)="handleCls(rulesList.value)">
<label for="userName">用户名:</label>
<input type="text" id="userName" formControlName="userName" required>
<!-- 下面这几行是username输入不满足验证要求提示的文字-->
<div *ngIf="userName?.invalid && (userName?.dirty || userName?.touched)" class="alert alert-danger">
<span *ngIf="pwd?.errors?.['required']">请填写User name</span>
<span *ngIf="pwd?.errors?.['minlength']">userName应该至少2个字符</span>
</div>
<button type="submit">提交</button>
</form>
当然你也可以自定义表单验证:这个你们可以后续慢慢去了解。
import { FormBuilder, Validators, ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
constructor(private fromBuilder: FormBuilder) { }
public formRules02 = this.fromBuilder.group({
name: ["", [Validators.required, Validators.minLength(2), this.forbiddenNameValidator(/yqj/i, '不能以yqj开头')]]
})
handleCls(val: {}): void {
this.formRules02.reset(0)
}
ngOnInit(): void {
}
get name() {
return this.formRules02.get("name")
}
//自定义form验证
forbiddenNameValidator(reg: RegExp, errorTips: string): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const forbidden = reg.test(control.value);
return forbidden ? { forbiddenName: { value: errorTips } } : null;
};
}
<form [formGroup]="formRules02" (ngSubmit)="handleCls(formRules02.value)">
<label for="name">用户名:</label>
<input type="text" id="name" formControlName="name" required>
<div *ngIf="name?.invalid && (name?.dirty || name?.touched)" class="alert alert-danger">
<span *ngIf="name?.errors?.['required']">请填写last name</span>
<span *ngIf="name?.errors?.['minlength']">last name应该至少4个字符</span>
<span *ngIf="name?.errors?.['forbiddenName']">不能以yqj开头</span>
</div>
<button type="submit">提交</button>
</form>
组件之间的传值:
父传子@input:
父组件:father.component.html:
<h1>我只父组件<h1>
<hr>
<p>将父组件的fatherName传给子组件</p>
<app-son01 [fatherName]="faterName"></app-son01>
<!-- 分割线 -->
<!--
public fatherName:string="father"
-->
子组件sonComponent.component.ts:
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-son01',
templateUrl: './son01.component.html',
styleUrls: ['./son01.component.scss']
})
export class Son01Component implements OnInit {
constructor() { }
@Input() public fatherName: string = ""; //通过@input接受父组件通过组件穿过来的
ngOnInit(): void {
console.log("我父组件的名字为"this.fatherName)
}
}
子传父@output、@eventEmitter:
将子组件的传给父组件,借助事发射器eventEmitter.
son02.component.ts文件:
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-com02',
templateUrl: './com02.component.html',
styleUrls: ['./com02.component.scss']
})
export class Com02Component implements OnInit {
constructor() { }
public sonName: string = "";
@Output() public cryEvent = new EventEmitter()
uppar() {
this.cryEvent.emit(this.sonName)
}
ngOnInit(): void {
}
}
son02.component.html文件:
<p>son02 works!</p>
<input type="text" [(ngModel)]="sonName">
<button (click)="uppar()">提交</button>
<p>{{sonName}}</p>
父组件 father02.component.html文件:
<app-com02 (cryEvent)="doCry($event)"></app-com02>
<!--
public doCry(e: any): void {
this.username = e
}
-->
@ViewChild:
例如father03.component.html文件:
<app-son #son03></app-son>
<!-- <app-son #son03><app-son> -->
<!-- 分割线 -->
<!--
@ViewChild('son03', { static: false }) child03!: any; //有多个son03,就使用@viewChildren
btn(){
console.log(this.child03.sonName) //打印子组件child03里的sonName
}
-->
组件投影@ng-content
组件:
<p>a works!</p>
<ng-content select="[name='test01']"></ng-content>
<!--
select="head" 表示选择第一个head标签
select=".head" 表示选择第一个class="head"的标签
select="#head" 表示选择第一个id="head"的标签
select="[name=head]" 表示选择第一个name="head"的标签
select="[slot=head]" 表示选择第一个slot="head"的标签
-->
使用组件的地方:
<app-aa #aa>
<h1 name="test01">hhhhhh</h1>
</app-aa>
第一阶段的简单笔记就到这里吧!end!