简书上写的很好的angular教程 — 学习angular可以看看,看完会掌握大部分!!!
angular cli 安装
执行下面的命令
npm install -g @angular/cli
创建项目
执行下面的命令, angular-study
可以任意,表示的是项目名称
ng new angular-study
输入n,再输入n。(不共享数据,先不安装路由模块)
css编写这里选择less
? Would you like to share pseudonymous usage data about this project with the Angular Team
at Google under Google's Privacy Policy at https://policies.google.com/privacy. For more
details and how to change this setting, see https://angular.io/analytics. No
Global setting: disabled
Local setting: No local workspace configuration file.
Effective status: disabled
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? Less [ http://lesscss.org
]
启动项目
ng serve
访问项目
http://localhost:4200/
项目结构说明
src
- app 根组件
- app.component.html html模板
- app.component.less css样式
- app.component.ts ts写数据处理逻辑
- app.module.ts app模块文件
- assets 静态资源目录
- index.html 主页
- main.ts 项目的入口文件
- styles.less 全局样式文件
- .editorconfig 环境配置(开发/测试/上线)
- .gitignore git忽略文件
- angular.json angular cli的配置相关
- tsconfig ts编译器相关配置
更多介绍可参考官网:工作区和项目文件结构
app.component.ts
主要用来定义根组件
import { Component } from '@angular/core';
@Component({
selector: 'app-root', //组件的唯一标识,当前是根组件
templateUrl: './app.component.html', //当前组件的html模板文件
styleUrls: ['./app.component.less'] //当前组件的样式文件
})
export class AppComponent {
title = 'angular-study'; //定义一个双向绑定的数据
}
app.module.ts
Angular 应用是模块化的,它拥有自己的模块化系统,称作 NgModule。 一个 NgModule 就是一个容器,用于存放一些内聚的代码块,这些代码块专注于某个应用领域、某个工作流或一组紧密相关的功能。 它可以包含一些组件、服务提供商或其它代码文件,其作用域由包含它们的 NgModule 定义。 它还可以导入一些由其它模块中导出的功能,并导出一些指定的功能供其它 NgModule 使用。
/*这个文件是Angular 根模块,告诉Angular如何组装应用*/
//Angular核心模块
import { NgModule } from '@angular/core';
//BrowserModule,浏览器解析的模块
import { BrowserModule } from '@angular/platform-browser';
//根组件
import { AppComponent } from './app.component';
//NgModule装饰器, @NgModule接受一个元数据对象,告诉 Angular 如何编译和启动应用
@NgModule({
declarations: [//配置当前项目运行的的组件
AppComponent
],
imports: [//配置当前模块运行依赖的其他模块
BrowserModule
],
providers: [],//项目所需要的服务
//指定应用的主视图(称为根组件) 通过引导根AppModule来启动应用 ,这里一般写的是根组件
bootstrap: [AppComponent]
})
export class AppModule { }
创建组件
- 在src/view下面创建一个Hello组件(先创建view文件夹并且切换到这个文件夹)
D:\webcode\angular-study\src\view> ng g component Hello --skip-import
- 修改src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
//导入Hello组件
import { HelloComponent } from '../view/hello/hello.component';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
HelloComponent //注册Hello组件
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
- 将src/app/app.component.html内容清空,并引入hello组件
<app-hello></app-hello>
然后就可以看到hello组件的内容展示了
定义数据
只需要定义类中的属性即可
export class HelloComponent {
title = 'hello angular' //定义双向绑定的数据变量
}
绑定数据
插值语法
<h2>{{title}}</h2>
绑定html
- 定义数据
export class HelloComponent {
htmlStr = '<h2>hello angular</h2>'
}
- 绑定
<div [innerHTML]="htmlStr"></div>
绑定属性
<div [title]="title" [id]="id">绑定属性</div>
数据循环 *ngFor
- 定义数据
export class HelloComponent {
userlist = [{
username: '张三',
age: 20
}, {
username: '李四',
age: 21
},
{
username: '王五',
age: 40
}]
}
- 使用
<ul>
<li *ngFor="let user of userlist">{{user.username + '-' + user.age}}</li>
</ul>
<!-- 带下标/绑定key -->
<ul>
<li *ngFor="let user of userlist;let i = index">{{i+":"+user.username + '-' + user.age}}</li>
</ul>
条件判断 *ngIf
<div *ngIf="gender == 'male'">男的</div>
<div *ngIf="gender == 'famale'">女的</div>
*ngSwitch
- 定义数据
export class HelloComponent {
status = 2
}
- 使用
<ul [ngSwitch]="status">
<li *ngSwitchCase="1">未开始</li>
<li *ngSwitchCase="2">进行中</li>
<li *ngSwitchCase="3">已完成</li>
</ul>
绑定click事件
- 定义方法
export class HelloComponent {
age = 18
setAge(){
this.age = this.age + 1
}
getAge(){
return this.age
}
}
- 使用
<h2>当前age:{{getAge()}}</h2>
<button (click)="setAge()">age+1</button>
input事件
- 定义
export class HelloComponent {
msg = ''
keyUpFn(event:any){
// console.log(event.target.value)
this.msg = event.target.value
}
}
- 使用
<input type="text" name="" id="" (keyup)="keyUpFn($event)">
<div>输入的值为:{{msg}}</div>
双向绑定
- 定义
export class HelloComponent {
text = ''
}
- 使用
<input type="text" name="" [(ngModel)]="text">
<div>双向绑定:{{text}}</div>
[ngStyle]
- 定义
export class HelloComponent {
textRed = 'red'
}
- 使用
<div [style]="{'color':'blue'}">蓝色字体</div>
<div [style]="{'color':textRed}">红色字体-变量</div>
[ngClass]
- 定义样式
.blueText{
color: blue;
}
.redText{
color: red;
}
- 定义数据
export class HelloComponent {
isRed = true
}
- 使用
<div [ngClass]="{blueText: true}">蓝色字体</div>
<div [ngClass]="{redText: isRed}">红色字体-变量</div>
管道
类似vue中的过滤器
- 定义
export class HelloComponent {
today = new Date()
}
- 使用
<div>{{today | date:'yyyy-MM-dd HH:mm:ss'}}</div>
服务
服务是一个广义的概念,它包括应用所需的任何值、函数或特性。狭义的服务是一个明确定义了用途的类。它应该做一些具体的事,并做好。
Angular 把组件和服务区分开,以提高模块性和复用性。 通过把组件中和视图有关的功能与其他类型的处理分离开,你可以让组件类更加精简、高效。
一个服务可以为多个组件使用
个人理解:类似Java中一个工具类
创建服务
ng g service services/storage
编写服务
src/services/storage.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class StorageService {
constructor() { }
private tlist = [
{ name: 'tom', age: 18},
{ name: 'jerry', age: 16}
]
getTlist(){
return this.tlist;
}
setTlist(tlist: []){
this.tlist = tlist
}
}
注册服务
src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HelloComponent } from '../view/hello/hello.component';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';
//导入自己写的服务
import {StorageService} from '../services/storage.service';
@NgModule({
declarations: [
AppComponent,HelloComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [StorageService],//注册服务
bootstrap: [AppComponent]
})
export class AppModule { }
使用服务
src/view/hello/hello.component.ts
import { Component } from '@angular/core';
//导入服务
import {StorageService} from '../../services/storage.service'
@Component({
selector: 'app-hello',
templateUrl: './hello.component.html',
styleUrls: ['./hello.component.less']
})
export class HelloComponent {
//注入服务给构造函数参数
constructor(private storage : StorageService) {
console.log('tlist', storage.getTlist()) //使用服务
}
}
dom
ngAfterViewInit() : angular 中操作dom要在这个生命周期钩子函数中。有原生的获取和ViewChild注入方式来获取
- 页面
<!-- 原生dom -->
<div id="dom1">dom1111</div>
<!-- ViewChild方式获取dom -->
<div #dom2>dom2222</div>
- ts
import { Component,ViewChild } from '@angular/core';
@Component({
selector: 'app-hello',
templateUrl: './hello.component.html',
styleUrls: ['./hello.component.less']
})
export class HelloComponent {
//注入dom2
@ViewChild('dom2') dom2:any
//页面 初始化之后,可以在这个钩子中操作dom
ngAfterViewInit() {
//1.原生方式操作dom
let dom1:any = document.getElementById('dom1')
dom1.style.color = 'red'
//2.ViewChild注入的方式,获取dom通过nativeElement属性
this.dom2.nativeElement.style.color = 'green'
}
}
组件通信
父组件给子组件传数据
- 父组件将变量传给子组件—>通过
标签属性
的方式传递
<!-- 给子组件传递数据 -->
<app-child [msg]="msg"></app-child>
- 子组件接收数据—>通过
Input
注入
import { Component,Input } from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.less']
})
export class ChildComponent {
//接受父组件传过来的数据,通过Input注入fatherMsg变量
@Input('msg') fatherMsg: string = ''
ngOnInit(){
console.log('fatherMsg', this.fatherMsg)
}
}
子组件给父组件传数据
- 子组件html
<button (click)="sendMsgToFather()">给父组件传数据</button>
- 子组件ts
import { Component,Output,EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.less']
})
export class ChildComponent {
//通过这个对象来触发父组件的方法(如果父组件有多个方法要传递,那么需要创建多个outer)
@Output() private outer = new EventEmitter<string>();
//点击后向父组件传数据
sendMsgToFather(){
//通过emit触发父组件方法给父组件传递数据
this.outer.emit('我是 child 组件')
}
}
- 父组件html
<app-child (outer)="reciveChild($event)"></app-child>
- 父组件ts
import { Component } from '@angular/core';
@Component({
selector: 'app-father',
templateUrl: './father.component.html',
styleUrls: ['./father.component.less']
})
export class FatherComponent {
childMsg = ''
//接收子组件发送来的数据
reciveChild(childMsg:string){
console.log('childMsg', childMsg)
this.childMsg = childMsg
}
}
生命周期
1、ngOnChanges()
当 Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的 SimpleChanges 对象当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在 ngOnInit() 之前。
2、ngOnInit()
在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。
在第一轮 ngOnChanges() 完成之后调用,只调用一次。
使用 ngOnInit() 有两个原因:
<1>在构造函数之后马上执行复杂的初始化逻辑
<2>在 Angular 设置完输入属性之后,对该组件进行准备。
有经验的开发者会认同组件的构建应该很便宜和安全。
3、ngDoCheck()
检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。在每个 Angular 变更检测周期中调用,ngOnChanges() 和 ngOnInit() 之后
4、ngAfterContentInit()
当把内容投影进组件之后调用。第一次 ngDoCheck() 之后调用,只调用一次。
5、ngAfterContentChecked()
每次完成被投影组件内容的变更检测之后调用。ngAfterContentInit() 和每次 ngDoCheck() 之后调用。
6、ngAfterViewInit()
初始化完组件视图及其子视图之后调用。第 一次 ngAfterContentChecked() 之后调用,只调用一次。
7、ngAfterViewChecked()
每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit()和每次 ngAfterContentChecked() 之后调用
8、ngOnDestroy()
当 Angular 每次销毁指令/组件之前调用并清扫。在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。在 Angular 销毁指令/组件之前调用
import { Component,Input} from '@angular/core';
@Component({
selector: 'app-lifecycle',
templateUrl: './lifecycle.component.html',
styleUrls: ['./lifecycle.component.scss']
})
export class LifecycleComponent{
@Input('title') title:string;
public msg:string='生命周期演示';
public userinfo:string='';
public oldUserinfo:string='';
constructor() {
console.log('00构造函数执行了---除了使用简单的值对局部变量进行初始化之外,什么都不应该做')
}
ngOnChanges() {
console.log('01ngOnChages执行了---当被绑定的输入属性的值发生变化时调用(父子组件传值的时候会触发)');
}
ngOnInit() {
console.log('02ngOnInit执行了--- 请求数据一般放在这个里面');
}
ngDoCheck() {
//写一些自定义的操作
console.log('03ngDoCheck执行了---检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应');
if(this.userinfo!==this.oldUserinfo){
console.log(`你从${this.oldUserinfo}改成${this.userinfo}`);
this.oldUserinfo = this.userinfo;
}else{
console.log("数据没有变化");
}
}
ngAfterContentInit() {
console.log('04ngAfterContentInit执行了---当把内容投影进组件之后调用');
}
ngAfterContentChecked() {
console.log('05ngAfterContentChecked执行了---每次完成被投影组件内容的变更检测之后调用');
}
ngAfterViewInit(): void {
console.log('06 ngAfterViewInit执行了----初始化完组件视图及其子视图之后调用(dom操作放在这个里面)');
}
ngAfterViewChecked() {
console.log('07ngAfterViewChecked执行了----每次做完组件视图和子视图的变更检测之后调用');
}
ngOnDestroy() {
console.log('08ngOnDestroy执行了····');
}
//自定义方法
changeMsg(){
this.msg="数据改变了";
}
}
路由
创建一个带路由的项目 angular-routing-study 为项目名称
ng new angular-routing-study --routing --defaults
创建四个组件
ng g component home --skip-import
ng g component news --skip-import
ng g component newsdetails --skip-import
ng g component pagenotfind --skip-import
配置路由
- app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
//导入自定义的组件
import { HomeComponent } from '../views/home/home.component'
import { NewsComponent } from '../views/news/news.component'
import { PagenotfindComponent } from '../views/pagenotfind/pagenotfind.component'
//配置路由
const routes: Routes = [
{path: 'home' , component: HomeComponent},
{path: 'news' , component: NewsComponent},
{path: '' , redirectTo: '/home',pathMatch: 'full'}, //''表示默认重定向到home页面
{path: '**',component: PagenotfindComponent} //通配符匹配,404
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
- app.component.html
<!-- 路由跳转链接-routerLink属性 routerLinkActive设置路由激活后的按钮链接样式 -->
<a class="menu-btn" routerLink="/home" routerLinkActive="active">首页</a>
<a class="menu-btn" routerLink="/news" routerLinkActive="active">新闻</a>
<!-- 路由匹配后展示的区域-router-outlet标签 -->
<router-outlet></router-outlet>
- app.component.css
.active{
color: deeppink;
}
.menu-btn{
width: 200px;
height: 60px;
padding: 10px;
margin-left: 6px;
}
然后点击页面上的首页
和新闻
按钮就可以在router-outlet
的区域进行的切换了
动态路由
和vue中的类似,也就是传递和接收params参数
- app-routing.module.ts 在Routes数组中新增一条路由规则
const routes: Routes = [
//新增下面这条路由规则 :id表示传递的参数名为id,也可以是其它的
{path: 'newDetails/:id' , component: NewsdetailsComponent}
]
- app.component.html 新增一个跳转的按钮
<a class="menu-btn" routerLink="/newDetails/1">新闻详情1,传递的参数为1</a>
<a class="menu-btn" [routerLink]="['/newDetails',2]" >新闻详情2,传递的参数为2</a>
- newsdetails.component.ts
import { Component } from '@angular/core';
//1.导入ActivatedRoute
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-newsdetails',
templateUrl: './newsdetails.component.html',
styleUrls: ['./newsdetails.component.css']
})
export class NewsdetailsComponent {
newsId = ''
//2.将ActivatedRoute注入给route
constructor(private route:ActivatedRoute){ }
ngOnInit(){
console.log(this.route.params);
//3.获取params参数,并赋值给newsId
this.route.params.subscribe((data:any) => this.newsId = data.id)
}
}
- newsdetails.component.html 新闻详情页就可以接收到传递过来的params参数了
<p>newsdetails works!,接受到参数新闻id:{{newsId}}</p>
问号传参跳转
也就是在链接后面用问号的形式传递参数,跳转页面
- app.component.html修改新闻按钮,这样点击按钮就会以问号传参的形式跳转到新闻页面
<a class="menu-btn" href="/news?name=tom&age=18">新闻(?传参)</a>
- news.component.ts - 接收参数
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-news',
templateUrl: './news.component.html',
styleUrls: ['./news.component.css']
})
export class NewsComponent {
name = ''
age = ''
constructor(private router:ActivatedRoute){
//通过 this.router.queryParams 获取问号传递过来的参数
this.router.queryParams.subscribe(params => {
console.log('?参数', params)
this.age = params['age']
this.name = params['name']
});
}
}
- news.component.html - 展示接收到的参数
<p>news works! 接受到的queryParams参数:{{name+'-'+age}}</p>
编程式导航
其实就是通过js的形式跳转页面
问号传参的编程式导航
- app.component.html 新增一个新闻详情按钮
<button class="menu-btn" (click)="goNews()">新闻(?传参)</button>
- app.component.ts 通过js事件的形式跳转路由
import { Component } from '@angular/core';
//1.导入Router,NavigationExtras
import { Router,NavigationExtras } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'angular-routing-study';
//2.注入Router到router
constructor(private router:Router){}
goNews(){
//3.定义需要传递的问号参数
let navigationExtras:NavigationExtras = {
queryParams: { name: 'jerry',age: 19 }
}
//4.调用navigate跳转页面并传递参数
this.router.navigate(['/news'],navigationExtras)
}
}
- 获取参数的方式和前面a标签跳转的方式一致
动态路由的编程式导航
- app.component.html 新增一个新闻详情按钮
<button class="menu-btn" (click)="goNewsDetails()">新闻详情1</button>
- app.component.ts 通过js事件的形式跳转路由
import { Component } from '@angular/core';
//1.导入Router
import { Router } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'angular-routing-study';
//2.注入Router到router
constructor(private router:Router){}
goNewsDetails(){
//3.调用navigate方法携带参数跳转
this.router.navigate(['/newDetails',1])
}
}
- 获取参数的方式和前面a标签跳转的方式一致
嵌套路由
假设newsdetails是news的子页面
- app-routing.module.ts 修改news为嵌套路由
const routes: Routes = [
{
path: 'news',
component: NewsComponent,
//添加一个children节点,并配置
children: [
{ path: 'newDetails/:id', component: NewsdetailsComponent }
]
}
]
- news.component.html 页面修改为如下
<p>news works!</p>
<ul>
<li><a routerLink="/news/newDetails/1">新闻1</a></li>
<li><a routerLink="/news/newDetails/2">新闻2</a></li>
<li><a routerLink="/news/newDetails/3">新闻3</a></li>
</ul>
<!-- 新闻详情展示的区域 -->
<router-outlet></router-outlet>
- newsdetails.component.ts 接收传递过来的路由参数
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-newsdetails',
templateUrl: './newsdetails.component.html',
styleUrls: ['./newsdetails.component.css']
})
export class NewsdetailsComponent {
newsId = ''
constructor(private route:ActivatedRoute){}
ngOnInit(){
//获取传递来的新闻id并赋值给newsId
this.route.params.subscribe((data:any) => this.newsId = data.id)
}
}
- newsdetails.component.html 渲染传递来的参数
<p>newsdetails works!,接受到参数新闻id:{{newsId}}</p>
- 新增一个news.module.ts 文件
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'
import { NewsComponent } from './news.component';
@NgModule({
declarations: [
//一定要声明这个,否则父组件中使用<router-outlet>标签将报错 !!!
//https://stackoverflow.com/questions/44517737/router-outlet-is-not-a-known-element
NewsComponent
],
imports: [
RouterModule
],
exports: [RouterModule],
})
export class NewsModule { }
模块
模块是组织应用和使用外部库扩展应用的最佳途径。
angular内置模块
Angular 自己的库都是 NgModule,比如 FormsModule
、HttpClientModule
和 RouterModule
。很多第三方库也是 NgModule,比如 Material Design、 Ionic 和 AngularFire2。
自定义模块
当项目非常庞大的时候把所有的组件都挂载到根模块里面不是特别合适。所以这个时候就可以自定义模块来组织的项目。并且通过 Angular 自定义模块可以实现路由的懒加载。
- 在组件中创建模块
D:\webcode\angular-routing-study\src\views>ng g module home # home组件的模块
D:\webcode\angular-routing-study\src\views>ng g module newsdetails # newsdetails组件的模块
D:\webcode\angular-routing-study\src\views>ng g module pagenotfind # pagenotfind组件的模块
- 修改app-routing.module.ts中路由的配置如下
const routes: Routes = [
//主要通过loadChildren回调函数的形式导入模块,实现懒加载
{ path: '**', loadChildren: () => import('../views/pagenotfind/pagenotfind.module').then(m => m.PagenotfindModule) } //通配符匹配,404
]