Home>Article>Web Front-end> Learn more about Angular (a beginner's guide)
This article will give you an in-depth understanding of Angular and share the most completeAngularbeginner's guide. I hope it will be helpful to everyone!
Angular is an open source web front-end framework developed by Google, based on TypeScript. [Related tutorial recommendations: "angular tutorial"]
and react Compared with vue, Angular is more suitable for medium and large enterprise-level projects.
main.ts => app.module.ts => app.component.ts => ; index.html => app.component.html
|-- project |-- .editorconfig // 用于在不同编辑器中统一代码风格 |-- .gitignore // git中的忽略文件列表 |-- README.md // markdown格式的说明文件 |-- angular.json // angular的配置文件 |-- browserslist // 用于配置浏览器兼容性的文件 |-- karma.conf.js // 自动化测试框架Karma的配置文件 |-- package-lock.json // 依赖包版本锁定文件 |-- package.json // npm的包定义文件 |-- tsconfig.app.json // 用于app项目的ts配置文件 |-- tsconfig.json // 整个工作区的ts配置文件 |-- tsconfig.spec.json // 用于测试的ts配置文件 |-- tslint.json // ts的代码静态扫描配置 |-- e2e // 自动化集成测试目录 |-- src // 源代码目录 |-- src // 源代码目录 |-- favicon.ico // 收藏图标 |-- index.html // 单页应用到宿主HTML |-- main.ts // 入口 ts 文件 |-- polyfills.ts // 用于不同浏览器的兼容脚本加载 |-- styles.css // 整个项目的全局css |-- test.ts // 测试入口 |-- app // 工程源码目录 |-- assets // 资源目录 |-- environments // 环境配置 |-- environments.prod.ts // 生产环境 |-- environments.ts // 开发环境
Static metadata (declarations)
declarations array: components, instructions or pipes owned by the module, pay attention to each Each component/instruction/pipeline can only be declared in one module
But when the project is very large, it is not appropriate to mount all the components into the root module
So you can Use custom modules to organize projects, and use custom modules to implement lazy loading of routes
Provide components, instructions or Pipeline moduleModule that only provides services
NgModule
才能被其他组件或应用使用@NgModule
元数据的declarations
字段中引用@Component({ selector: 'app-xxx', templateUrl: 'XXX', styleUrls: ['XXX'], encapsulation:ViewEncapsulation.Emulated // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签 })
数据绑定{{data}}
属性绑定[id]="id"
,其中[class.样式类名]=“判断表达式”
是在应用单个class
样式时的常用技巧
事件绑定(keyup)="keyUpFn($event)"
样式绑定可以用:host
这样一个伪类选择器,绑定的样式作用于组件本身
双向数据绑定[(ngModel)]
// 注意引入:FormsModule import { FormsModule } from '@angular/forms'; {{inputValue}} // 其实是一个语法糖 [ngModel]="username" (ngModelChange)="username = $event"
脏值检测:当数据改变时更新视图(DOM)
如何进行检测:检测两个状态值(当前状态和新状态)
何时触发脏值检测:浏览器事件(click
、mouseover
、keyup
等)、setTimeout()
或setInterval()
、HTTP请求
Angular 有两种变更检测策略:Default
和OnPush
可以通过在@Component
元数据中设置changeDetection: ChangeDetectionStrategy.OnPush
进行切换
Default
:
优点:每一次有异步事件发生,Angular 都会触发变更检测,从根组件开始遍历其子组件,对每一个组件都进行变更检测,对dom进行更新。
缺点:有很多组件状态没有发生变化,无需进行变更检测。如果应用程序中组件越多,性能问题会越来越明显。
OnPush
:
优点:组件的变更检测完全依赖于组件的输入(@Input
),只要输入值不变就不会触发变更检测,也不会对其子组件进行变更检测,在组件很多的时候会有明显的性能提升。
缺点:必须保证输入(@Input
)是不可变的(可以用Immutable.js
解决),每一次输入变化都必须是新的引用。
父组件给子组件传值 @input
父组件不仅可以给子组件传递简单的数据,还可把自己的方法以及整个父组件传给子组件。
// 父组件调用子组件的时候传入数据// 子组件引入 Input 模块 import { Component, OnInit ,Input } from '@angular/core'; // 子组件中 @Input 装饰器接收父组件传过来的数据 export class HeaderComponent implements OnInit { @Input() msg:string constructor() { } ngOnInit() { } } // 子组件中使用父组件的数据 这是头部组件--{{msg}}
**子组件触发父组件的方法 @Output **
// 子组件引入 Output 和 EventEmitter import { Component,OnInit,Input,Output,EventEmitter} from '@angular/core'; // 子组件中实例化 EventEmitter // 用 EventEmitter 和 @Output 装饰器配合使用指定类型变量 @Output() private outer=new EventEmitter (); // 子组件通过 EventEmitter 对象 outer 实例广播数据 sendParent(){ this.outer.emit('msg from child') } // 父组件调用子组件的时候,定义接收事件,outer 就是子组件的 EventEmitter 对象 outer // 父组件接收到数据会调用自己的 runParent, 这个时候就能拿到子组件的数据 // 接收子组件传递过来的数据 runParent(msg:string){ alert(msg); }
父组件通过 ViewChild 主动调用子组件DOM和方法
// 给子组件定义一个名称// 引入 ViewChild import { Component, OnInit ,ViewChild} from '@angular/core'; // ViewChild 和子组件关联起来 @ViewChild('footerChild') footer; // 调用子组件 run(){ this.footer.footerRun(); }
由于组件过度嵌套会导致数据冗余和事件传递,因此引入投影组件的概念
投影组件ng-content
作为一个容器组件使用
主要用于组件动态内容的渲染,而这些内容没有复杂的业务逻辑,也不需要重用,只是一小部分 HTML 片段
使用ng-content
指令将父组件模板中的任意片段投影到它的子组件上
组件里面的ng-content
部分可以被组件外部包裹的元素替代
// 表现形式:
select
表明包含appGridItem
的指令的元素才能投影穿透过来
指令可以理解为没有模版的组件,它需要一个宿主元素(Host)
推荐使用方括号 [] 指定 Selector,使它变成一个属性
@Directive({ selector: '[appGridItem]' })
NgClass
ngClass
是自由度和拓展性最强的样式绑定方式
这是一个 div
NgStyle
ngStyle
由于是嵌入式样式,因此可能会覆盖掉其他样式,需谨慎
你好 ngStyle
NgModel
// 注意引入:FormsModule import { FormsModule } from '@angular/forms'; {{inputValue}}
ngIf
ngIf 根据表达式是否成立,决定是否展示 DOM 标签
3">这是 ngIF 判断是否显示
ngIf else
这是 ngIF 内容// 结构性指令都依赖于 ng-template,*ngIf 实际上就是 ng-template 指令的 [ngIf] 属性。 这是 else 内容
ngFor
ngSwitch
@HostBinding
绑定宿主的属性或者样式
@HostBinding('style.display') display = "grid"; // 用样式绑定代替rd2的 this.setStyle('display','grid');
@HostListener
绑定宿主的事件
@HostListener('click',['$event.target']) // 第一个参数是事件名,第二个是事件携带参数
生命周期函数通俗的讲就是组件创建、组件更新、组件销毁的时候会触发的一系列的方法
当 Angular 使用构造函数新建一个组件或指令后,就会按下面规定的顺序在特定时刻调用生命周期钩子
constructor :构造函数永远首先被调用,一般用于变量初始化以及类实例化
ngOnChanges :被绑定的输入属性变化时被调用,首次调用一定在 ngOnInit 之前。输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChanges
ngOnInit :组件初始化时被调用,在第一轮 ngOnChanges 完成之后调用,只调用一次。使用 ngOnInit 可以在构造函数之后马上执行复杂的初始化逻辑,同时在 Angular 设置完输入属性之后,可以很安全的对该组件进行构建
ngDoCheck :脏值检测时调用,在变更检测周期中 ngOnChanges 和 ngOnInit 之后
ngAfterContentInit :内容投影ng-content完成时调用,只在第一次 ngDoCheck 之后调用
ngAfterContentChecked: 每次完成被投影组件内容的变更检测之后调用(多次)
ngAfterViewInit :组件视图及子视图初始化完成时调用,只在第一次 ngAfterContentChecked 调用一次
ngAfterViewChecked: 检测组件视图及子视图变化之后调用(多次)
ngOnDestroy 当组件销毁时调用,可以反订阅可观察对象和分离事件处理器,以防内存泄漏
路由(导航)本质上是切换视图的一种机制,路由的导航URL并不真实存在
Angular 的路由借鉴了浏览器URL变化导致页面切换的机制
Angular 是单页程序,路由显示的路径不过是一种保存路由状态的机制,这个路径在 web 服务器上不存在
/** * 在功能模块中定义子路由后,只要导入该模块,等同于在根路由中直接定义 * 也就是说在 AppModule 中导入 HomeModule 的时候, * 由于 HomeModule 中导入了 HomeRouting Module * 在 HomeRoutingModule 中定义的路由会合并到根路由表 * 相当于直接在根模块中定义下面的数组。 * const routes = [{ * path: 'home', * component: HomeContainerComponent * }] */ const routes: Routes = [ {path: 'home', component: HomeComponent}, {path: 'news', component: NewsComponent}, {path: 'newscontent/:id', component: NewscontentComponent}, // 配置动态路由 { path: '', redirectTo: '/home', // 重定向 pathMatch: 'full' }, //匹配不到路由的时候加载的组件 或者跳转的路由 { path: '**', /*任意的路由*/ // component:HomeComponent redirectTo:'home' } ] @NgModule({ /** * 根路由使用 `RouterModule.forRoot(routes)` 形式。 * 而功能模块中的路由模块使用 `outerModule.forChild(routes)` 形式。 * 启用路由的 debug 跟踪模式,需要在根模块中设置 `enableTracing: true` */ imports: [RouterModule.forRoot(routes, { enableTracing: true })], exports: [RouterModule] }) export class AppRoutingModule { }
找到app.component.html
根组件模板,配置router-outlet
通过模版属性访问路由,即路由链接routerLink
首页 首页 首页 首页
控制路由激活状态的样式routerLinkActive
首页 新闻
首页 新闻
.active{ color:red; }
路径参数读取
this.route.paramsMap.subscribe(params => {...})
查询参数读取
this.route.queryParamsMap.subscribe(params => {...})
路由传递一个参数及其接收方法:
传递参数:path:’info/:id’
接收参数:
constructor(private routerInfo: ActivatedRoute){} ngOnInit(){ this.routerInfo.snapshot.params['id'] }
路由传递多个参数及其接收方法:
传递:[queryParams]=‘{id:1,name:‘crm’}’
接收参数:
constructor(private routerInfo: ActivatedRoute){} ngOnInit(){ this.routerInfo.snapshot.params['id'] this.routerInfo.snapshot.params['name'] }
懒加载子模块,子模块需要配置路由设置启动子模块loadChildren
const routes: Routes = [ {path:'user',loadChildren:'./module/user/user.module#UserModule' }, {path:'product',loadChildren:'./module/product/product.module#ProductModule'}, {path:'article',loadChildren:'./module/article/article.module#ArticleModule'}, {path:'**',redirectTo:'user'} ]; // 上面好像会报错 Error find module // 配置懒加载 const routes: Routes = [ {path:'user',loadChildren:()=>import('./module/user/user.module').then(mod=>mod.UserModule)}, {path:'article',loadChildren:()=>import('./module/article/article.module').then(mod=>mod.ArticleModule)}, {path:'product',loadChildren:()=>import('./module/product/product.module').then(mod=>mod.ProductModule)}, {path:'**',redirectTo:'user'} ];
组件不应该直接获取或保存数据,应该聚焦于展示数据,而把数据访问的职责委托给某个服务
获取数据和视图展示应该相分离,获取数据的方法应该放在服务中
类似 VueX,全局的共享数据(通用数据)及非父子组件传值、共享数据放在服务中
组件之间相互调用各组件里定义的方法
多个组件都用的方法(例如数据缓存的方法)放在服务(service)里
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class HeroService { aa = 'abc'; constructor(){ } ngOnInit(){ } } import { HeroService } from '../../../services/hero/hero.service'; export class AComponent implements OnInit{ constructor(private heroService : HeroService) {} //实例化 ngOnInit(){ console.log(this.heroService.aa) } }
在 Angular 中,要把一个类定义为服务,就要用@Injectable()
装饰器来提供元数据,以便让 Angular 把它作为依赖注入到组件中。
同样,也要使用@Injectable ()
装饰器来表明一个组件或其它类(比如另一个服务、管道或NgModule
)拥有一个依赖。
@Injectable ()
装饰器把这个服务类标记为依赖注入系统的参与者之一,它是每个 Angular 服务定义中的基本要素。
在未配置好 Angular 的依赖注入器时,Angular 实际上无法将它注入到任何位置。
@Injectable ()
装饰器具有一个名叫providedIn
的元数据选项,providedIn
设置为'root'
,即根组件中,那么该服务就可以在整个应用程序中使用了。
providedIn
提供这些值:‘root'
、'platform'
、'any'
、null
对于要用到的任何服务,必须至少注册一个提供者。
服务可以在自己的元数据中把自己注册为提供者,可以让自己随处可用,也可以为特定的模块或组件注册提供者。
要注册提供者,就要在服务的@Injectable ()
装饰器中提供它的元数据,或者在@NgModule ()
或@Component ()
的元数据中。
在组件中提供服务时,还可以使用viewProdivers
,viewProviders
对子组件树不可见
可以使用不同层级的提供者来配置注入器,也表示该服务的作用范围
Angular 创建服务默认采用的方式:在服务本身的 @Injectable () 装饰器中
该服务只在某服务中使用:在 NgModule 的 @NgModule () 装饰器中
该服务在某组件中使用:在组件的 @Component () 装饰器中
在项目中,有人提供服务,有人消耗服务,而依赖注入的机制提供了中间的接口,并替消费者创建并初始化处理
消费者只需要知道拿到的是完整可用的服务就好,至于这个服务内部的实现,甚至是它又依赖了怎样的其他服务,都不需要关注。
Angular 通过service
共享状态,而这些管理状态和数据的服务便是通过依赖注入的方式进行处理的
Angular 的service
的本质就是依赖注入,将service
作为一个Injector
注入到component
中
归根到底,很多时候我们创建服务,是为了维护公用的状态和数据,通过依赖注入的方式来规定哪些组件可共享
正是因为 Angular 提供的这种依赖注入机制,才能在构造函数中直接声明实例化
constructor(private heroService : HeroService) {} // 依赖注入
先看一下 Angular 中 TS 单文件的注入
// 首先写 @injectable 我们需要注入的东西,比如说 product @Injectable() class Product { constructor( private name: string, private color: string, private price: number, ) { } } class PurchaseOrder { constructor(private product: Product){ } } export class HomeGrandComponent implements OnInit { constructor() { } ngOnInit() { // 构造一个 injector 用 create 方法 里面 providers 数组中写我们需要构造的东西 const injector = Injector.create({ providers: [ { provide: Product, // 构造 Product 在 useFactory 中就会把上面定义的 product 注入到这里 useFactory: () => { return new Product('大米手机', '黑色', 2999); }, deps: [] }, { provide: PurchaseOrder, deps: [Product] }, { provide: token, useValue: { baseUrl: 'http://local.dev' } } ] }); console.log('injector获取product', injector.get(PurchaseOrder).getProduct); console.log(injector.get(token)); }
再看一下Angular 中 module 模块的注入
// .service.ts 中 @Injectable () 依赖注入 @Injectable() export class HomeService { imageSliders: ImageSlider[] = [ { imgUrl:'', link: '', caption: '' } ] getBanners() { return this.imageSliders; } } // 使用模块对应的.module.ts 中 @NgModule({ declarations: [ HomeDetailComponent, ], providers:[HomeService], // 在 providers 直接写对应服务,直接将服务注入模块 imports: [SharedModule, HomeRoutingModule] })
不管是在组件内还是在模块内,我们使用
providers
的时候,就是进行了一次依赖注入的注册和初始化其实模块类(
NgModule
)也和组件一样,在依赖注入中是一个注入器,作为容器提供依赖注入的接口
NgModule
使我们不需要在一个组件中注入另一个组件,通过模块类(NgModule
)可以进行获取和共享
Angular
管道是编写可以在 HTML 组件中声明的显示值转换的方法
管道将数据作为输入并将其转换为所需的输出
管道其实就是过滤器,用来转换数据然后显示给用户
管道将整数、字符串、数组和日期作为输入,用|
分隔,然后根据需要转换格式,并在浏览器中显示出来
在插值表达式中,可以定义管道并根据情况使用
在Angular
应用程序中可以使用许多类型的管道
String
->String
Number
->String
Object
->String
Tools
使用方法
{{ 'Angular' | uppercase }}{{ data | date:'yyyy-MM-dd' }}{{ { name: 'ccc' } | json }}{{ 'ccc' | slice:0:1 | uppercase }}
管道本质上就是个类,在这个类里面去实现PipeTransfrom
接口的transform
这个方法
@Pipe
装饰器定义Pipe
的metadata
信息,如Pipe
的名称 - 即name
属性PipeTransform
接口中定义的transform
方法// 引入PipeTransform是为了继承transform方法 import { Pipe, PipeTransform } form '@angular/core'; // name属性值惯用小驼峰写法, name的值为html中 | 后面的名称 @Pipe({ name: 'sexReform' }) export class SexReformPipe implements PipeTransform { transform(value: string, args?: any): string { // value的值为html中 | 前面传入的值, args为名称后传入的参数 switch(value){ case 'male': return '男'; case 'female': return '女'; default: return '雌雄同体'; } } } // demo.component.ts export Class DemoComponent { sexValue = 'female'; } // demo.component.html {{ sexValue | sexReform }} // 浏览器输出 女 // 管道可以链式使用,还可以传参 {{date | date: 'fullDate' | uppercase}} // 每一个自定义管道都需要实现 PipeTransform 接口,这个接口非常简单,只需要实现 transform 方法即可。 // transform()方法参数格式 - transform(value: string, args1: any, args2?: any): // value为传入的值(即为需要用此管道处理的值, | 前面的值); // args 为传入的参数(?:代表可选); // html 中使用管道格式 - {{ 数据 | 管道名 : 参数1 : 参数2 }} // 与 component 一样,pipe 需要先在 declarations 数组中声明后使用
ngAfterViewInit(){ var boxDom:any=document.getElementById('box'); boxDom.style.color='red'; }
ElementRef
是对视图中某个原生元素的包装类
因为 DOM 元素不是 Angular 中的类,所以需要一个包装类以便在 Angular 中使用和标识其类型
ElementRef
的背后是一个可渲染的具体元素。在浏览器中,它通常是一个 DOM 元素
class ElementRef{ constructor(nativeElement: T) nativeElement: T //背后的原生元素,如果不支持直接访问原生元素,则为 null(比如:在 Web Worker 环境下运行此应用的时候)。 }
当需要直接访问 DOM 时,请把本 API 作为最后选择 。优先使用 Angular 提供的模板和数据绑定机制
如果依赖直接访问 DOM 的方式,就可能在应用和渲染层之间产生紧耦合。这将导致无法分开两者,也就无法将应用发布到 Web Worker 中
使用模板和数据绑定机制,使用@viewChild
// 模版中给 DOM 起一个引用名字,以便可以在组件类或模版中进行引用 // 引入 ViewChild import { ViewChild,ElementRef } from '@angular/core'; // 用 ViewChild 绑定 DOM @ViewChild('myattr') myattr: ElementRef; // 在 ngAfterViewInit 生命周期函数里可以很安全的获取 ViewChild 引用的 DOM ngAfterViewInit(){ let attrEl = this.myattr.nativeElement; }
父组件中可以通过ViewChild
调用子组件的方法
// 给子组件定义一个名称// 引入 ViewChild import { Component, OnInit ,ViewChild} from '@angular/core'; // ViewChild 和子组件关联起来 // 如果想引用模版中的 Angular 组件,ViewChild 中可以使用引用名,也可以使用组件类型 @ViewChild('footerChild') footer; // @ViewChild('imageSlider', { static: true }) // static指定是动态还是静态,在*ngFor或者*ngIf中是动态,否则即为静态,动态为 true // 调用子组件 run(){ this.footer.footerRun(); }
引用多个模版元素,可以用@ViewChildren
,在ViewChildren
中可以使用引用名
或者使用 Angular 组件/指令的类型,声明类型为QueryList>
// 使用 ViewChildren 引用获取 @ViewChildren(’img‘); // 使用类型引用获取 imgs: QueryList;
Renderer2
是 Angular 提供的操作element
的抽象类,使用该类提供的方法,能够实现在不直接接触 DOM 的情况下操作页面上的元素。
Renderer2
的常用方法:
addClass
/removeClass
在directive
的宿主元素添加或删除class
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core'; @Directive({ selector: '[testRenderer2]' }) export class TestRenderer2Directive implements OnInit { constructor(private renderer: Renderer2, private el: ElementRef) {} // 实例化 ngOnInit() { this.renderer.addClass(this.el.nativeElement, 'test-renderer2'); // this.renderer.removeClass(this.el.nativeElement, 'old-class'); } }
createElement
/appendChild
/createText
创建 DIV 元素,插入文本内容,并将其挂载到宿主元素上import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core'; constructor(private renderer: Renderer2, private el: ElementRef) {} ngOnInit() { const div = this.renderer.createElement('div'); const text = this.renderer.createText('Hello world!'); this.renderer.appendChild(div, text); this.renderer.appendChild(this.el.nativeElement, div); }
setAttribute
/removeAttribute
在宿主元素上添加或删除attribute
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core'; constructor(private renderer: Renderer2, private el: ElementRef) {} ngOnInit() { this.renderer.setAttribute(this.el.nativeElement, 'aria-hidden', 'true'); }
setStyle
/removeStyle
在宿主元素上添加inline-style
import { Directive, Renderer2, ElementRef, OnInit } from '@angular/core'; constructor(private renderer: Renderer2, private el: ElementRef) {} ngOnInit() { this.renderer.setStyle( this.el.nativeElement, 'border-left', '2px dashed olive' ); }
移除inline-style
:
constructor(private renderer: Renderer2, private el: ElementRef) {} ngOnInit() { this.renderer.removeStyle(this.el.nativeElement, 'border-left'); }
setProperty
设置宿主元素的property
的值constructor(private renderer: Renderer2, private el: ElementRef) {} ngOnInit() { this.renderer.setProperty(this.el.nativeElement, 'alt', 'Cute alligator'); }
直接操作DOM,
Angular
不推荐。尽量采用@viewChild
和renderer2
组合,Angular
推荐使用constructor(private rd2: Renderer2) {}
依赖注入,
import { Component, OnInit, Renderer2, ViewChild, } from '@angular/core'; import { AboxItemComponent } from './abox-item/abox-item.component'; @Component({ selector: 'app-abox', templateUrl: './abox.component.html', styleUrls: ['./abox.component.less'], }) export class AboxComponent implements OnInit { private container; activeIndex: number; @ViewChild('containers') containers: any; constructor(private rd2: Renderer2) {} ngOnInit(): void {} ngAfterViewInit(): void { this.container = this.containers.nativeElement; this.initCarouselWidth(); } initCarouselWidth() { this.rd2.setStyle(this.container, 'width', '100px'); } }
需导入HttpClientModule
,只在根模块中导入,并且整个应用只需导入一次,不用在其他模块导入
在构造函数中注入HttpClient
,get/post
方法对应HTTP方法,这些方法是泛型的,可以直接把返回的JSON转换成对应类型。若是不规范的请求,使用request
方法
返回的值是Observable
,必须订阅才会发送请求,否则不会发送
get 请求数据
// 在 app.module.ts 中引入 HttpClientModule 并注入 import {HttpClientModule} from '@angular/common/http'; imports: [ BrowserModule, HttpClientModule ] // 在用到的地方引入 HttpClient 并在构造函数声明 import {HttpClient} from "@angular/common/http"; constructor(private http: HttpClient,private cd: ChangeDetectorRef) { } // 依赖注入 // get 请求数据 var api = "http://baidu.com/api/productlist"; this.http.get(api).subscribe(response => { console.log(response); this.cd.markForCheck(); // 如果改变了脏值检测的变更原则 changeDetection: ChangeDetectionStrategy.OnPush // 则需要使用 this.cd.markForCheck() 手动提醒 Angular 这里需要进行脏值检测 });
post 提交数据
// 在 app.module.ts 中引入 HttpClientModule 并注入 import {HttpClientModule} from '@angular/common/http'; imports: [ BrowserModule, HttpClientModule ] // 在用到的地方引入 HttpClient 、HttpHeaders 并在构造函数声明 HttpClient import {HttpClient,HttpHeaders} from "@angular/common/http"; constructor(private http:HttpClient) { } // 实例化 // post 提交数据 const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; var api = "http://127.0.0.1:4200/doLogin"; this.http.post(api,{username:'瑞萌萌',age:'22'},httpOptions).subscribe(response => { console.log(response); });
Jsonp请求数据
// 在 app.module.ts 中引入 HttpClientModule、HttpClientJsonpModule 并注入 import {HttpClientModule,HttpClientJsonpModule} from'@angular/common/http'; imports: [ BrowserModule, HttpClientModule, HttpClientJsonpModule ] // 在用到的地方引入 HttpClient 并在构造函数声明 import {HttpClient} from "@angular/common/http"; constructor(private http:HttpClient) { } // 实例化 // jsonp 请求数据 var api = "http://baidu.com/api/productlist"; this.http.jsonp(api,'callback').subscribe(response => { console.log(response); });
Angular 拦截器是 Angular 应用中全局捕获和修改 HTTP 请求和响应的方式,例如携带Token
和捕获Error
前提是只能拦截使用HttpClientModule
发出的请求,如果使用axios
则拦截不到
创建拦截器
// 使用命令 ng g interceptor name,在这里创建拦截器 ng g interceptor LanJieQi // cli 生成拦截器是没有简写方式的 import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable() export class LanJieQiInterceptor implements HttpInterceptor { constructor() {} // 默认的 intercept() 方法只是单纯的将请求转发给下一个拦截器(如果有),并最终返回 HTTP 响应体的 Observable // request: HttpRequest表示请求对象,包含了请求相关的所有信息,unknown指定请求体body的类型 // next: HttpHandler 请求对象修改完成,将修改后的请求对象通过next中的handle方法传回真正发送请求的方法中 intercept(request: HttpRequest , next: HttpHandler): Observable > { // next 对象表示拦截器链表中的下一个拦截器(在应用中可以设置多个拦截器) return next.handle(request); } }
注入拦截器
// 在 @NgModule 模块中注入拦截器 // 拦截器也是一个由 Angular 依赖注入 (DI) 系统管理的服务,也必须先提供这个拦截器类,才能使用它 // 由于拦截器是 HttpClient 服务的依赖,所以必须在提供 HttpClient 的同一个(或其各级父注入器)注入器中提供这些拦截器 @NgModule({ imports: [ HttpClientModule // others... ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: LanJieQiInterceptor, // multi: true 表明 HTTP_INTERCEPTORS 是一个多重提供者的令牌,表示这个令牌可以注入多个拦截器 multi: true }, ], bootstrap: [AppComponent] }) export class AppModule { }
请求头拦截
@Injectable()export class LanJieQiInterceptor implements HttpInterceptor { constructor() {} intercept(request: HttpRequest, next: HttpHandler): Observable > { // 为了统一设置请求头,需要修改请求 // 但 HttpRequest 和 HttpResponse 实例的属性却是只读(readonly)的 // 所以修改前需要先 clone 一份,修改这个克隆体后再把它传给 next.handle() let req = request.clone({ setHeaders:{ token:"123456" // 在请求头中增加 token:123456 } // setHeaders 和 headers: request.headers.set('token', '123456') 一致 }) return next.handle(req)// 将修改后的请求返回给应用 }}
响应捕获
@Injectable() export class LanJieQiInterceptor implements HttpInterceptor { constructor() {} intercept(request: HttpRequest, next: HttpHandler): Observable > { // 为了统一设置请求头,需要修改请求 // 但 HttpRequest 和 HttpResponse 实例的属性却是只读(readonly)的 // 所以修改前需要先 clone 一份,修改这个克隆体后再把它传给 next.handle() let req = request.clone({ setHeaders:{ token:"123456" // 在请求头中增加 token:123456 } // setHeaders 和 headers: request.headers.set('token', '123456') 一致 }) return next.handle(req)// 将修改后的请求返回给应用 } }
如果有多个拦截器,请求顺序是按照配置顺序执行,响应拦截则是相反的顺序
如果提供拦截器的顺序是先 A再 B再 C,那么请求阶段的执行顺序就是 A->B->C,而响应阶段的执行顺序则是 C->B->A
模板驱动表单在往应用中添加简单的表单时非常有用,但是不像响应式表单那么容易扩展
如果有非常基本的表单需求和简单到能用模板管理的逻辑,就使用模板驱动表单
响应式表单和模板驱动表单共享了一些底层构造块:
FormControl
实例用于追踪单个表单控件的值和验证状态
FormGroup
用于追踪一个表单控件组的值和状态
FormArray
用于追踪表单控件数组的值和状态,有长度属性,通常用来代表一个可以增长的字段集合
ControlValueAccessor
用于在 Angular 的FormControl
实例和原生 DOM 元素之间创建一个桥梁
FormControl
和FormGroup
是 angular 中两个最基本的表单对象
FormControl
代表单一的输入字段,它是 Angular 表单中最小单员,它封装了这些字段的值和状态,比如是否有效、是否脏(被修改过)或是否有错误等
FormGroup
可以为一组FormControl
提供总包接口(wrapper interface),来管理多个FormControl
当我们试图从FormGroup
中获取 value 时,会收到一个 “键值对” 结构的对象
它能让我们从表单中一次性获取全部的值而无需逐一遍历FormControl
,使用起来相当顺手
FormGroup
和FormControl
都继承自同一个祖先AbstractControltractControl
(这是FormControl
,FormGroup
和FormArray
的基类)
首先加载FormsModule
// 先在 NgModule 中导入了 FormsModule 表单库 // FormsModule 为我们提供了一些模板驱动的指令,例如:ngModel、NgForm import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ FormsDemoApp, DemoFormSku, // ... our declarations here ], imports: [ BrowserModule, FormsModule, ], bootstrap: [ FormsDemoApp ] }) class FormsDemoAppModule {}
接下来创建一个模版表单
基础表单:商品名称
我们导入了FormsModule
,因此可以在视图中使用NgForm
了
当这些指令在视图中可用时,它就会被附加到任何能匹配其 selector 的节点上
NgForm
做了一件便利但隐晦的工作:它的选择器包含 form 标签(而不用显式添加ngForm
属性)
这意味着当导入FormsModule
时候,NgForm
就会被自动附加到视图中所有的标签上
NgForm
提供了两个重要的功能:
ngForm
的FormGroup
对象ngSubmit
)CDK 是Component Dev kit
的简称,是 Angular Material 团队在开发 Library 时发现组件有很多相似的地方,最后进行了抽取,提炼出了公共的逻辑,这部分即是 CDK
官方用了一个很形象的比喻:如果组件库是火箭飞船,那么 CDK 就是发动机零件盒
更多编程相关知识,请访问:编程入门!!
The above is the detailed content of Learn more about Angular (a beginner's guide). For more information, please follow other related articles on the PHP Chinese website!