美文网首页
学习angular可以看看,看完会掌握大部分!!!

学习angular可以看看,看完会掌握大部分!!!

作者: johnnie_wang | 来源:发表于2019-08-19 22:22 被阅读0次

因工作需要学习angular ,特此总结一下~

一、angular安装

1、安装前准备工作:
1.1、安装 nodejs
安装 angular 的计算机上面必须安装最新的 nodejs--注意安装 nodejs 稳定版本
2、使用 npm/cnpm 命令安装 angular/cli (只需要安装一次)

npm install -g @angular/cli 或者 cnpm install -g @angular/cli

二、angular创建项目

1、ng new 项目名称

例如 ng new angulardemo

如果要跳过 npm i 安装:

ng new angulardemo --skip-install

image.png

2、运行项目

cd angulardemo
ng serve --open

3、目录解构分析


image.png

三、app.module.ts、组件分析

1、app.module.ts
Angular 应用是模块化的,它拥有自己的模块化系统,称作 NgModule。 一个 NgModule 就是一个容器,用于存放一些内聚的代码块,这些代码块专注于某个应用领域、某个工作流或一组紧密相关的功能。 它可以包含一些组件、服务提供商或其它代码文件,其作用域由包含它们的 NgModule 定义。 它还可以导入一些由其它模块中导出的功能,并导出一些指定的功能供其它 NgModule 使用。

/*这个文件是Angular 根模块,告诉Angular如何组装应用*/


//BrowserModule,浏览器解析的模块
import { BrowserModule } from '@angular/platform-browser';  
//Angular核心模块
import { NgModule } from '@angular/core';
//根组件
import { AppComponent } from './app.component';
import { NewsComponent } from './components/news/news.component';
import { HomeComponent } from './components/home/home.component';
import { HeaderComponent } from './components/header/header.component';

/*@NgModule装饰器, @NgModule接受一个元数据对象,告诉 Angular 如何编译和启动应用*/
@NgModule({
  declarations: [    /*配置当前项目运行的的组件*/
    AppComponent, NewsComponent, HomeComponent, HeaderComponent
  ],
  imports: [  /*配置当前模块运行依赖的其他模块*/
    BrowserModule
  ],
  providers: [],  /*配置项目所需要的服务*/
  bootstrap: [AppComponent]    /* 指定应用的主视图(称为根组件) 通过引导根AppModule来启动应用  ,这里一般写的是根组件*/
})

//根模块不需要导出任何东西,   因为其它组件不需要导入根模块
export class AppModule { }

2、自定义组件
创建组件:

ng g component components/header
组件内容解析

import { Component, OnInit } from '@angular/core';  /* 引入angular核心 */

@Component({
  selector: 'app-header', /* 组件名称 */
  templateUrl: './header.component.html', /* html模版 */
  styleUrls: ['./header.component.scss']  /* css样式 */
})
export class HeaderComponent implements OnInit { /* 实现接口 */

  constructor() { }  /* 构造函数 */

  ngOnInit() {   /* 初始化加载时执行的生命周期函数 */
  }

}

使用组件(在html文件中)

<app-header></app-header>

四、Angular 绑定数据

1、 数据文本绑定 {{ }}

<h1>
   {{title}}
</h1>
<div>
   1+1={{1+1}}
</div>

2、绑定 html

public content="<h2>我是一个html标签</h2>";
<div [innerHTML]="content"></div>

3.绑定属性

<div [id]="id" [title]="msg">调试工具看看我的属性</div>

4、数据循环 *ngFor
(1) *ngFor 普通循环

public userlist:any[]=[{
    username:'张三',
    age:20
  },{
    username:'李四',
    age:21
  },
  {
    username:'王五',
    age:40
  }];
<ul>
<li *ngFor="let item of userlist">
   {{item.username}}
</li>
</ul>

(2)循环的时候设置 key

<ul>
<li *ngFor="let item of userlist;let i = index;">
   {{item.username}} --{{i}}
</li>
</ul>

5、条件判断 *ngIf

<p *ngIf="userlist.length > 3">这是 ngIF 判断是否显示</p>

6、*ngSwitch

<ul [ngSwitch]="num">
  <li *ngSwitchCase="1">ok</li>
  <li *ngSwitchCase="2">未完成</li>
  <li *ngSwitchCase="3">无效字段</li>
</ul>

7、执行事件 (click)=”getData()”

<button class="button" (click)="getData()">
点击按钮触发事件
</button>
<button class="button" (click)="setData()">
点击按钮设置数据
</button>
getData(){ /*自定义方法获取数据*/
//获取
alert(this.msg);
}
setData(){
//设置值
this.msg='这是设置的值';
}

8、表单事件

<input type="text" (keyup)="keyUpFn($event)"/>
keyUpFn(e){
  console.log(e);
  if(e.keyCode == 13){
        console.log('11111')
  };
}

9、双向数据绑定
引入:FormsModule

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';   //引入:FormsModule
 
@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
<input type="text" [(ngModel)]="inputValue"/>
{{inputValue}}

10、[ngClass]、[ngStyle]
(1)[ngClass]

<div [ngClass]="{'red': true, 'blue': false}">
这是一个 div
</div>
public flag=false;
<div [ngClass]="{'red': flag, 'blue': !flag}">
这是一个 div
</div>
public arr = [1, 3, 4, 5, 6];
<ul>
<li *ngFor="let item of arr, let i = index">
<span [ngClass]="{'red': i==0}">{{item}}</span>
</li>
</ul>

(2)[ngStyle]

public attr='red';
<div [ngStyle]="{'background-color':'green'}">你好 ngStyle</div>

<div [ngStyle]="{'background-color':attr}">你好 ngStyle</div>

11、管道[官方文档]

public today=new Date();
<p>{{today | date:'yyyy-MM-dd HH:mm:ss' }}</p>

五、服务[官方文档]

1.服务是一个广义的概念,它包括应用所需的任何值、函数或特性。狭义的服务是一个明确定义了用途的类。它应该做一些具体的事,并做好。
2.Angular 把组件和服务区分开,以提高模块性和复用性。 通过把组件中和视图有关的功能与其他类型的处理分离开,你可以让组件类更加精简、高效


image.png

1、创建服务

ng g service services/storage

2、app.module.ts 里面引入创建的服务

mport { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

//引入表单相关的模块 才可以用双休数据绑定
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';


//引入并且配置服务
import { StorageService } from './services/storage.service';


@NgModule({
  declarations: [
    AppComponent,
    SearchComponent,
    TodolistComponent   
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [StorageService],  //NgModule 里面的 providers 里面依赖注入服务
  bootstrap: [AppComponent]
})
export class AppModule { }

3、使用的页面引入服务,注册服务

import { Component, OnInit } from '@angular/core';
//引入服务
import { StorageService } from '../../services/storage.service';

@Component({
 selector: 'app-list',
 templateUrl: './list.component.html',
 styleUrls: ['./list.component.scss']
})
export class TodolistComponent implements OnInit {

 constructor(public storage:StorageService) {  //注册服务

  }

 ngOnInit() {
 }
}

4、使用

ngOnInit() {
    var list:any=this.storage.get('tlist');
    if(list){
      this.list=list;        
    }
  }

六、动画

(1)Angular 中的 dom 操作(原生 js)

ngAfterViewInit(){   //angular 中操作dom要在这个生命周期钩子函数中
var boxDom:any=document.getElementById('box');
boxDom.style.color='red';
}

(2)Angular 中的 dom 操作(ViewChild)

<div #myBox>
   我是一个dom节点
</div>
//在业务逻辑里面引入ViewChild
import { Component, OnInit,ViewChild} from '@angular/core'; 

@Component({
  selector: 'app-news',
  templateUrl: './news.component.html',
  styleUrls: ['./news.component.scss']
})
export class NewsComponent implements OnInit {

  //获取dom节点
  @ViewChild('myBox') myBox:any;
constructor() { }

  ngOnInit() {


  }
//ngAfterViewInit生命周期函数里面获取dom
  ngAfterViewInit(): void {
    console.log(this.myBox.nativeElement);
    this.myBox.nativeElement.style.width='100px';
    this.myBox.nativeElement.style.height='100px';
    this.myBox.nativeElement.style.background='red';
    console.log(this.myBox.nativeElement.innerHTML);

  }
}

补充: 父子组件中通过 ViewChild 调用子组件的方法
(1)引入子组件

<app-header #header></app-header>
<button (click)="getChildRun()">获取子组件的方法</button>

(2)关联起来

import { Component, OnInit,ViewChild} from '@angular/core';

@Component({
  selector: 'app-news',
  templateUrl: './news.component.html',
  styleUrls: ['./news.component.scss']
})
export class NewsComponent implements OnInit {


  //获取dom节点

  @ViewChild('myBox') myBox:any;

  //获取一个组件
  @ViewChild('header') header:any;

  constructor() { }

  ngOnInit() {
  }

getChildRun(){

    //调用子组件里面的方法
     this.header.run();
     
  }
}

七、Angular 父子组件以及组件之间通讯

1、父组件给子组件传值-@input
(1)父组件调用子组件的时候传入数据

<app-header [msg]="msg"></app-header>

(2)子组件引入 Input 模块

import { Component, OnInit,Input} from '@angular/core';

(3)子组件中 @Input 接收父组件传过来的数据

export class HeaderComponent implements OnInit {
@Input() msg:string
constructor() { }
ngOnInit() {
}
}

(4)子组件中使用父组件的数据

<h2>这是头部组件--{{msg}}</h2>

2、子组件通过@Output 触发父组件的方法
(1)子组件引入 Output 和 EventEmitter

import { Component, OnInit,Output,EventEmitter } from '@angular/core';

(2)子组件中实例化 EventEmitter

@Output() private outer=new EventEmitter<string>();
/*用 EventEmitter 和 output 装饰器配合使用 <string>指定类型变量

(3)子组件通过 EventEmitter 对象 outer 实例广播数据

sendParent(){
  // alert('zhixing');
  this.outer.emit('msg from child')
}

(4).父组件调用子组件的时候,定义接收事件 , outer 就是子组件的 EventEmitter 对象 outer

<app-header (outer)="runParent($event)"></app-head

(5)父组件接收到数据会调用自己的 runParent 方法,这个时候就能拿到子组件的数据

//接收子组件传递过来的数据
runParent(msg:string){
   console.log(msg);
}

八、Angular 中的生命周期函数

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="数据改变了";
    }

}

运行后结果: image.png
再次进入页面: image.png
只会执行ngDoCheck() , ngAfterContentChecked() , ngAfterViewChecked()

九、Rxjs

它是一种针对异步数据流的编程。简单来说,它将一切数据,包括 HTTP 请求,DOM 事件或者普通数据等包装成流的形式,然后用强大丰富的操作符对流进行处理,使你能以同步编程的方式处理异步数据,并组合不同的操作符来轻松优雅的实现你所需要的功能
1、RxJS 处理异步:

import {Observable} from 'rxjs';
let stream = new Observable(observer => {
setTimeout(() => {
  observer.next('observable timeout');
 }, 2000);
});
stream.subscribe(value => console.log(value));

从上面例子我们感觉Promise 和 RxJS 的用法基本相似。其实Rxjs相比Promise 要强大很多。
比如 Rxjs 中可以中途撤回、Rxjs 可以发射多个值、Rxjs 提供了多种工具函数等等。

2、Rxjs unsubscribe 取消订阅
Promise 的创建之后,动作是无法撤回的。
Observable 不一样,动作可以通过unsbscribe() 方法中途撤回,而且Observable 在内部做了智能的处理.

Promise 创建之后动作无法

let promise = new Promise(resolve => {
setTimeout(() => {
resolve('---promise timeout---');
}, 2000);
});
promise.then(value => console.log(value));

Rxjs 可以通过 unsubscribe() 可以撤回 subscribe 的动作

let stream = new Observable(observer => {
let timeout = setTimeout(() => {
   clearTimeout(timeout);
    observer.next('observable timeout');
  }, 2000);
});
let disposable = stream.subscribe(value => console.log(value));
setTimeout(() => {
   //取消执行
   disposable.unsubscribe();
}, 1000);

3、Rxjs 订阅后多次执行
我们想让异步里面的方法多次执行 , promise做不到

let promise = new Promise(resolve => {
   setInterval(() => {
      resolve('---promise setInterval---');
  }, 2000);
});
promise.then(value => console.log(value));

但是Rxjs可以

let stream = new Observable<number>(observer => {
     let count = 0;
     setInterval(() => {
          observer.next(count++);
     }, 1000);
    });
stream.subscribe(value => console.log("Observable>"+value));

4、使用 Rxjs 的工具函数map filter

import {Observable} from 'rxjs';
let stream= new Observable<any>(observer => {
     let count = 0;
     setInterval(() => {
           observer.next(count++);
       }, 1000);
     });
stream.filter(val=>val%2==0)
.subscribe(value => console.log("filter>"+value));
 stream
.map(value => {
return value * value
})
.subscribe(value => console.log("map>"+value));

5、Rxjs 延迟执行

import {Observable,fromEvent} from 'rxjs';
import {map,filter,throttleTime} from 'rxjs/operators';
    var button = document.querySelector('button');
    fromEvent(button, 'click').pipe(
     throttleTime(1000)
    )
.subscribe(() => console.log(`Clicked`));

6、Rxjs6.x 的变化以及使用
RXJS6 改变了包的结构,主要变化在 import 方式和 operator 上面以及使用 pipe()

import {Observable} from 'rxjs';
import {map,filter} from 'rxjs/operators';
let stream= new Observable<any>(observer => {
   let count = 0;
   setInterval(() => {
       observer.next(count++);
     }, 1000);
});
stream.pipe(
    filter(val=>val%2==0)
)
.subscribe(value => console.log("filter>"+value));
stream.pipe(
   filter(val=>val%2==0), map(value => {
       return value * value
   })
 )
.subscribe(value => console.log("map>"+value));Ï

十、Angular 中的数据交互

1、Angular get 请求数据
(1)在 app.module.ts 中引入 HttpClientModule 并注入

import {HttpClientModule} from '@angular/common/http';
imports: [
  BrowserModule,
  HttpClientModule
]

(2)在用到的地方引入 HttpClient 并在构造函数声明

import {HttpClient} from "@angular/common/http";
constructor(public http:HttpClient) { }

(3)get 请求数据

var api = "http://xxxxxxxxxxxx";
this.http.get(api).subscribe(response => {
     console.log(response);
});

2、Angular post 提交数据
(1)在 app.module.ts 中引入 HttpClientModule 并注入

import {HttpClientModule} from '@angular/common/http';
imports: [
  BrowserModule,
  HttpClientModule
]

(2)在用到的地方引入 HttpClient、HttpHeaders 并在构造函数声明 HttpClient

import {HttpClient,HttpHeaders} from "@angular/common/http";
constructor(public http:HttpClient) { }

(3)post 提交数据

const httpOptions = {
     headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
var api = "http://127.0.0.1:3000/doLogin";
this.http.post(api,{username:'张三',age:'20'},httpOptions).subscribe(response => {
     console.log(response);
});

3、Angular Jsonp 请求数据
(1)在 app.module.ts 中引入 HttpClientModule、HttpClientJsonpModule 并注入

import {HttpClientModule,HttpClientJsonpModule} from '@angular/common/http';
imports: [
BrowserModule,
HttpClientModule,
HttpClientJsonpModule
]

(2)在用到的地方引入 HttpClient 并在构造函数声明

import {HttpClient} from "@angular/common/http";
constructor(public http:HttpClient) { }

(3)jsonp 请求数据

var api = "http://a.itying.com/api/productlist";
this.http.jsonp(api,'callback').subscribe(response => {
     console.log(response);
});

4、Angular 中使用第三方模块 axios 请求数据(无比怀念axios!!!!!)
(1)安装 axios

cnpm install axios --save

(2)用到的地方引入 axios

import axios from 'axios';

(3)用它

axios.get('/user?ID=12345')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
})

十一、路由

1、找到 app-routing.module.ts 配置路由
引入组件

import { HomeComponent } from './home/home.component';
import { NewsComponent } from './news/news.component';
import { NewscontentComponent } from './newscontent/newscontent.component';

配置路由

const routes: Routes = [
   {path: 'home', component: HomeComponent},
   {path: 'news', component: NewsComponent},
   {path: 'newscontent/:id', component: NewscontentComponent},
  {
    path: '',
    redirectTo: '/home',
   pathMatch: 'full'
  }
]

空路径('')表示应用的默认路径,当 URL 为空时就会访问那里,因此它通常会作为起点。 这个默认路由会重定向到 URL /home,并显示 HomeComponent。

找到 app.component.html 根组件模板,配置 router-outlet 显示动态加载的路由

<h1>
   <a routerLink="/home">首页</a>
   <a routerLink="/news">新闻</a>
</h1>
<router-outlet></router-outlet>

2、**通配符

{ path: '**', component: PageNotFoundComponent }

最后一个路由中的 ** 路径是一个通配符。当所请求的 URL 不匹配前面定义的路由表中的任何路径时,路由器就会选择此路由。 这个特性可用于显示“404 - Not Found”页,或自动重定向到其它路由。

3、routerLinkActive 设 置 routerLink 默认选中路由

<h1>
   <a [routerLink]="[ '/home' ]" routerLinkActive="active">首页</a>
   <a [routerLink]="[ '/news' ]" routerLinkActive="active">新闻</a>
</h1>
.active{
   color:red;
}

4、动态路由
配置动态路由

const routes: Routes = [
  {path: 'home', component: HomeComponent},
  {path: 'news', component: NewsComponent},
  {path: 'newscontent/:id', component: NewscontentComponent},
  {
     path: '',
     redirectTo: '/home',
     pathMatch: 'full'
   }
];

跳转传值

<a [routerLink]="[ '/newscontent/',aid]">跳转到详情</a>
<a routerLink="/newscontent/{{aid}}">跳转到详情</a>

获取动态路由的值

import { ActivatedRoute} from '@angular/router';
constructor( private route: ActivatedRoute) { }
ngOnInit() {
   console.log(this.route.params);
   this.route.params.subscribe(data=>this.id=data.id);
}

5、动态路由的 js 跳转
引入

import { Router } from '@angular/router';

初始化

export class HomeComponent implements OnInit {
   constructor(private router: Router) { }
   ngOnInit() { }
   goNews(){
     // this.router.navigate(['/news', hero.id]);
     this.router.navigate(['/news']);
     //传值  
     this.router.navigate(['/news', hero.id]);
   }
}

6、路由 get 传值 js 跳转
引入 NavigationExtras

import { Router ,NavigationExtras} from '@angular/router';

定义一个 goNewsContent 方法执行跳转,用 NavigationExtras 配置传参

goNewsContent(){
   let navigationExtras: NavigationExtras = {
      queryParams: { 'session_id': '123' },
      fragment: 'anchor'
    };
this.router.navigate(['/news'],navigationExtras);
}

获取 get 传值

constructor(private route: ActivatedRoute) {
    console.log(this.route.queryParams);
}

7、父子路由
创建组件引入组件

import { NewsaddComponent } from './components/newsadd/newsadd.component';
import { NewslistComponent } from './components/newslist/newslist.component';

配置路由

{
   path: 'news',
   component:NewsComponent,
   children: [
     {
         path:'newslist',
         component:NewslistComponent
     },
     {
        path:'newsadd',
        component:NewsaddComponent
     }
   ]
}

父组件中定义 router-outlet

<router-outlet></router-outlet>

十二、Angular 中自定义模块

1、Angular 内置模块


image.png

2、Angular 自定义模块
当我们项目比较小的时候可以不用自定义模块。但是当我们项目非常庞大的时候把所有的组
件都挂载到根模块里面不是特别合适。所以这个时候我们就可以自定义模块来组织我们的项
目。并且通过 Angular 自定义模块可以实现路由的懒加载。

ng g module mymodule

image.png

创建模块:
ng g module module/user --routing
ng g module module/article --routing
ng g module module/product --routing
创建组件:
ng g component module/user
ng g component module/user/components/profile
ng g component module/user/components/order
ng g component module/article
ng g component module/article/components/articlelist
ng g component module/article/components/info
ng g component module/product
ng g component module/product/components/plist
ng g component module/product/components/pinfo

配置懒加载

import { HomeComponent } from './components/home/home.component';
const routes: Routes = [
 {
   path:'',component : HomeComponent
 },
 {
   path:'user',loadChildren : './usermodule/usermodule.module#UsermoduleModule' 
 }
];

最后我只想说一句 vue相比于angular真的太简单了!!
以后在工作中遇到的问题会持续更新在简书~
在学习angular的朋友一起共勉吧~~

相关文章

网友评论

      本文标题:学习angular可以看看,看完会掌握大部分!!!

      本文链接:https://www.haomeiwen.com/subject/aeresctx.html