【全球新要闻】深入了解Angular(新手入门指南)

时间:2022-12-06 20:44:44       来源:转载
本篇文章带大家深入了解Angular,分享最全的Angular新手入门指南,希望对大家有所帮助!


(资料图)

Angular概述

Angular 是谷歌开发的一款开源的 web 前端框架,基于 TypeScript 。【相关教程推荐:《angular教程》】

和 react 与 vue 相比, Angular 更适合中大型企业级项目。

Angular程序架构

Angular优势

可伸缩性:基于RxJS 、immutable.js和其他推送模型,能适应海量数据需求跨平台:渐进式应用(高性能、离线使用、免安装),原生(Ionic),桌面端生产率:模版(通过简单而强大的模版语法,快速创建UI视图),CLI(快速进入构建环节、添加组件和测试,然后立即部署)测试:单元测试(支持Karma、Jasmine等工具进行单元测试),端到端测试(支持Protractor等工具进行端到端测试)

@angular/cli脚手架

ng new 新建项目

——routing 配置路由——style=css|scss|less 配置css样式

ng serve 启动项目

——port 4200 端口号,默认4200——open 自动打开浏览器

ng build 打包项目

——aot 预编译——prod 压缩打包——base-href=/static/

ng generate 创建模块/组件/服务

module ——routing 创建模块component 创建组件service / 创建服务

文件加载顺序

项目目录结构

|-- 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 // 开发环境
登录后复制

Angular模块

在 app.module.ts 中定义 AppModule,这个根模块会告诉 Angular 如何组装应用。

@NgModule 装饰器

@NgModule 接受一个元数据对象,告诉 Angular 如何编译和启动应用

设计意图

静态的元数据(declarations)运行时的元数据(providers)组合与分组(imports 和 exports)

元数据

declarations 数组:模块拥有的组件、指令或管道,注意每个组件/指令/管道只能在一个模块中声明providers 数组: 模块中需要使用的服务imports 数组:导入本模块需要的依赖模块,注意是模块exports 数组: 暴露给其他模块使用的组件、指令或管道等bootstrap 数组:指定应用的主视图(称为根组件)通过引导根 AppModule 来启动应用,即项目刚加载时选择读哪个组件entryComponents 数组:一般用于动态组件

内置模块

常用的有:核心模块、通用模块、表单模块、网络模块等

自定义模块

当项目比较小的时候可以不用自定义模块

但是当项目非常庞大的时候,把所有的组件都挂载到根模块里面就不太合适了

所以可以使用自定义模块来组织项目,并且通过自定义模块可以实现路由的懒加载

模块的tips

导入其他模块时,需要知道使用该模块的目的

如果是组件,那么需要在每一个需要的模块中都进行导入如果是服务,那么一般来说在根模块导入一次即可

需要在每个需要的模块中进行导入的

CommonModule: 提供绑定、*ngIf 和 *ngFor 等基础指令,基本上每个模块都需要导入它FormsModule / ReactiveFormsModule: 表单模块需要在每个需要的模块导入提供组件、指令或管道的模块

只在根模块导入一次的

HttpClientModule / BrowerAnimationsModule NoopAnimationsModule只提供服务的模块

Angular组件

组件是 Angular 的核心,是 Angular 应用中最基本的 UI 构造块,控制屏幕上被称为视图的一小片区域组件必须从属于某个 NgModule才能被其他组件或应用使用组件在 @NgModule元数据的 declarations字段中引用

@Component 元数据

selector :选择器,选择相匹配的HTML里的指令模版templateUrl :将选择器中匹配的指令同级替换成值的模版template :内嵌模版,直接可以在里面写HTML模版styleUrls :对应模版的样式,为一个数组,可以引入多个css样式控制组件encapsulation:组件样式封装策略
@Component({  selector: "app-xxx",  templateUrl: "XXX",  styleUrls: ["XXX"],  encapsulation:ViewEncapsulation.Emulated  // 不写则默认该值,表示该组件样式只作用于组件本身,不影响全局样式,在 head 中生成单独的 style 标签})
登录后复制

数据绑定

数据绑定 {{data}}

属性绑定 [id]="id",其中[class.样式类名]=“判断表达式”是在应用单个class样式时的常用技巧

事件绑定 (keyup)="keyUpFn($event)"

样式绑定可以用 :host这样一个伪类选择器,绑定的样式作用于组件本身

双向数据绑定 [(ngModel)]

// 注意引入:FormsModuleimport { FormsModule } from "@angular/forms"; {{inputValue}}// 其实是一个语法糖[ngModel]="username" (ngModelChange)="username = $event"
登录后复制

脏值检测

脏值检测:当数据改变时更新视图(DOM)

如何进行检测:检测两个状态值(当前状态和新状态)

何时触发脏值检测:浏览器事件(clickmouseoverkeyup等)、setTimeout()setInterval()、HTTP请求

Angular 有两种变更检测策略:DefaultOnPush

可以通过在@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:stringconstructor() { }ngOnInit() { }}// 子组件中使用父组件的数据

这是头部组件--{{msg}}

登录后复制

**子组件触发父组件的方法 @Output **

// 子组件引入 Output 和 EventEmitterimport { 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和方法

// 给子组件定义一个名称// 引入 ViewChildimport { Component, OnInit ,ViewChild} from "@angular/core";// ViewChild 和子组件关联起来@ViewChild("footerChild") footer;// 调用子组件run(){   this.footer.footerRun();}
登录后复制

投影组件

投影组件 ng-content作为一个容器组件使用

主要用于组件动态内容的渲染,而这些内容没有复杂的业务逻辑,也不需要重用,只是一小部分 HTML 片段

使用 ng-content指令将父组件模板中的任意片段投影到它的子组件上

组件里面的 ng-content部分可以被组件外部包裹的元素替代

// 表现形式: 
登录后复制

select表明包含 appGridItem的指令的元素才能投影穿透过来

Angular指令

内置属性型指令

NgClass

ngClass是自由度和拓展性最强的样式绑定方式

这是一个 div
登录后复制

NgStyle

ngStyle由于是嵌入式样式,因此可能会覆盖掉其他样式,需谨慎

你好 ngStyle
登录后复制

NgModel

// 注意引入:FormsModuleimport { FormsModule } from "@angular/forms"; {{inputValue}}
登录后复制

内置结构型指令

ngIf

ngIf 根据表达式是否成立,决定是否展示 DOM 标签

这是 ngIF 判断是否显示

登录后复制

ngIf else

这是 ngIF 内容

这是 else 内容

// 结构性指令都依赖于 ng-template,*ngIf 实际上就是 ng-template 指令的 [ngIf] 属性。
登录后复制

ngFor

  • {{item}} --{{i}}
登录后复制

ngSwitch

  • 已支付
  • 已确认
  • 已发货
  • 已失效
登录后复制

指令事件样式绑定

@HostBinding绑定宿主的属性或者样式

@HostBinding("style.display") display = "grid";// 用样式绑定代替rd2的 this.setStyle("display","grid");
登录后复制

@HostListener绑定宿主的事件

@HostListener("click",["$event.target"])// 第一个参数是事件名,第二个是事件携带参数
登录后复制

Angular生命周期

生命周期函数通俗的讲就是组件创建、组件更新、组件销毁的时候会触发的一系列的方法

当 Angular 使用构造函数新建一个组件或指令后,就会按下面规定的顺序在特定时刻调用生命周期钩子

Angular路由

路由基本配置

/** * 在功能模块中定义子路由后,只要导入该模块,等同于在根路由中直接定义 * 也就是说在 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"}];
登录后复制

Angular服务

组件不应该直接获取或保存数据,应该聚焦于展示数据,而把数据访问的职责委托给某个服务

获取数据和视图展示应该相分离,获取数据的方法应该放在服务中

类似 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)  }}
登录后复制

@Injectable()装饰器

在 Angular 中,要把一个类定义为服务,就要用 @Injectable()装饰器来提供元数据,以便让 Angular 把它作为依赖注入到组件中。

同样,也要使用 @Injectable ()装饰器来表明一个组件或其它类(比如另一个服务、管道或 NgModule)拥有一个依赖。

@Injectable ()装饰器把这个服务类标记为依赖注入系统的参与者之一,它是每个 Angular 服务定义中的基本要素。

在未配置好 Angular 的依赖注入器时,Angular 实际上无法将它注入到任何位置。

@Injectable ()装饰器具有一个名叫 providedIn的元数据选项,providedIn设置为 "root",即根组件中,那么该服务就可以在整个应用程序中使用了。

providedIn提供这些值:‘root""platform""any"null

对于要用到的任何服务,必须至少注册一个提供者。

服务可以在自己的元数据中把自己注册为提供者,可以让自己随处可用,也可以为特定的模块或组件注册提供者。

要注册提供者,就要在服务的 @Injectable ()装饰器中提供它的元数据,或者在 @NgModule ()@Component ()的元数据中。

在组件中提供服务时,还可以使用 viewProdiversviewProviders对子组件树不可见

依赖注入

在项目中,有人提供服务,有人消耗服务,而依赖注入的机制提供了中间的接口,并替消费者创建并初始化处理

消费者只需要知道拿到的是完整可用的服务就好,至于这个服务内部的实现,甚至是它又依赖了怎样的其他服务,都不需要关注。

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]})
登录后复制

Angular 管道

Angular管道是编写可以在 HTML 组件中声明的显示值转换的方法

管道将数据作为输入并将其转换为所需的输出

管道其实就是过滤器,用来转换数据然后显示给用户

管道将整数、字符串、数组和日期作为输入,用 |分隔,然后根据需要转换格式,并在浏览器中显示出来

在插值表达式中,可以定义管道并根据情况使用

Angular应用程序中可以使用许多类型的管道

内置管道

String-> StringUpperCasePipe 转换成大写字符LowerCasePipe 转换成小写字符TitleCasePipe 转换成标题形式,第一个字母大写,其余小写Number-> StringDecimalPipe 根据数字选项和区域设置规则格式化值PercentPipe 将数字转换为百分比字符串CurrencyPipe 改变人名币格式Object-> StringJsonPipe 对象序列化DatePipe 日期格式转换ToolsSlicePipe 字符串截取AsyncPipe 从异步回执中解出一个值I18nPluralPipe 复数化I18nSelectPipe 显示与当前值匹配的字符串

使用方法

{{ "Angular" | uppercase }}
{{ data | date:"yyyy-MM-dd" }}
{{ { name: "ccc" } | json }}
{{ "ccc" | slice:0:1 | uppercase }}
登录后复制

自定义管道

管道本质上就是个类,在这个类里面去实现 PipeTransfrom接口的 transform这个方法

使用 @Pipe装饰器定义 Pipemetadata信息,如 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.tsexport 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 数组中声明后使用
登录后复制

Angular操作DOM

原生JS操作

ngAfterViewInit(){   var boxDom:any=document.getElementById("box");   boxDom.style.color="red";}
登录后复制

ElementRef

ElementRef 是对视图中某个原生元素的包装类

因为 DOM 元素不是 Angular 中的类,所以需要一个包装类以便在 Angular 中使用和标识其类型

ElementRef的背后是一个可渲染的具体元素。在浏览器中,它通常是一个 DOM 元素

class ElementRef {  constructor(nativeElement: T)  nativeElement: T  //背后的原生元素,如果不支持直接访问原生元素,则为 null(比如:在 Web Worker 环境下运行此应用的时候)。}
登录后复制

当需要直接访问 DOM 时,请把本 API 作为最后选择 。优先使用 Angular 提供的模板和数据绑定机制

如果依赖直接访问 DOM 的方式,就可能在应用和渲染层之间产生紧耦合。这将导致无法分开两者,也就无法将应用发布到 Web Worker 中

ViewChild

使用模板和数据绑定机制,使用 @viewChild

// 模版中给 DOM 起一个引用名字,以便可以在组件类或模版中进行引用 
// 引入 ViewChildimport { ViewChild,ElementRef } from "@angular/core";// 用 ViewChild 绑定 DOM@ViewChild("myattr") myattr: ElementRef;// 在 ngAfterViewInit 生命周期函数里可以很安全的获取 ViewChild 引用的 DOMngAfterViewInit(){ let attrEl = this.myattr.nativeElement;}
登录后复制

父组件中可以通过 ViewChild调用子组件的方法

// 给子组件定义一个名称// 引入 ViewChildimport { 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

Renderer2是 Angular 提供的操作 element的抽象类,使用该类提供的方法,能够实现在不直接接触 DOM 的情况下操作页面上的元素。

Renderer2的常用方法:

addClass/removeClassdirective的宿主元素添加或删除 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");}
登录后复制
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");  }}
登录后复制

Angular网络请求

HttpClient

需导入 HttpClientModule,只在根模块中导入,并且整个应用只需导入一次,不用在其他模块导入

在构造函数中注入HttpClientget/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 并在构造函数声明 HttpClientimport {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)// 将修改后的请求返回给应用  }}
登录后复制

Angular表单

模版驱动表单

模板驱动表单在往应用中添加简单的表单时非常有用,但是不像响应式表单那么容易扩展

如果有非常基本的表单需求和简单到能用模板管理的逻辑,就使用模板驱动表单

FormControlFormGroup是 angular 中两个最基本的表单对象

FormControl代表单一的输入字段,它是 Angular 表单中最小单员,它封装了这些字段的值和状态,比如是否有效、是否脏(被修改过)或是否有错误等

FormGroup可以为一组 FormControl提供总包接口(wrapper interface),来管理多个 FormControl

当我们试图从 FormGroup中获取 value 时,会收到一个 “键值对” 结构的对象

它能让我们从表单中一次性获取全部的值而无需逐一遍历 FormControl,使用起来相当顺手

FormGroupFormControl都继承自同一个祖先 AbstractControltractControl(这是 FormControlFormGroupFormArray的基类)

首先加载 FormsModule

// 先在 NgModule 中导入了 FormsModule 表单库// FormsModule 为我们提供了一些模板驱动的指令,例如:ngModel、NgFormimport {   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提供了两个重要的功能:

一个 ngFormFormGroup对象一个输出事件 (ngSubmit)
登录后复制

NgModel会创建一个新的 FormControl对象,把它自动添加到父 FormGroup上(这里也就是 form 表单对象)

并把这个 FormControl对象绑定到一个 DOM 上

也就是说,它会在视图中的 input标签和 FormControl对象之间建立关联

这种关联是通过 name属性建立的,在本例中是 "name"

响应式表单

使用 ngForm构建 FormControlFormGroup很方便,但是无法提供定制化选项,因此引入响应式表单

响应式表单提供了一种模型驱动的方式来处理表单输入,其中的值会随时间而变化

使用响应式表单时,通过编写 TypeScript 代码而不是 HTML 代码来创建一个底层的数据模型

在这个模型定义好以后,使用一些特定的指令将模板上的 HTML 元素与底层的数据模型连接在一起

FormBuilder是一个名副其实的表单构建助手(可以把他看作一个 “工厂” 对象)

在先前的例子中添加一个 FormBuilder,然后在组件定义类中使用 FormGroup

// 先在 NgModule 中导入了 ReactiveFormsModule 表单库import {   ReactiveFormsModule } from "@angular/forms"; @NgModule({  imports: [    FormsModule,    ReactiveFormsModule  ]}) // 使用 formGroup 和 formControl 指令来构建这个组件,需要导入相应的类import {   FormBuilder,   FormGroup,  ReactiveFormsModule} from "@angular/forms"; // 在组件类上注入一个从 FormBuilder 类创建的对象实例,并把它赋值给 fb 变量(来自构造函数)export class DemoFormSkuBuilder {   myForm: FormGroup;  // myForm 是 FormGroup 类型  constructor(fb: FormBuilder) {     // FormBuilder 中的 group 方法用于创建一个新的 FormGroup    // group 方法的参数是代表组内各个 FormControl 的键值对    this.myForm = fb.group({  // 调用 fb.group () 来创建 FormGroup      // 设置一个名为 sku 的控件,控件的默认值为 "123456"      "sku": ["123456"]     });   }  onSubmit(value: string): void {     console.log("submit value:", value);   } }
登录后复制

在视图表单中使用自定义的 FormGroup

Demo Form: Sku with Builder

SKU
登录后复制

表单验证

用户输入的数据格式并不总是正确的,如果有人输入错误的数据格式,我们希望给他反馈并阻止他提交表单

因此,我们要用到验证器,由 validators模块提供

Validators.required是最简单的验证,表明指定的字段是必填项,否则就认为 FormControl是无效的

如果 FormGroup中有一个 FormControl是无效的, 那整个 FormGroup都是无效的

要为 FormControl对象分配一个验证器 ,可以直接把它作为第二个参数传给 FormControl的构造函数

const control = new FormControl("name", Validators.required);// 在组件定义类中使用 FormBuilder  constructor(fb: FormBuilder) {     this.myForm = fb.group({       "name": ["",Validators.required]     });     this.name = this.myForm.controls["name"];   }
登录后复制

在视图中检查验证器的状态,并据此采取行动

template:`

商品表单:商品名称

名称无效
名称不是以“123”开头
数据已变动
该项必填
只可输入数字和英文
表单无效
表单有效
`export class NonInWarehouseComponent implements OnInit { myForm: FormGroup; name: AbstractControl; constructor(fb: FormBuilder) { this.myForm = fb.group({ name: ["牛奶", Validators.compose([Validators.required, textValidator])], code: ["", [Validators.required, Validators.pattern("^[A-Za-z0-9]*$")]], }); this.name = this.myForm.controls.name; } ngOnInit() { const nameControl = new FormControl("nate"); console.log("nameControl", nameControl); } onSubmit(a: any) { console.log("a", a); }}
登录后复制

内置校验器

Angular 提供了几个内置校验器,下面是比较常用的校验器:

Validators.required- 表单控件值非空Validators.email- 表单控件值的格式是 emailValidators.minLength()- 表单控件值的最小长度Validators.maxLength()- 表单控件值的最大长度Validators.pattern()- 表单控件的值需匹配 pattern 对应的模式(正则表达式)

自定义验证器

假设我们的 name 有特殊的验证需求,比如 name 必须以 123 作为开始

当输入值(控件的值 control.value)不是以 123 作为开始时,验证器会返回错误代码 invalidSku

// angular 源代码中实现 Validators.required export class Validators {  // 接收一个 AbstractControl 对象作为输入static required(control: AbstractControl): ValidationErrors | null;}// 当验证器失败时,会返回一个 String Map 对象,他的键是” 错误代码 “,它的值是 trueexport declare type ValidationErrors = {    [key: string]: any;};// 自定义验证器function textValidator(  controls: FormControl // 因为FormControl继承于 AbstractControl 所以也可以写成FormControl对象): {  [s: string]: boolean;} {  if (!controls.value.match(/^123/)) {    return { textinvalid: true };  }}
登录后复制

FormControl分配验证器,但是 name 已经有一个验证器了,如何在同一个字段上添加多个验证器

Validators.compose来实现

Validators.compose把两个验证器包装在一起,我们可以将其赋值给 FormControl

只有当两个验证器都合法时,FormControl才是合法的

Validators.compose([Validators.required, textValidator])// 不用compose   [Validators.required, textValidator]// 保留 compose 是为了向以前历史版本进行兼容,不用 compose 也可实现
登录后复制

动态表单

要实现 Angular 动态表单,主要使用 formArray方法,formArray生成的实例是一个数组,在这个数组中可以动态的放入 formGroupformControl,这样便形成了动态表单。

export class ReativeFormsComponent implements OnInit {  ngOnInit() {    this.addContact()  }  //动态表单  personMess: FormGroup = new FormGroup({    //生成动态表单数组    contacts: new FormArray([])   })  //获取数组对象  get contacts(){    return this.personMess.get("contacts") as FormArray  }  //增加一个表单组  addContact(){    let myContact = new FormGroup({      name: new FormControl(),      phone: new FormControl()    })    this.contacts.push(myContact)  }   //删除一个表单组  deleteContact(i:number){    this.contacts.removeAt(i)  }  //提交表单  OnSubmit() {    console.log(this.personMess.value)  }}
登录后复制

登录后复制

Angular CDK

CDK 是 Component Dev kit的简称,是 Angular Material 团队在开发 Library 时发现组件有很多相似的地方,最后进行了抽取,提炼出了公共的逻辑,这部分即是 CDK

官方用了一个很形象的比喻:如果组件库是火箭飞船,那么 CDK 就是发动机零件盒

更多编程相关知识,请访问:编程入门!!

以上就是深入了解Angular(新手入门指南)的详细内容,更多请关注php中文网其它相关文章!

关键词: 构造函数 可以使用