**什么是Angular:**一个基于TypeScript构建的开发平台包括:
**优势:**让更新更容易,可以用最小的成本升级到最新的Angular版本。
组件是构成应用的砖块。 由三个部分组成:带有@Component()装饰器的TypeScript类、Html模板和样式文件。
@Component()装饰器的TypeScript类
@Component()装饰器会指定一下信息
每个组件都有一个HTML模板,用于声明该组件的渲染方式。使用内联或文件路径定义
Angular使用额外的语法扩展了HTML,文本插值(向组件中动态的插入动态值)
,支持属性绑定
、事件监听
、指令为模板添加额外功能
。
满足声明TypeScript类的依赖项,无需操心如何实例化。
指某个类执行其功能所需的服务或对象。依赖项注入(DI)是一种设计模式,此设计模式中类会从外部源请求依赖项而不是创建。
常用CLI命令:
ng build
:把Angular应用编译到一个输出目录中。
ng serve
:构建你的应用并启动开发服务器,当有文件变化就重新构建
ng generate
:基于原理图生成或修改某些文件
ng test
:在指定的项目上运行单元测试。
ng e2e
:构建一个Angular应用并启动开发服务器,然后运行端到端测试。
常用库:
Angular 路由器
:高级的客户侧导航功能与基于Angular组件的路由机制。支持惰性加载、嵌套路由、自定义路径匹配规则等。
Angular 表单
:同意的表单填报与验证体系。
Angular HttpClient
:支持高级的客户端-服务器通讯。
Angular 动画
:用于驱动基于应用状态的动画。
Angular PWA
:用于构建渐进式Web应用(PWA)的工具,包括Service Worker和Web应用清单。
Angular 原理图
:一些搭建脚手架、重构、升级的自动化工具。用于简化大规模应用的开发。
创建组件
ng g component componentName
生命周期伴随着变更检测,Angular会检查数据绑定属性何时发生变化,并按需更新视图和组件实例。直至销毁组件实例并移除它渲染的模板
组件与指令的生命周期唯一的区别就是指令没有AfterContentInit和AfterContentChecked,因为指令没有投影,组件或指令发生变更检测时都会调用三个变更检测方法:ngDoCheck、ngAfterContentChecked、ngAfterViewChecked
生命周期顺序:
constructor()
:初始化类,主要用于注入依赖,在所有生命周期钩子之前执行,用于依赖注入或执行简单的数据初始化操作。
ngOnChanges()
:触发顺序在ngOnInit
之前,当绑定有输入属性或输入属性有发生改变时触发,接收当前和上一个属性值的SimpleChanges对象,。
ngOnInit()
:顺序在ngOnChages()
之后。第一次显示数据绑定和设置指令/组件的输入属性后触发,即使没触发ngOnChages()
也会触发。只触发一次。
ngDoCheck()
:自定义的方法,用于检测和处理值的改变。
ngAfterContentInit()
:在组件内容初始化之后调用。
ngAfterContentChecked()
:组件每次检查内容时调用。
ngAfterViewInit()
:组件相应的视图初始化之后调用。
ngAfterViewCeched()
:组件每次检查视图时调用
ngOnDestory()
:指令销毁时调用,此处可以反订阅观察对象和分离事件处理器,防止内存泄漏。
1
响应视图的变更
当Angular再变更检测期间遍历视图树时,需要确保子组件中的某个变更不会尝试更改父组件中的属性,因为单向数据流
的工作原理如此,这样的更改无法正常渲染。
如果需要做一个上述操作,必须触发一个新的变更检测周期。
举个栗子:
// 子视图
@Component({
selector: 'app-child-view',
template: `
`
})
export class ChildViewComponent {
hero = 'Magneta';
}
//父组件template使用
template: `
child view begins
child view ends
`
// 父组件的类文件
export class AfterViewComponent implements AfterViewChecked, AfterViewInit {
private prevHero = '';
// Query for a VIEW child of type `ChildViewComponent`
//注入子视图
@ViewChild(ChildViewComponent) viewChild!: ChildViewComponent;
ngAfterViewInit() {
// viewChild is set after the view has been initialized
this.logIt('AfterViewInit');
this.doSomething();
}
ngAfterViewChecked() {
// viewChild is updated after the view has been checked
if (this.prevHero === this.viewChild.hero) {
this.logIt('AfterViewChecked (no change)');
} else {
this.prevHero = this.viewChild.hero;
this.logIt('AfterViewChecked');
this.doSomething();
}
}
private doSomething() {
const c = this.viewChild.hero.length > 10 ? "That's a long name" : '';
if (c !== this.comment) {
// Wait a tick because the component's view has already been checked
this.logger.tick_then(() => this.comment = c);
}
}
}
在视图合成完之后,就会触发AfterViewInit()
和AfterViewCheched()
钩子,如果修改了这段代码,这个钩子就会立即修改该组件的数据绑定属性comment,测试会报错。
this.logger.tick_then(() => this.comment = c);
会把日志的的更新工作推迟一个浏览器JS周期,也就会触发一个新的变更检测周期。
响应被投影内容的变更
内容投影时从组件外部导入HTML内容,并插入在组件模板中指定位置
投影内容会触发AfterContentInit()
和AfterContentChecked()
需要在父组件中导入
//子组件 标签时外来内容的占位符
template: `
projected content begins
projected content ends
`
//父组件
`
`
AfterContent和AfterView类似:不同在于子组件类型不同:
ViewChildren
,这些子组件的元素标签会出现在该组件的模板里面ContentChildren
,这些子组件被Angular投影进该组件中。doSomething()
会立即更新该组件的数据绑定属性comment
,无需延迟更新以确保正确渲染。将组件的样式封装在组建的宿主元素中,避免影响其他应用程序。
分三种模式:
ViewEncapsulation.ShadowDom
:组件的样式被包含在这个ShadowDom中,外部样式进不来,内部样式出不去
ViewEncapsulation.Emulated
:样式局限在组件视图,全局样式可以进来,内部样式出不去
ViewEncapsulation.None
:不使用视图封装,Angular会把CSS添加到全局样式中,全局样式可以进来,内部样式可以出去。
使用:
@Component({
selector: 'app-no-encapsulation',
template: `
None
`,
styles: ['h2, .none-message { color: red; }'],
encapsulation: ViewEncapsulation.None,
})
export class NoEncapsulationComponent { }
在子组件中添加@Input()
装饰器
// 子组件
export class HeroChildComponent {
@Input() hero!: Hero;
//为子组件的属性名masterName指定一个别名master
@Input('master') masterName = '';
}
//父组件
<app-hero-child
*ngFor="let hero of heroes"
[hero]="hero"
[master]="master">
</app-hero-child>
export class HeroParentComponent {
heroes = HEROES;
master = 'Master';
}
使用一个输入属性的setter,拦截父组件中值的变化
// 子组件
@Input()
get name(): string { return this._name; }
set name(name: string) {
this._name = (name && name.trim()) || '' ;
}
private _name = '';
父组件与上述一致
当需要监听多个、交互式输入属性时可以使用该方法
@Input() major = 0;
@Input() minor = 0;
changeLog: string[] = [];
ngOnChanges(changes: SimpleChanges) {
const log: string[] = [];
for (const propName in changes) {
const changedProp = changes[propName];
const to = JSON.stringify(changedProp.currentValue);
if (changedProp.isFirstChange()) {
log.push(`Initial value of ${propName} set to ${to}`);
} else {
const from = JSON.stringify(changedProp.previousValue);
log.push(`${propName} changed from ${from} to ${to}`);
}
}
this.changeLog.push(log.join(', '));
}
父组件不变
子组件暴露一个EventEmitter属性,当事件发生,子组件利用该属性emits(向上弹射)事件,父组件绑定到这个事件属性,并在事件发生时做出相应。
使用@Output()
装饰器
// 子组件
@Input() name = '';
@Output() voted = new EventEmitter<boolean>();
didVote = false;
vote(agreed: boolean) {
this.voted.emit(agreed);
this.didVote = true;
}
// 父组件
template:`
//事件属性
`
export class VoteTakerComponent {
agreed = 0;
disagreed = 0;
voters = ['Dr IQ', 'Celeritas', 'Bombasto'];
onVoted(agreed: boolean) {
if (agreed) {
this.agreed++;
} else {
this.disagreed++;
}
}
在父组件模板中新建一个本地变量待变子组件,利用这个变量来读取子组件的属性和调用子组件的方法。
// 父组件
template: `
Countdown to Liftoff (via local variable)
{{timer.seconds}}
//新建变量 timer
`
只可以在父组件的模板中使用该局部变量
当父组件的类需要依赖于子组件类,此时不能使用本地变量方法,此时可以将子组件作为ViewChild,注入到父组件中。
// 父组件
@ViewChild(CountdownTimerComponent)
private timerComponent!: CountdownTimerComponent;
seconds() { return 0; }
ngAfterViewInit() {
// Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ...
// but wait a tick first to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
}
start() { this.timerComponent.start(); }
stop() { this.timerComponent.stop(); }
将子组件的视图插入到父组件类需要做一点额外的操作
必须导入对装饰器Viewchild
以及生命周期钩子AfterViewInit
的引用,通过@ViewChild()
装饰器,将子组件CountdownTimerComponent
注入到私有属性timerComponent
里面。
父子组件共享一个服务,利用该服务在组件家族内部实现双向通讯。
// 服务
@Injectable()
export class MissionService {
// 通过RxJs来实现双向通讯
// Observable string sources
private missionAnnouncedSource = new Subject<string>();
private missionConfirmedSource = new Subject<string>();
// Observable string streams
missionAnnounced$ = this.missionAnnouncedSource.asObservable();
missionConfirmed$ = this.missionConfirmedSource.asObservable();
// Service message commands
announceMission(mission: string) {
this.missionAnnouncedSource.next(mission);
}
confirmMission(astronaut: string) {
this.missionConfirmedSource.next(astronaut);
}
}
// 父组件
//注入服务
constructor(private missionService: MissionService) {
//接收子组件的传值
missionService.missionConfirmed$.subscribe(
astronaut => {
this.history.push(`${astronaut} confirmed the mission`);
});
}
announce() {
const mission = this.missions[this.nextMission++];
//向子组件传值
this.missionService.announceMission(mission);
this.history.push(`Mission "${mission}" announced`);
if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
}
子组件通过构造函数注入该服务,因为是子组件所以获取到的也是父组件的这个服务实例。
subscript
变量在子组件ngOnDestory()
钩子函数内被销毁(调用unsubscribe())
,防止内存泄漏的保护措施,在父组件中不需要因为服务与父组件的生命周期相同。
特殊的选择器::host、:host-context
:host
每个组件都会关联一个与组件选择器相匹配的元素,该元素被称为宿主元素,模板会渲染到其中。:host
伪类选择器可用于创建针对宿主元素自身的样式,而不是针对宿主内部的哪些元素。
host-context
在当前组件宿主元素的祖先节点上查找CSS类,直到文档的根节点为止。只能与其他选择器组合使用。
父组件和一个或多个子组件共享数据,使用@Input()
和@Output()
。
@Input()
:允许父组件更新子组件中的数据
@Output()
:允许子组件向父组件发送数据。
创建一个组件,在其中投影一个组件。
在组件模板中添加,
,元素希望投影的内容出现在其中。
栗子:
// 子组件
template: `
Single-slot content projection
`
// 父组件
<app-zippy-basic>
<p>Is content projection cool?</p>
</app-zippy-basic>
元素是一个占位符,它不会创建真正的DOM元素。的自定义属性被忽略。
一个组件可以具有多个插槽,每一个插槽可以指定一个CSS选择器,选择器可以指定哪些内容放入该插槽。
必须指定投影出现的位置,通过使用和select
属性来完成此任务。
栗子:
// 子组件
template: `
Multi-slot content projection
Default:
Question:
`
// 父组件
<app-zippy-multislot>
<p question>
Is content projection cool?
</p>
<p>Let's learn about content projection!</p>
</app-zippy-multislot>
question
属性的将内容投影到带有select=[question]
属性的
元素。
如果组件包含不带select属性的
元素,则该实例将接受所有与其他
元素都不匹配的投影组件。
后补
当组件数量不多时可以使用ngif来判断component
栗子:
<ng-container *ngIf="radioValue === 'A'">
<app-component-a></app-component-a>
</ng-container>
<ng-container *ngIf="radioValue === 'B'">
<app-component-b></app-component-b>
</ng-container>
<ng-container *ngIf="radioValue === 'C'">
<app-component-c></app-component-c>
</ng-container>
当选项很多时,或者选项通过后端决定等复杂情况下,再用ngif实现会很复杂,代码不容易维护此时需要动态组件,动态的方式来生成或删除component
可以通过ViewContainerRef可以将一个或多个视图附着到组件中的容器,也只有它才可以加载组件。不过可以将新的组件作为其兄弟(节点)的DOM元素(容器),是兄弟不是父子。
步骤:
ng g d directiveName
@Directive({
selector: '[appDynamic]'
})
export class DynamicDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
appDynamic
(使用指令)template:
`
`
class类:
/*所有的组件列表*/
componentList = {
componentA: ComponentAComponent,
componentB: ComponentBComponent,
componentC: ComponentCComponent,
}
/*利用viewChild获取模板元素*/
@ViewChild(DynamicDirective) componentHost!:DynamicDirective;
radioValue: string = 'A';
radioValueChange() {
/** 获取到模板元素,因为用viewChild获取dom时定义的undefined 所以需要手动as定义一下类型*/
const viewContainerRef = this.componentHost.viewContainerRef;
/*将模板上的元素清空*/
viewContainerRef.clear();
/*创建组件*/
viewContainerRef.createComponent(this.getComponent(this.radioValue));
}
getComponent(radioValue: string): any {
if (radioValue === 'A') {
return this.componentList['componentA']
} else if (radioValue === 'B') {
return this.componentList['componentB']
} else {
return this.componentList['componentC']
}
}
Angular元素就是打包成自定义元素的Angular组件
Angular使用额外的特性扩展了模板中的HTML语法,Angular模板只是UI的一个片段,因此不包含、
、
等元素。并且不支持模板中使用元素
**插值:**将表达式嵌入到被标记的文本中。默认情况下使用双花括号{{}}
作为定界符。
模板语句是可在HTML中用于响应用户事件的方法或属性。
<button type="button" (click)="deleteHero()">Delete hero</button>
**语法:**与模板表达式一样,模板语句使用类似与JS的语言,支持赋值和带分号的串联表达式,不允许使用
类似与Property绑定,但是不能直接在方括号之间放置元素的Property,而是在Attribute
名前加上前缀attr
,后跟.
,然后,使用解析为字符串的表达式设置Attribute
值。
<p [attr.attribute-you-are-targeting]="expression"></p>
当表达式解析为null
或undefined
时,Angular会完全删除该Attribute。
ARIA Attribute
<button type="button" [attr.aria-label]="actionName">{{actionName}} with Aria</button>
绑定colSpan
colspan帮助以编程方式让表格保持动态
将[attr.colspan]设置为等于某个表达式。
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
使用类和样式绑定从元素的class属性中添加和删除css类名,以及动态设置样式。
绑定到单个CSS class
[class.sale]='onSale'
sale为类名
当表达时onSale为真值时,Angular会添加类,为假时会删除类,undefined除外
绑定到多个CSS class类
[class]='classExpression'
calssExpression可以为:
"my-class-1 my-class-2 my-class-3"
{foo: true, bar: false}
['foo', 'bar']
style
前缀后跟点加CSS样式的名称。// 中线格式
<nav [style.background-color]="expression"></nav>
// 驼峰格式
<nav [style.backgroundColor]="expression"></nav>
绑定到多个样式
<nav [style]='navStyle'>
navStyle = 'font-size: 1.2rem; color: cornflowerblue;';
click
:事件名
onSave()
:模板语句
属性绑定在单一方向上将值从组件的属性送到目标元素的属性。
绑定到属性:要绑定到元素的属性,放到方括号内,将此属性标识为目标属性。
colspan 和 colSpan
最容易混淆的地方是 colspan 这个 Attribute 和 colSpan 这个 Property。请注意,这两个名称只有一个字母的大小写不同。
Three-Four
双向绑定将属性和事件绑定结合在一起
Angular 的双向绑定语法是方括号和圆括号的组合 [()]。[] 进行属性绑定,() 进行事件绑定
**原理:**属性绑定展示数据,事件绑定修改数据
管道用于转换数据进行显示。
内置管道:
uppercase
: 转换成大写字符 {{ str | uppercase }}
lowercase
: 转换成小写 {{ str | lowercase }}
date
: 日期格式转换 {{ today | date: ‘yyyy-MM-dd HH:mm:ss’ }}
number
: 数字转换 {{ p | number:‘1.2-4’ }} 【格式:{最少整数位数}.{最少小数位数}-{最多小数位数},即保留2~4位小数】
slice
: 字符串截取 {{ ‘hello world!’ | slice:0:3 }} //输出结果:“hel”
管道的优先级比三目运算符高
**使用参数和管道链来格式化数据:**模板表达式 {{ amount | currency:'EUR' }}
会把 amount 转换成欧元。紧跟在管道名称(currency)后面的是冒号(:)和参数值(‘EUR’)。多个参数使用:分割开来
{{ amount | currency:'EUR':'Euros '}}
管道串联:{{ birthday | date | uppercase}}
自定义管道
创建指令: ng g p pipeName
模板变量事项在模板的另一部分使用这个部分的数据。
可以引用的东西:
<input #phone placeholder="phone number" />
<!-- lots of other elements -->
<!-- phone refers to the input element; pass its `value` to an event handler -->
<button type="button" (click)="callPhone(phone.value)">Call</button>
模板变量的赋值:
元素上声明变量,该变量就会引用一个TemplateRef
实例来代表此模板。ViewContainerRef
的createEmbeddedView()
方法。*ngIf
和 *ngFor
类的结构指令或
声明会创建一个新的嵌套模板范围,就像 JavaScript 的控制流语句(例如 if 和 for 创建新的词法范围。你不能从边界外访问这些结构指令之一中的模板变量。<input #ref1 type="text" [(ngModel)]="firstExample" />
<span *ngIf="true">Value: {{ ref1.value }}</span>
上的
*ngIf
会创建一个新的模板范围,其中包括其父范围中的 ref1
变量。
外部的父模板访问子范围中的变量不行。
模板输入变量
是一个具有在创建该模板实例时设置的值的变量
<ul>
<ng-template ngFor let-hero let-i="index" [ngForOf]="heroes">
<li>Hero number {{i}}: {{hero.name}}
</ng-template>
</ul>
实例化
时,可以传递国歌命名值,这些值可以绑定到不同的模板输入变量。输入变量的let-
声明的右侧可以指定应该用于该变量的值。
指令:为元素添加额外的行为的类。使用指令可以管理表单、列表、样式以及可以让用户看到的内容
不同类型的指令:
属性型指令会监听并修改其他HTML元素和组件的行为、Attribute(属性)和Property(特性)
通用指令:
NgClass:添加和删除一组CSS类
NgStyle:添加和删除一组HTML样式。
NgModel:将双向数据绑定添加到HTML表单元素
NgClass:同时添加或删除多个CSS类。
如果时添加或删除单个类,可以使用类绑定
。
类绑定:[class.sale]="onSale"
与方法一起使用:
通过一个对象来设置属性currentClasses
,该对象每个键(key)都是一个CSS类名。如果键为true,则ngClass添加该类。如果为false则删除该类。
currentClasses: Record<string, boolean> = {};
/* . . . */
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
saveable: this.canSave,
modified: !this.isUnchanged,
special: this.isSpecial
};
}
使用:
NgStyle向组件添加的都是内联样式。
栗子:
currentStyles: Record<string, string> = {};
/* . . . */
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
//使用
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
结构型指令的职责是HTML布局,塑造或重塑DOM的结构,通过添加、移除和操作它们附加的宿主元素来实现。
用于宿主元素添加或删除元素
为false则从DOM中移除一个元素及其后代。然后Angular销毁其组件,释放内存和资源。
isActive
为true添加组件到DOM中
Ngif与NgFor不可同时在同一个元素上。
用*ngFor的tackBy跟踪条目:
通过跟踪对条目列表的更改,可以减少应用程序对服务器的调用次数。使用*ngFor的trackBy属性,Angular只能更改和重新渲染已更改的条目,而不必重新加载整个条目列表。
// 该方法会返回NgFor应该跟踪的值
trackByItems(index: number, item: Item): number { return item.id; }
<div *ngFor="let item of items; trackBy: trackByItems">
({{item.id}}) {{item.name}}
</div>
是一个分组元素,不会干扰样式或布局,因为Angular不会将其放置在DOM中。当没有单个元素承载指令时可以使用
。
<ng-container *ngIf="hero">
and saw {{hero.name}}. I waved
</ng-container>
<select [(ngModel)]="hero">
<ng-container *ngFor="let h of heroes">
<ng-container *ngIf="showSad || h.emotion !== 'sad'">
<option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
</ng-container>
</ng-container>
</select>
NgSwitch
会根据切换条件显示几个可能的元素中的一个。
NgSwitch
是一组指令
NgSwitch
:一个属性型指令,会改变其伴生指令的行为NgSwitchCase
:当其绑定值等于开关值时将其元素添加到DOM中,而在其不等于开关值时将其绑定之移除。NgSwitchDefault
:当没有选中的NgSwitchCase
时,将其宿主元素添加到DOM中。<div [ngSwitch]="currentItem.feature">
<app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item>
<app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item>
<app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item>
<app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item>
<!-- . . . -->
<app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item>
</div>
属性型指令,可以改变DOM元素和Angular组件的外观或行为。
创建指令:ng generate directive highlight
@Directive() 装饰器的配置属性会指定指令的 CSS 属性选择器 [appHighlight]。
从@angular/core
导入ElementRef
。ElementRef的nativeElement属性会提供对宿主DOM元素的直接访问权限。
在指令的 constructor() 中添加 ElementRef 以注入对宿主 DOM 元素的引用,该元素就是 appHighlight 的作用目标。
向 HighlightDirective 类中添加逻辑,将背景设置为黄色。
import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) {
this.el.nativeElement.style.backgroundColor = 'yellow';
}
}
// 应用指令
<p appHighlight>Highlight me!</p>
要添加事件处理程序,每个事件处理程序都需要带有@HostListener()
装饰器。