angular 学习记录
环境安装
Node.js
yarn(https://www.yarnpkg.cn/)
npm install -g yarn
@angular/cli 命令
npm install -g @angular/cli
ng new 创建项目
--routing = true| false(defualt)
--skip-install 跳过安装
ng serve 启动开发服务器
--port 4200
--open = true | false
ng serve --host 0.0.0.0 手机调试(手机和电脑连接同个wifi,手机打开电脑ip:4200)
ng bulid 应用程序或库编译到给定输出路径处名为 dist/ 的输出目录中。
--aot = true | false 使用提前编译进行构建
--prod = true | false 压缩打包
--base-href = baseHref 在index.html生成 <base>
标签
ng generate 创建模块/组件/服务
-
ng generate module [name]
案例:
ng generate module app-routing --flat --module=app
参数 详情 --flat
把这个文件放进了 src/app
中,而不是单独的目录中。--module=app
告诉 ng generate
把它注册到AppModule
的imports
数组中。--routing 生成 routing.module.ts 文件 -
ng generate component [name]
案例:
ng generate component info
-
ng generate service [name]
案例:
ng generate service info/infoService
这里的info/info,前面个 info 是目录,后面 infoService 是名字
总结:
ng generate component <name>
或ng g c <name>
:生成一个新的组件。这将创建一个包含 TypeScript、HTML 和 CSS 文件的新目录。ng generate directive <name>
或ng g d <name>
:生成一个新的指令。ng generate pipe <name>
或ng g p <name>
:生成一个新的管道。ng generate service <name>
或ng g s <name>
:生成一个新的服务。ng generate module <name>
或ng g m <name>
:生成一个新的模块。ng generate class <name>
或ng g cl <name>
:生成一个新的类。ng generate interface <name>
或ng g i <name>
:生成一个新的接口。ng generate enum <name>
或ng g e <name>
:生成一个新的枚举。ng generate guard <name>
或ng g g <name>
:生成一个新的守卫。ng generate resolver <name>
或ng g r <name>
命令用于在 Angular 应用中生成一个新的 Resolver。每个
ng generate
命令都会生成一个或多个文件,并更新项目的配置。例如,ng generate component
命令会更新app.module.ts
文件,以声明新生成的组件。注意:在
<name>
的位置,你应该提供你想要生成的实体的名称。例如,如果你想生成一个名为 "my-component" 的组件,你应该运行ng generate component my-component
。
理解构建过程
main.js 包含从src/app 文件夹编译的输出
polyfills.js 包含javascriupt polyfill, 用于应用程序使用到,但是目标浏览器不支持的功能
runtime.js 加载其他模块
stlyes.js 全局CSS的JS代码
vendor.js 包含程序依赖的第三方包,包含 angular 包
顺序: runtime.js -> polyfills.js -> stlyes.js -> vendor.js -> main.js
文件加载顺序
绑定语法
数据绑定的类型
Angular 根据数据流的方向提供三种类型的数据绑定:
- 从源到视图
- 从视图到源
- 双向,从视图到源再到视图
类型 | 语法 | 分类 |
---|---|---|
插值 <br> Property <br> Attribute <br> 类 <br> 样式 |
{{expression}} [target]="expression" |
单向从数据源到视图 |
事件 | (target)="statement" |
单向从视图到数据源 |
双向 | [(target)]="expression" |
双向 |
插值以外的绑定类型在等号左侧有一个目标名称。绑定的目标是 property 或事件,你可以用方括号 ( [ ]
) 字符、括号 ( ( )
) 字符或两者 ( [( )]
) 字符括起来。
[]
、()
、[()]
这些绑定标点以及前缀,用来指定数据流的方向。
- 使用
[]
从源绑定到视图 - 使用
()
从视图绑定到源 - 使用
[()]
进行双向绑定,将视图绑定到源再绑定到视图
将表达式或语句放在双引号 ""
中等号的右侧。有关更多信息,请参见插值和模板语句。
绑定类型和目标
数据绑定的目标可以是 Property、事件或 Attribute 的名称。源指令的每个 public 成员都可以自动用于绑定模板表达式或模板语句中。下表总结了不同绑定类型的目标。
类型 | 目标 | 例子 | |
---|---|---|---|
属性 | 元素属性 <br> 组件属性 <br> 指令属性 |
下面例子中的 alt 、src 、hero 和 ngClass:<br><br>``<img [alt]="hero.name" [src]="heroImageUrl"> <br><app-hero-detail [hero]="currentHero"></app-hero-detail>``<br><a [ngClass]="{frist:false,'second':true, three:true}">信息展示</a> |
ngclass 这里是对象,生成 <a _ngcontent-cfx-c16="" ng-reflect-ng-class="[object Object]" class="second three">信息展示</a> |
事件 | 元素事件 <br> 组件事件 <br> 指令事件 |
下面例子中的 click 、deleteRequest 和 myClick :<br><br>``<button type="button" (click)="onSave()">Save</button> <br><app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail> <br><div (myClick)="clicked=$event" clickable>click me</div> |
|
双向 | 事件与属性 | <input [([ngModel)]="name"> |
|
Attribute | Attribute (少数特例情况) | <button type="button" [attr.aria-label]="help">help</button> |
help = false``这里生成 aria-label="false" |
类 | class 属性 |
<div [class.special]="isSpecial">Special</div> |
class="special"``当绑定表达式 isSpecial 为真值时,Angular 会添加类,当表达式为假值时,它会删除类 |
Property 和 Attribute 的比较
尽管从技术角度上说,可以设置 [attr.disabled]
Attribute 这个绑定,但是它的值是不同的,差异在于其 Property 绑定必须是布尔值,而其相应的 Attribute 绑定则取决于该值是否为 null
。考虑以下情况:
<input [disabled]="condition ? true : false">
<input [attr.disabled]="condition ? 'disabled' : null">
第一行使用 disabled
这个 Property,要使用布尔值。第二行使用 disabled
这个 Attribute,要判定是否为 null
。
通常,要使用 Property 绑定而不是 Attribute 绑定。因为布尔值很容易阅读,语法较短,并且 Property 绑定的性能更高。
要在运行的应用程序中查看 disabled
按钮,请参见现场演练 / 下载范例。本示例说明如何从组件中切换 disabled 这个 Property。
属性绑定: [属性名]
property 绑定
要为 image 元素的目标属性(src
)赋值,请键入以下代码:
<img alt="item" [src]="itemImageUrl">
attribute 绑定
在 Attribute 名称前面加上前缀 attr
,后跟一个点 .
。然后,使用解析为字符串的表达式设置 Attribute 值。
<p [attr.attribute-you-are-targeting]="expression"></p>
类和样式绑定(属于 property 绑定):
1 绑定到单个 CSS class
要创建单个类绑定,请键入以下内容:
[class.sale]="onSale"
当绑定表达式 onSale
为真值时,Angular 会添加类,当表达式为假值时,它会删除类 —— undefined
除外。有关更多信息,参阅样式委托。
2 绑定到多个 CSS 类
要绑定到多个类,请键入以下内容:
[class]="classExpression"
表达式可以是以下之一:
- 用空格分隔的类名字符串。
- 以类名作为键名并将真或假表达式作为值的对象。
- 类名的数组。
对于对象格式,Angular 会在其关联的值为真时才添加类。
绑定类型 | 语法 | 输入属性 | 范例输入值 |
---|---|---|---|
单一类绑定 | [class.sale]="onSale" |
`boolean\ | undefined\ |
多重类绑定 | [class]="classExpression" |
string |
"my-class-1 my-class-2 my-class-3" |
多重类绑定 | [class]="classExpression" |
`Record<string, boolean\ | undefined\ |
多重类绑定 | [class]="classExpression" |
Array<string> |
['foo', 'bar'] |
3 多个样式的绑定和嵌入式绑定
事件绑定: (event)
在事件绑定中,Angular 会为目标事件配置事件处理函数。你还可以将事件绑定用于自定义事件。
当组件或指令引发事件时,处理程序就会执行模板语句。模板语句会执行一个动作来响应这个事件。
处理事件
处理事件的常见方法之一是把事件对象 $event
传给处理该事件的方法。$event
对象通常包含该方法所需的信息,比如用户名或图片 URL。
目标事件决定了 $event
对象的形态。如果目标事件是来自原生 DOM 元素的,那么 $event
是一个DOM 事件对象,它具有 target
和 target.value
等属性。
在下面的例子中,代码通过绑定到 name
来设置 <input>
的 value
属性。
<input [value]="currentItem.name"
(input)="currentItem.name=getValue($event)">
这个例子中,会发生下列操作:
- 该代码绑定到
<input>
元素的input
事件,该事件允许代码监听这些更改。 - 当用户做出更改时,该组件会引发
input
事件。 - 这个绑定会在一个上下文中执行该语句,此上下文中包含 DOM 事件对象
$event
。 - Angular 会通过调用
getValue($event.target)
来获取更改后的文本,并用它更新name
属性。
如果该事件属于某个指令或组件,那么 $event
就具有指令或组件中生成的形态。
在模板中,
$event.target
的类型只是EventTarget
。在getValue()
方法中,把此目标转为HTMLInputElement
类型,以允许对其value
属性进行类型安全的访问。getValue(event: Event): string { return (event.target as HTMLInputElement).value; }
双向绑定: [(....)]
双向绑定为应用中的组件提供了一种共享数据的方式。使用双向绑定绑定来侦听事件并在父组件和子组件之间同步更新值。
前提条件
为了充分利用双向绑定,你应该对以下概念有基本的了解:
双向绑定将属性绑定与事件绑定结合在一起:
绑定 | 详情 |
---|---|
属性绑定 | 设置特定的元素属性。 |
事件绑定 | 侦听元素更改事件。 |
双向绑定工作原理
为了使双向数据绑定有效,@Output()
属性的名字必须遵循 inputChange
模式,其中 input
是相应 @Input()
属性的名字。比如,如果 @Input()
属性为 size
,则 @Output()
属性必须为 sizeChange
。
后面的 sizerComponent
具有值属性 size
和事件属性 sizeChange
。size
属性是 @Input()
,因此数据可以流入 sizerComponent
。sizeChange
事件是一个 @Output()
,它允许数据从 sizerComponent
流出到父组件。
案例
<!-- 1.普通的属性和事件实现绑定 -->
<!-- <input type="text" [value]="username" (input)="handuleInput($event)"> -->
<!-- 2.普通双向绑定 表单Form:使用 ngModel -->
<input type="text" [(ngModel)]="username">
<!-- 3.(失败了,当前版本不行,本想使用 [(username)])自定义get, set 方法 -->
<!-- <input type="text" [value]="username" (input)="username"> -->
<span>你好,{{username}}</span>
<!-- 在 Angular v6 中已不推荐把 ngModel 输入属性、ngModelChange 事件与响应式表单指令一起使用,并将在 Angular 的未来版本中删除。 -->
<!-- <input type="text" [ngModel]="username" (ngModelChange)="handuleInput($event)"> -->
horizontal-grid.component.ts
export class HorizontalGridComponent implements OnInit {
// 1的普通绑定和2的双向绑定都行
@Input() username = '';
handuleInput(event:Event): void{
this.username = (event.target as HTMLInputElement).value;
}
// 3.(失败了,当前版本不行,本想使用 [(username)])自定义get, set 方法
// private _username = '';
// @Output() usernameChange = new EventEmitter<string>();
// @Input()
// public get username() : string {
// return this._username;
// }
// public set username(username : string) {
// this._username = username;
// this.usernameChange.emit(username);
// }
constructor() { }
ngOnInit() {
}
}
NgNonBindable 停用绑定
通过 NgNonBindable 停用 Angular 处理过程
<p >This should evaluate: {{ 1 + 1 }}</p>
<p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p>
指令
属性指令:修改宿主元素的行为和外观的类
如何使用属性指令:属性指令是已经应用 @Directive
装饰器的类
内置属性型指令
ngClass
第一种 text
<a [ngClass]="'first second'">信息展示</a>
生成
<a _ngcontent-qxr-c16="" ng-reflect-ng-class="first second" class="first second">信息展示</a>
第二种 array
<a [ngClass]="['frist','second']">信息展示</a>
生成
<a _ngcontent-yos-c16="" ng-reflect-ng-class="frist,second" class="frist second">信息展示</a>
第三种 object
<a [ngClass]="{frist:false,'second':true, three:true}">信息展示</a>
生成
<a _ngcontent-cfx-c16="" ng-reflect-ng-class="[object Object]" class="second three">信息展示</a>
ngStyle
添加和删除一组 HTML 样式
ngModel
将双向数据绑定添加到 HTML 表单元素
内置结构指令
应用结构指令时,它们通常以星号 *
为前缀,例如 *ngIf。
NgIf
ts先声明一下:
export class AppComponent {
public condition = true;
}
1 具有简写语法的简单形式:
<div *ngIf="condition">1.Content to render when condition is true.</div>具有扩展语法的简单形式:
2 扩展语法:
<ng-template [ngIf]="condition"><div>2.Content to render when condition is true.</div></ng-template>
3 带有 “else” 块的格式:
<div *ngIf="condition; else elseBlock">3.1 Content to render when condition is true.</div>
<ng-template #elseBlock>3.2 Content to render when condition is false.</ng-template>
如果 condition = false
;
4 带 “then” 和 “else” 块的简写形式:
<div *ngIf="condition; then thenBlock else elseBlock">4.1 这里的 condition 是条件,true 显示 #thenBlock</div>
<ng-template #thenBlock>4.1 Content to render when condition is true.</ng-template>
<ng-template #elseBlock>4.2 Content to render when condition is false.</ng-template>本地存储值的形式:
如果 condition = false
;
5 本地存储值的形式:
<div *ngIf="condition as value; else elseBlock">{{value}}</div>
<ng-template #elseBlock>5.value is null.</ng-template>
如果 condition = false
;
6 简写语法
*ngIf 的简写语法会把 "then" 和 "else" 子句分别扩展成两个独立的模板。比如,考虑下列简写语句,它要在等待数据加载期间显示一个加载中页面。
<div class="hero-list" *ngIf="heroes else loading">
这里作为模板
</div>
<ng-template #loading>
<div>Loading...</div>
</ng-template>
你可以看到,"else" 子句引用了带有 #loading
标签的 <ng-template>
,而 then
(就是为true的内容) 子句的模板是作为宿主元素的内容提供的。
不过,当 Angular 扩展此简写语法的时候,它创建了另一个带有 ngIf
和 ngIfElse
指令的 <ng-template>
。then
(就是为true的内容) 需要自己创建 <ng-template>
的内容。
<ng-template [ngIf]="heroes" [ngIfElse]="loading">
<div class="hero-list">
单独需要模板
</div>
</ng-template>
<ng-template #loading>
<div>Loading...</div>
</ng-template>
NgFor
ngForOf 指令通常在 *ngFor
的简写形式内部使用。在这种形式下,每次迭代要渲染的模板是包含指令的锚点元素的内容。
<li>
元素中包含一些选项的简写语法。
<li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>
简写形式会扩展为使用 <ng-template>
元素 ngForOf
选择器的长形式。<ng-template>
元素的内容是包裹此简写格式指令的 <li>
元素。
<ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
<li>...</li>
</ng-template>
局部变量
<li *ngFor="let user of users; index as i; first as isFirst">
{{i}}/{{users.length}}. {{user}} <span *ngIf="isFirst">default</span>
</li>
NgForOf
导出了一系列值,可以指定别名后作为局部变量使用:
$implicit: T
:迭代目标(绑定到ngForOf
)中每个条目的值。ngForOf: NgIterable<T>
:迭代表达式的值。当表达式不局限于访问某个属性时,这会非常有用,比如在使用 async 管道时(userStreams | async
)。index: number
:可迭代对象中当前条目的索引。count: number
:可迭代对象的长度。first: boolean
:当前条目是第一个条目则为true
。last: boolean
:当前条目是最后一个条目则为true
。even: boolean
:当前条目在索引号为偶数则为true
。odd: boolean
:当前条目在索引号为奇数则为true
。
html
<ul>
<li *ngFor="let item of tabs, let i = index">
<a href="#" [class.active]="i === selectIndex" (click)="handleSelected(i)">{{item.title}}</a>
</li>
</ul>
ts
export class AppComponent {
title = 'pinduoduo';
selectIndex = -1;
...
handleSelected(index: number){
this.selectIndex = index;
}
TrackBy
一个可选地传入 NgForOf
指令的函数,以自定义 NgForOf
如何唯一标识迭代中的条目。
在所有这些场景中,通常希望仅更新与受更改影响的条目关联的 DOM 元素。此行为对以下内容很重要:
- 修改迭代器时保留任何特定于 DOM 的 UI 状态(例如光标位置、焦点、文本选择)
- 条目添加、删除和重新排序的动画
- 当使用
NgForOf
动态填充嵌套<option>
元素并更新绑定迭代器时,保留<select>
元素的值
NgSwitch
略
ngTemplateOutlet 重复内容块
<ng-template #titleTemplate>
<h4 class="p-2 bg-success text-white">Repeated Content</h4>
</ng-template>
<ng-template [ngTemplateOutlet]="titleTemplate"></ng-template>
<div class="bg-info p-2">
There are {{getProductCount()}} products.
</div>
<ng-template [ngTemplateOutlet]="titleTemplate"></ng-template>
ngTemplateOutletContext 接收上下文
为了接收上下文,在包含重复的 ng-template
定义一个 let-
属性,用于定义变量,类似 ngFor
的指令属性。
下面创建一个 text
的变量,并通过对表达式 title
求值,为其赋值: let-text="title"
,然后使用 ngTemplateOutletContext
提供映射对象。
<ng-template #titleTemplate2 let-text="title">
<h4 class="p-2 bg-success text-white">{{text}}</h4>
</ng-template>
<ng-template [ngTemplateOutlet]="titleTemplate2" [ngTemplateOutletContext]="{title: 'Header'}" ></ng-template>
<div class="bg-info p-2">
There are {{getProductCount()}} products.
</div>
<ng-template [ngTemplateOutlet]="titleTemplate2" [ngTemplateOutletContext]="{title: 'Footer'}"></ng-template>
其他指令
自定义属性指令
a) 创建简单颜色属性案例
第一步,添加指令 @Directive
// attr.directive.ts
import { Directive, ElementRef, Renderer2 } from '@angular/core';
@Directive({
selector: '[pa-attr]',
})
export class PaAttrDirective {
constructor(element: ElementRef, private renderer2: Renderer2) {
// element.nativeElement.classList.add('bg-success', 'text-white');
this.renderer2.addClass(element.nativeElement, 'bg-success');
this.renderer2.addClass(element.nativeElement, 'text-white');
}
}
第二步,@NgModule
的declarations
添加指令 PaAttrDirective
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
// import { AppComponent } from './app.component';
import { ProductComponent } from './component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { PaAttrDirective } from './directive/attr.directive';
@NgModule({
declarations: [ProductComponent, PaAttrDirective], //列出应用的类(组件、指令和管道),告诉Angular哪些类构成了这个模块。构成了这个模块。
imports: [BrowserModule, FormsModule, ReactiveFormsModule],
providers: [], //提供服务
bootstrap: [ProductComponent], //根组件
})
export class AppModule {}
第三步,应用HTML
<div class="col-6">
<table class="table table-sm table-bordered table-striped">
<tr>
<th></th><th>Name</th><th>Category</th><th>Price</th>
</tr>
<tr *ngFor="let item of getProducts(); let i=index" pa-attr>
<td>{{i+1}}</td>
<td>{{item.name}}</td>
<td>{{item.category}}</td>
<td>{{item.price}}</td>
</tr>
</table>
</div>
</div>
b) 指令中访问应用程序数据
b.1) 读取宿主属性 @Attribute
添加一个属性指定宿主元素加入的CSS类。
html
<div class="col-6">
<table class="table table-sm table-bordered table-striped">
<tr>
<th></th><th>Name</th><th>Category</th><th>Price</th>
</tr>
<tr *ngFor="let item of getProducts(); let i=index" pa-attr>
<td>{{i+1}}</td>
<td>{{item.name}}</td>
<td pa-attr pa-attr-class="bg-warning">{{item.category}}</td>
<td pa-attr pa-attr-class="bg-info">{{item.price}}</td>
</tr>
</table>
</div>
pa-attr
属性被用于2个td
元素,有一个新属性 pa-attr-class
. 下面根据新属性,修改宿主元素
应用了 @Attribute
装饰器,创建指令类的新实例时,应该使用该属性为这个构造函数提供形参。
@Attribute 的限制:
元素属性是静态的。
// attr.directive.ts
import { Attribute, Directive, ElementRef, Renderer2 } from '@angular/core';
@Directive({
selector: '[pa-attr]',
})
export class PaAttrDirective {
constructor(
element: ElementRef,
private renderer2: Renderer2,
@Attribute('pa-attr-class') bgClass: string
) {
// element.nativeElement.classList.add('bg-success', 'text-white');
this.renderer2.addClass(element.nativeElement, bgClass || 'bg-success');
this.renderer2.addClass(element.nativeElement, 'text-white');
}
}
b.2) 创建数据绑定输入属性 @Input
如果是需要动态的属性,就需要绑定对表达式的支持。
<div class="col-6">
<table class="table table-sm table-bordered table-striped">
<tr>
<th></th><th>Name</th><th>Category</th><th>Price</th>
</tr>
<tr *ngFor="let item of getProducts(); let i=index"
[pa-attr]="getProducts().length < 6 ? 'bg-success':'bg-warning'">
<td>{{i+1}}</td>
<td>{{item.name}}</td>
<td [pa-attr]="item.category == 'Ruby' ? 'bg-info': null">{{item.category}}</td>
<td [pa-attr]="'bg-info'">{{item.price}}</td>
</tr>
</table>
</div>
tr,td 的 pa-attr
属性中包含表达式,而不仅仅是静态的CSS类。
要实现绑定,需要在指令中创建一个输入属性。
// attr.directive.ts
import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
@Directive({
selector: '[pa-attr]',
})
export class PaAttrDirective {
@Input('pa-attr') bgClass?: string | null; //因为可以为 null. --> [pa-attr]="item.category == 'Ruby' ? 'bg-info': null"
constructor(private element: ElementRef, private renderer2: Renderer2){}
ngOnInit(){
this.renderer2.addClass(this.element.nativeElement, this.bgClass || 'bg-success');
this.renderer2.addClass(this.element.nativeElement, 'text-white');
}
}
@Input
装饰器应用于属性,并使用它来指定包含表达式的属性的名称。
顺序:
-> angular创建指令类的新实例
-> 构造函数
-> 设置输入的值 (意味
构造函数
阶段不能获取输入的值,所以需要在ngOnInit
设置值)-> ngOnInit
b.3) 响应输入属性的变化 ngOnChanges
添加新项仅影响新项的外观,而不影响现有元素。所以必须实现 ngOnChanges
,实时接收通知。
SimpleChange
类属性和方法
previousValue: 输入属性的前值
currentValue: 输入属性的现值
isFirstChange: ngOnChanges 在 ngOnInit 调用之前,返回 true。
// attr.directive.ts
import {
Directive,
ElementRef,
Input,
OnChanges,
Renderer2,
SimpleChanges,
} from '@angular/core';
@Directive({
selector: '[pa-attr]',
}) //implements OnChanges
export class PaAttrDirective {
@Input('pa-attr') bgClass?: string | null; //因为可以为 null. --> [pa-attr]="item.category == 'Ruby' ? 'bg-info': null"
constructor(private element: ElementRef, private renderer2: Renderer2) {}
// ngOnInit(){
// this.renderer2.addClass(this.element.nativeElement, this.bgClass || 'bg-success');
// this.renderer2.addClass(this.element.nativeElement, 'text-white');
// }
ngOnChanges(changes: SimpleChanges) {
console.count()
let change = changes['bgClass'];
let classList = this.element.nativeElement.classList;
if (!change.isFirstChange() && classList.contains(change.previousValue)) {
classList.remove(change.previousValue);
}
if (!classList.contains(change.currentValue)) {
classList.add(change.currentValue);
}
}
}
现在都更新了。
b.4) 创建自定义事件
EventEmitter
类为angular提供了事件机制。下面的例子创建EventEmitter
赋值给一个click
的变量。
@Output("pa-category") click= new EventEmitter<string>();
emit(value)
这个方法触发与 EventEmitter 对象相关的自定义事件,为监听器提供对象或值。
输出属性 @Output
是一项让指令向宿主元素添加自定义事件的angular特性。
// attr.directive.ts
import {
Directive,
ElementRef,
EventEmitter,
Input,
OnChanges,
Output,
Renderer2,
SimpleChanges,
} from '@angular/core';
import { Product } from '../product.model';
@Directive({
selector: '[pa-attr]',
}) //implements OnChanges
export class PaAttrDirective {
@Input('pa-attr') bgClass?: string | null; //因为可以为 null. --> [pa-attr]="item.category == 'Ruby' ? 'bg-info': null"
@Input("pa-product") product? :Product;
@Output("pa-category") click= new EventEmitter<string>();
constructor(private element: ElementRef, private renderer2: Renderer2) {
this.element.nativeElement.addEventListener('click', (event:any) => {
if(this.product != null){
this.click.emit(this.product.category);
}
});
}
ngOnChanges(changes: SimpleChanges) {
console.count()
let change = changes['bgClass'];
let classList = this.element.nativeElement.classList;
if (!change.isFirstChange() && classList.contains(change.previousValue)) {
classList.remove(change.previousValue);
}
if (!classList.contains(change.currentValue)) {
classList.add(change.currentValue);
}
}
}
下面绑定到自定义事件
<!-- 15.4 创建自定义事件 -->
<style>
input.ng-dirty.ng-invalid {
border: 2px solid #ff0000
}
input.ng-dirty.ng-valid {
border: 2px solid #6bc502
}
</style>
<div class="row m-2">
<div class="col-6">
<form class="m-2" novalidate (ngSubmit)="submitForm()">
<div class="form-group">
<label>Name</label>
<input class="form-control" name="name" [(ngModel)]="newProduct.name" />
</div>
<div class="form-group">
<label>Category</label>
<input class="form-control" name="category" [(ngModel)]="newProduct.category" />
</div>
<div class="form-group">
<label>Price</label>
<input class="form-control" name="price" [(ngModel)]="newProduct.price" />
</div>
<button class="btn btn-primary" type="submit">
Create
</button>
</form>
</div>
<div class="col-6">
<table class="table table-sm table-bordered table-striped">
<tr>
<th></th><th>Name</th><th>Category</th><th>Price</th>
</tr>
<tr *ngFor="let item of getProducts(); let i=index"
[pa-attr]="getProducts().length < 6 ? 'bg-success':'bg-warning'"
[pa-product]="item"
(pa-category)="newProduct.category = $event">
<td>{{i+1}}</td>
<td>{{item.name}}</td>
<td [pa-attr]="item.category == 'Ruby' ? 'bg-info': null">{{item.category}}</td>
<td [pa-attr]="'bg-info'">{{item.price}}</td>
</tr>
</table>
</div>
</div>
$event
用于由指令传递给 EventEmitter.emit
方法的值。
效果:点击后,赋值给Category.
c) 处理用户事件
本节会展示如何检测用户何时将鼠标移入或移出元素以及如何通过设置或清除突出显示颜色来进行响应。
HostListener
一个装饰器,用于声明要监听的 DOM 事件,并提供在该事件发生时要运行的处理器方法。
ng generate directive highlight
import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef, private r2: Renderer2) {
//第1种: 直接操作ElementRef
// this.el.nativeElement.style.backgroundColor = 'yellow';
//第2种: 推荐使用 renderer2 设置
// this.r2.setStyle(this.el.nativeElement, "background-color", 'yellow');
}
@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight('');
}
private highlight(color: string) {
this.r2.setStyle(this.el.nativeElement, "background-color", color);
}
}
html 当作属性使用
<p appHighlight>Highlight me!</p>
d) 将值传递给属性
app.component.html
若要绑定到 AppComponent.color
并回退为默认颜色“紫罗兰(violet)”,请添加以下 HTML。在这里,defaultColor
绑定没有使用方括号 []
,因为它是静态的
<h2>Pick a highlight color</h2>
<div>
<input type="radio" name="colors" (click)="color='lightgreen'">Green
<input type="radio" name="colors" (click)="color='yellow'">Yellow
<input type="radio" name="colors" (click)="color='cyan'">Cyan
</div>
<p [appHighlight]="color">Highlight me!</p>
<p [appHighlight]="color" defaultColor="violet">
Highlight me too!
</p>
修改 AppComponent.color
export class AppComponent {
color = '';
}
修改指令的 onMouseEnter
,使其首先尝试使用 appHighlight
进行突出显示,然后尝试 defaultColor
,如果两个属性都 undefined
,则变回 red
。
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.appHighlight || this.defaultColor || 'red');
}
如果没有默认颜色(defaultColor)绑定,则默认为红色。当用户选择一种颜色时,所选的颜色将成为突出显示的颜色。
<p >This should evaluate: {{ 1 + 1 }}</p>
<p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p>
自定义结构指令
结构型指令简写形式
应用结构指令时,它们通常以星号 *
为前缀,例如 *ngIf
。本约定是 Angular 解释并转换为更长形式的速记。Angular 会将结构指令前面的星号转换为围绕宿主元素及其后代的 <ng-template>
。
每个元素一个结构指令
重复一个 HTML 块是一个很常见的用例,但前提是在特定条件为真时。一种直观的方法是将 *ngFor
和 *ngIf
放在同一个元素上。但是,由于 *NgFor
和 *ngIf
都是结构指令,因此编译器会将其视为错误。你只能将一个 结构 指令应用于一个元素。
原因是简单。结构指令可以用宿主元素及其后代做复杂的事情。
这个用例有一个简单的解决方案:将 *ngIf
放在包装 *ngFor
元素的容器元素上。一个或两个元素可以是 <ng-container>
,以便不会生成额外的 DOM 元素。****
ng-container
一种特殊元素,可以在不向 DOM 添加新元素的情况下保存结构指令。
理解:如果需要不用 div,就是用文本,就使用 ng-container。
例子:这里 condition 为 true,就显示文本1,而没有在其他的结构下面,当然一般可以用 span。
<ng-container *ngIf="condition; else templateA"> 1 </ng-container> <ng-template #templateA> 2 </ng-template>
创建结构型指令
UnlessDirective
与 *NgIf
相反,并且 condition
值可以设置为 true
或 false
。*NgIf
为 true
时显示模板内容;而 UnlessDirective
在这个条件为 false
时显示内容。
ng generate directive unless
每当条件的值更改时,Angular 都会设置 appUnless
属性。
- 如果条件是假值,并且 Angular 以前尚未创建视图,则此 setter 会导致视图容器从模板创建出嵌入式视图。
- 如果条件为真值,并且当前正显示着视图,则此 setter 会清除容器,这会导致销毁该视图。
// 4.2 自定义结构指令
import { Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
@Directive({
selector: '[appUnless]'
})
export class UnlessDirective {
private hasView = false;
constructor( private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) {
}
@Input() set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
directive-test.component.html
<div *appUnless="condition" class="unless a">
(A) 显示这一段是因为condition 为 false。
</div>
<div *appUnless="!condition" class="unless b">
(B) 虽然condition 是 true, 显示这一段是因为appUnless被设置为false。
</div>
<p>
The condition =
<span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }">{{condition}}</span>.
<button
type="button"
(click)="condition = !condition"
[ngClass] = "{ 'a': condition, 'b': !condition }" >
Toggle condition to {{condition ? 'false' : 'true'}}
</button>
</p>
error: NG0303: Can't bind to 'appUnless' since it isn't a known property of 'XXX'
如果'XXX'是一个Angular组件,并且它的输入是'appUnless',那么请确认它是声明该组件的@NgModule的一部分。
这里记录一下,遇到个问题,UnlessDirective 才发现忘记导出了。
指令的样式绑定和事件绑定
指令没有模板,要寄宿到一个宿主上(HOST)
@HostBinding
: 绑定宿主的属性或者样式
@HostListener
: 监听的 DOM 事件
@HostBinding
: 绑定 Dom 的属性或者样式
静态的绑定
如果需要传入值,和 @Input()
一起使用。
export class HighlightDirective {
// @Input() appHighlight = '';
@Input() defaultColor = '';
@HostBinding('style.background-color') @Input() appHighlight = '';
constructor() {
}
}
路由
服务
服务用于放置和特定组件无关并希望跨组件共享的数据或逻辑。
服务出现的目的在于解耦组件类中的代码,是组件类中的代码干净整洁。
服务是由 Injectable 装饰器装饰的类。
依赖注入
提供依赖项
(1) 组件和Module级别依赖注入
第一步是添加 @Injectable
装饰器以表明此类可以被注入。
@Injectable()
class HeroService {
在组件级别,使用 @Component
装饰器的 providers
字段
@Component({
selector: 'hero-list',
template: '...',
providers: [HeroService]
})
class HeroListComponent {}
在 NgModule 级别,要使用 @NgModule
装饰器的 providers
字段。在这种情况下,HeroService
可用于此 NgModule 或与本模块位于同一个 ModuleInjector 的其它模块中声明的所有组件、指令和管道。当你向特定的 NgModule 注册提供者时,同一个服务实例可用于该 NgModule 中的所有组件、指令和管道。要理解所有边缘情况,参见多级注入器。
@NgModule({
declarations: [HeroListComponent]
providers: [HeroService]
})
class HeroListModule {}
新语法:
服务里面些注入到哪里
@Injectable({
providedIn: HeroListModule
})
class HeroService{}
(2) 全局依赖注入
在应用程序根级别,允许将其注入应用程序中的其他类。这可以通过将 providedIn: 'root'
字段添加到 @Injectable
装饰器来实现
@Injectable({
providedIn: 'root'
})
class HeroService {}
当你在根级别提供服务时,Angular 会创建一个 HeroService
的共享实例,并将其注入到任何需要它的类中。在 @Injectable
元数据中注册提供者还允许 Angular 通过从已编译的应用程序中删除没用到的服务来优化应用程序,这个过程称为摇树优化(tree-shaking)。
注入依赖项
注入依赖项的最常见方法是在类的构造函数中声明它。当 Angular 创建组件、指令或管道类的新实例时,它会通过查看构造函数的参数类型来确定该类需要哪些服务或其他依赖项。例如,如果 HeroListComponent
要用 HeroService
,则构造函数可以如下所示:
@Component({ … })
class HeroListComponent {
constructor(private service: HeroService) {}
}
参考
多级注入器
依赖注入实战
表单
模块与组件
Rxjs
HTTP Client
参考
Ngrx
Proxy 代理(跨域问题)
-
在项目的
src/
目录下创建一个proxy.conf.json
文件。 -
往这个新的代理配置文件中添加如下内容:
{
"/api": {
"target": "http://localhost:3000",
"secure": false
}
}
- 在 CLI 配置文件
angular.json
中为serve
目标添加proxyConfig
选项:
参考
跨域解决方案
https://angular.cn/guide/build#proxying-to-a-backend-server
插件
Chrome扩展插件商店 - Angular DevTools
参考
Configure environment-specific defaults
Component
装饰器 · TypeScript中文网 · TypeScript——JavaScript的超集
概览 Overview
https://angular.cn/guide/component-overview
v17: https://v17.angular.cn/guide/component-overview
生命周期 Lifecycle
https://angular.cn/guide/lifecycle-hooks
v17: https://v17.angular.cn/guide/component-overview
组件之间的交互 Component interaction
https://angular.cn/guide/component-interaction
v17: https://v17.angular.cn/guide/component-interaction
在父子指令及组件之间共享数据 Sharing data between child and parent directives and components
https://angular.cn/guide/inputs-outputs
v17: https://v17.angular.cn/guide/inputs-outputs
Routing and navigation
基本路由
https://angular.cn/guide/routing/common-router-tasks
v17: https://v17.angular.cn/guide/router#defining-a-basic-route
嵌套路由
https://angular.cn/guide/routing/common-router-tasks#nesting-routes
v17: https://v17.angular.cn/guide/router#nesting-routes
防止未经授权的访问(路由守卫)
https://angular.cn/guide/routing/common-router-tasks#preventing-unauthorized-access
v17: https://v17.angular.cn/guide/router#preventing-unauthorized-access
惰性加载
https://v17.angular.cn/guide/router#lazy-loading
v17: https://v17.angular.cn/guide/lazy-loading-ngmodules
HTTP Client
RxJS
https://rxjs.tech/guide/overview
操作符
https://rxjs.tech/guide/operators
Style guide
https://angular.cn/guide/styleguide
1.1 Directives folder
This folder contain all custom angular directive
https://angular.cn/guide/built-in-directives
1.2 Interceptors folder
This folder contain all custom angular interceptor
- loading interceptor : add loading spiner when http to be executing
- error interceptor : hanle error when http to be executing
- auth interceptor : add token and antifogrey key when http to be executing
https://angular.cn/guide/http#intercepting-requests-and-responses
1.3 Interfaces folder
This folder contain all custom angular interface
1.4 Models folder
This folder contain all custom angular model
1.5 Pipe folder
This folder contain all custom angular pipe
- translate pipe : mapping language file with key
- filter pagination : filter list for elipsis pagination
- filter checkbox : filter list for multi-level checkbox list
https://angular.cn/guide/pipes-overview
1.6 Services folder
This folder contain all service file which handle call api through http
1.7 Types folder
This folder contain all custom user define type
1.8 Utilities folder
This folder contain all service file which contain common function
2. Pages folder
This folder contain all page, dialog
3. Shared folder
This folder contain all shared ui, custom control ...
4. Assets folder
This folder contain all media file (image, video, icon..) and language file
5. Environment folder
This folder contain all setting for each environment
https://angular.cn/guide/build#configuring-application-environments