Home>Article>Web Front-end> Learn more about Angular (a beginner's guide)

Learn more about Angular (a beginner's guide)

青灯夜游
青灯夜游 forward
2022-12-06 20:19:45 2578browse

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!

Learn more about Angular (a beginner's guide)

Angular Overview

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.

Angular Program Architecture

Learn more about Angular (a beginners guide)

##Angular Advantages

    Scalability: Based on RxJS, immutable.js and other push models, it can adapt to massive data requirements
  • Cross-platform: progressive application (high performance, offline use, installation-free ), native (Ionic), desktop
  • Productivity: Templates (quickly create UI views through simple and powerful template syntax), CLI (quickly enter the build process, add components and tests, and then deploy immediately)
  • Testing: unit testing (supports tools such as Karma and Jasmine for unit testing), end-to-end testing (supports tools such as Protractor for end-to-end testing)

@angular/cli scaffolding

ng new New project

    ——routing Configure routing
  • ——style=css|scss| less configure css style
ng serve start project

    ——port 4200 port number, default 4200
  • ——open automatically open the browser
ng build packaging project

    ——aot pre-compilation
  • ——prod compression packaging
  • ——base-href=/static /
ng generate Create module/component/service

    module ——routing Create module
  • component Create component
  • service / Create service

#File loading sequence

main.ts => app.module.ts => app.component.ts => ; index.html => app.component.html

Project directory structure
|-- 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 moduleDefine AppModule in app.module.ts. This root module will tell Angular how to assemble the application.

Learn more about Angular (a beginners guide)

@NgModule Decorator@NgModule accepts a metadata object that tells Angular how to compile and Start the application

Design intent

Static metadata (declarations)
  • Runtime metadata (providers)
  • Combination and grouping (imports and exports)
Metadata

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
  • providers array: Services that need to be used in the module
  • imports array: import the dependent modules required by this module, note that it is module
  • Exports array: Expose components, instructions or pipes used by other modules
  • bootstrap array: Specify the main view of the application (called the root component) to start the application by bootstrapping the root AppModule, that is, the project just Select which component to read when loading
  • entryComponents array: generally used for dynamic components

Built-in modulesCommonly used The ones include: core module, general module, form module, network module, etc.

Learn more about Angular (a beginners guide)

Custom moduleWhen the project is relatively small, you don’t need to customize the 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

Tips for modulesWhen importing other modules , you need to know the purpose of using this module

If it is a component, then it needs to be imported in every required module
  • If it is a service, then generally in the root module Import once
  • Need to import in each required module

    CommonModule
  • : Provides binding, *ngIf and *ngFor and other basic instructions, basically every module needs to import it
  • FormsModule / ReactiveFormsModule
  • : The form module needs to be imported in each required moduleProvide components, instructions or Pipeline module
  • Only imported once in the root module

##HttpClientModule / BrowerAnimationsModule NoopAnimationsModule
  • Module that only provides services

Angular Component

  • 组件是 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)]

    // 注意引入:FormsModule import { 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解决),每一次输入变化都必须是新的引用。

父子组件通讯

Learn more about Angular (a beginners guide)

父组件给子组件传值 @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(); }

投影组件

Learn more about Angular (a beginners guide)

由于组件过度嵌套会导致数据冗余和事件传递,因此引入投影组件的概念

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

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

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

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

// 表现形式:  

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

Angular指令

Learn more about Angular (a beginners guide)

指令可以理解为没有模版的组件,它需要一个宿主元素(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 内容

这是 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 使用构造函数新建一个组件或指令后,就会按下面规定的顺序在特定时刻调用生命周期钩子

  • constructor :构造函数永远首先被调用,一般用于变量初始化以及类实例化

  • ngOnChanges :被绑定的输入属性变化时被调用,首次调用一定在 ngOnInit 之前。输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChanges

  • ngOnInit :组件初始化时被调用,在第一轮 ngOnChanges 完成之后调用,只调用一次。使用 ngOnInit 可以在构造函数之后马上执行复杂的初始化逻辑,同时在 Angular 设置完输入属性之后,可以很安全的对该组件进行构建

  • ngDoCheck :脏值检测时调用,在变更检测周期中 ngOnChanges 和 ngOnInit 之后

    • ngAfterContentInit :内容投影ng-content完成时调用,只在第一次 ngDoCheck 之后调用

    • ngAfterContentChecked: 每次完成被投影组件内容的变更检测之后调用(多次)

    • ngAfterViewInit :组件视图及子视图初始化完成时调用,只在第一次 ngAfterContentChecked 调用一次

    • ngAfterViewChecked: 检测组件视图及子视图变化之后调用(多次)

  • ngOnDestroy 当组件销毁时调用,可以反订阅可观察对象和分离事件处理器,以防内存泄漏

Angular路由

路由(导航)本质上是切换视图的一种机制,路由的导航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'} ];

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 创建服务默认采用的方式:在服务本身的 @Injectable () 装饰器中

  • 该服务只在某服务中使用:在 NgModule 的 @NgModule () 装饰器中

  • 该服务在某组件中使用:在组件的 @Component () 装饰器中

依赖注入

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

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

Angular 通过service共享状态,而这些管理状态和数据的服务便是通过依赖注入的方式进行处理的

Angular 的service的本质就是依赖注入,将service作为一个Injector注入到component

归根到底,很多时候我们创建服务,是为了维护公用的状态和数据,通过依赖注入的方式来规定哪些组件可共享

Learn more about Angular (a beginners guide)

正是因为 Angular 提供的这种依赖注入机制,才能在构造函数中直接声明实例化

constructor(private heroService : HeroService) {} // 依赖注入

Learn more about Angular (a beginners guide)

先看一下 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 管道

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

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

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

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

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

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

内置管道

  • String->String
    • UpperCasePipe 转换成大写字符
    • LowerCasePipe 转换成小写字符
    • TitleCasePipe 转换成标题形式,第一个字母大写,其余小写
  • Number->String
    • DecimalPipe 根据数字选项和区域设置规则格式化值
    • PercentPipe 将数字转换为百分比字符串
    • CurrencyPipe 改变人名币格式
  • Object->String
    • JsonPipe 对象序列化
    • DatePipe 日期格式转换
  • Tools
    • SlicePipe 字符串截取
    • 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.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 数组中声明后使用

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 起一个引用名字,以便可以在组件类或模版中进行引用 
// 引入 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

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'); }

直接操作DOM,Angular不推荐。尽量采用@viewChildrenderer2组合,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'); } }

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 并在构造函数声明 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

Angular表单

模版驱动表单

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

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

响应式表单和模板驱动表单共享了一些底层构造块:

FormControl实例用于追踪单个表单控件的值和验证状态

FormGroup用于追踪一个表单控件组的值和状态

FormArray用于追踪表单控件数组的值和状态,有长度属性,通常用来代表一个可以增长的字段集合

ControlValueAccessor用于在 Angular 的FormControl实例和原生 DOM 元素之间创建一个桥梁

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

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

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

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

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

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

首先加载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提供了两个重要的功能:

  • 一个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

记住以下两点:

  1. 如果想隐式创建新的 FormGroup 和 FormControl,使用:ngForm、ngModel
  2. 如果要绑定一个现有的 FormGroup 和 FormControl,使用:formGroup、formControl

表单验证

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

因此,我们要用到验证器,由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- 表单控件值的格式是 email
  • Validators.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 对象,他的键是” 错误代码 “,它的值是 true export 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 就是发动机零件盒
Learn more about Angular (a beginners guide)

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

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!

Statement:
This article is reproduced at:csdn.net. If there is any infringement, please contact admin@php.cn delete