开发环境搭建
1.node 安装
通过 node -v 以及 npm -v 来检查是否安装成功
2.脚手架安装
npm install -g @angular/cli // 装的是最新的版本
npm install -g @angular/cli@xx.xx.x // 也可以安装指定版本
3.创建项目并运行
ng new project_name // project_name用户可以自定义项目名
cd project_name
ng serve
浏览器输入http://localhost:4200 便可看到相关页面
组件
1.概念
app 模块是根模块,管理整个单页面应用中涉及到的其他模块和组件
2.hello world 案例
2.1 手动创建组件
- 创建组件:在 app 文件夹下,新建 hello-world.component.ts
- 组件声明:在 App 文件夹下,在 app.module.ts 中声明组件
- 组件引用:将声明好的组件,在 app.component.html 中引用
2.2 命令创建组件
- 运行
ng generate component <component-name>命令,其中<component-name>是新组件的名字
3.生命周期
3.1 指令与组件共有的钩子
| 钩子 | 用途 |
|---|---|
ngOnChanges | 数据绑定输入属性的值发生变化时调用 |
ngOnInit | 在第一次 ngOnChanges()之后调用,初始化指令/组件 |
ngDoCheck | 自定义的方法,用于检测和处理值的改变 |
ngOnDestroy | 每次销毁指令/组件之前调用 |
3.2 组件特有的钩子
| 钩子 | 用途 |
|---|---|
ngAfterContentInit | 在组件内容初始化之后调用 |
ngAfterContentChecked | 组件每次检查内容时调用 |
ngAfterViewInit | 组件相应的视图初始化之后调用 |
ngAfterViewChecked | 组件每次检查视图时调用 |
3.3 ngOnChanges调用案例
// app/on-changes.component.ts
import { Component, Input, OnChanges, SimpleChanges } from "@angular/core";
@Component({
selector: "on-changes",
template: `<div class="info">
<p>{{ power }}</p>
</div>`,
styles: [""],
})
export class OnChangesComponent implements OnChanges {
@Input() power = "";
ngOnChanges(changes: SimpleChanges): void {
console.log("changes happen", changes);
}
}
// app/test-demo.component.ts
import { Component, ViewChild } from '@angular/core';
import { OnChangesComponent } from './on-changes.component';
@Component({
selector: 'app-test-demo',
template: `
<div>
<p>test demo component</p>
<label for="power-input">Power:</label>
<input type="text" id="power-input" [(ngModel)]="power" />
<on-changes [power]="power" />
</div>
`,
styles: [''],
})
export class TestDemoComponent {
power = '';
@ViewChild(OnChangesComponent) childView!: OnChangesComponent;
}
4视图封装
4.1 三种模式
| 模式 | 作用 |
|---|---|
ShadowDom | 组件样式不进不出,全局样式进不来,组件样式出不去 |
Emulated | 组件样式只进不出,全局样式能进来,组件样式出不去 |
None | 组件样式能进能出,直接把 css 添加到全局样式中 |
4.2 案例
// 父组件
import { Component } from '@angular/core';
@Component({
selector: 'app-test-demo',
template: `<h1>global style</h1><div class="desc">love and peace</div><div class="self">self style</div><app-test-son />`,
styles: [''],
})
export class TestDemoComponent {}
// 子组件
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-test-son',
template: `<h1>son global style</h1>
<div class="desc">page son</div>
<div class="self">son self style</div>`,
styles: ['.desc {color:green;}', '.self {color: blue}'],
encapsulation: ViewEncapsulation.ShadowDom,
})
export class TestSonComponent {}
4.3 引入样式的几种方式
4.3.1 组件元数据中直接设置样式
import { Component } from '@angular/core';
@Component({
selector: 'app-demo-test',
template: '<h1>hello world</h1>',
// 此时的样式只在当前组件中生效。既不会作用于模板中嵌入的任何组件,也不会作用于投影进来的组件
styles: ['h1 {color:purple}'],
})
export class DemoTestComponent {}
4.3.2 组件元数据中引入外部样式文件
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {}
4.3.3 模板中内联的样式
(1)在模板中引入style标签
import { Component } from '@angular/core';
@Component({
selector: 'app-demo-test',
template:
'<style>h1{color:purple} .desc{color:green;font-size:18px}</style><h1>test page</h1><p class="desc">detail introduction</p>',
styles: [''],
})
export class DemoTestComponent {}
(2)在标签内使用style属性
import { Component } from '@angular/core';
@Component({
selector: 'app-demo-test',
template:
'<div style="color:red;letter-spacing:5px;">A man who has not climbed the Great Wall is not a true man.</div>',
styles: [''],
})
export class DemoTestComponent {}
(3)通过link标签引入
import { Component } from '@angular/core';
@Component({
selector: 'app-demo-test',
template:
'<link rel="stylesheet" href="../assets/demo-test.component.css"><p>back to back and love and peace</p>',
styles: [''],
})
export class DemoTestComponent {}
4.3.4 通过CSS的import引入
@import "../common/css/demotest";
4.4 特殊的选择器
(1):host
:host选择是是把宿主元素作为目标的唯一方式。除此之外,你将没办法指定它,因为宿主不是组件自身模板的一部分,而是父组件模板的一部分。
//选择组件宿主元素中的元素
:host {
display: block;
border: 1px solid black;
}
//把宿主作为目标,同时带有active的class类的时候才会生效
:host(.active){
border-width: 3px;
}
//可以在:host后面添加选择器以选择子元素。例如::host h1定位组件视图内的h1标签
:host h1{
color:red;
}
(2):host-content
- 它在当前组件宿主元素的祖先节点中查找 CSS 类,直到文档的根节点为止。它只能与其它选择器组合使用。
//选择组件宿主元素中的元素
:host-context{
color:red;
}
//把宿主作为目标,同时带有active的class类的时候才会生效
:host-context(.active){
color:red;
}
//可以在:host-context后面添加选择器以选择子元素。例如::host-context h1定位组件视图内的h1标签
:host-context p{
color: hotpink;
}
//可用于某个样式内部条件判断
h1{
color: hotpink;
:host-context(.active) &{
color: yellow;
}
}
(3)::ng-deep
- 把伪类
::ng-deep应用到任何一条 CSS 规则上就会完全禁止对那条规则的视图封装。任何带有::ng-deep的样式都会变成全局样式。为了把指定的样式限定在当前组件及其下级组件中,请确保在::ng-deep之前带上:host选择器。如果::ng-deep组合器在:host伪类之外使用,该样式就会污染其它组件。
//从宿主元素到当前元素再到 DOM 中的所有子h3元素
:host ::ng-deep h3 {
font-style: italic;
}
//搜索某类型下面的特定类型:
.card-container ::ng-deep .ant-tabs-content {
color: red;
}
//::ng-deep如果放在顶头,这种强烈不推荐,会污染全局样式。
5.组件交互
5.1 父传子 @Input
// 父组件
import { Component } from '@angular/core';
@Component({
selector: 'app-test-demo',
template: `<div>parent component</div><app-test-son [sonMsg]="sonMsg" />`,
styles: [''],
})
export class TestDemoComponent {
public sonMsg = 'son message';
}
// 子组件
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-test-son',
template: `<div>son component, 这是传递的内容:{{ sonMsg }}</div>`,
styles: [''],
})
export class TestSonComponent {
private newMsg = '';
// @Input() sonMsg = '';
@Input()
get sonMsg(): string {
return this.newMsg;
}
set sonMsg(sonMsg: string) {
this.newMsg = sonMsg + ' hello world';
}
}
5.2 子传父
(1)@Output
// 父组件
import { Component } from '@angular/core';
@Component({
selector: 'app-test-demo',
template: `<div>parent component</div><app-test-son [count]="count" (newCountEvent)="addCount($event)" />`,
styles: [''],
})
export class TestDemoComponent {
public count = 1;
addCount(newItem: number) {
this.count += newItem;
}
}
// 子组件
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-test-son',
template: `<div>son component, 这是传递的内容:{{ count }}</div>
<button type="button" (click)="addCount(2)">count++</button>`,
styles: [''],
})
export class TestSonComponent {
@Input() count = 0;
@Output() newCountEvent = new EventEmitter<number>();
addCount(value: number) {
this.newCountEvent.emit(value);
}
}
(2)本地变量
// 父组件
import { Component } from '@angular/core';
@Component({
selector: 'app-test-demo',
template: `<div>parent component</div><app-test-son #child /><button type="button" (click)="child.addCount()">count++</button>`,
styles: [''],
})
export class TestDemoComponent {}
// 子组件
import { Component } from '@angular/core';
@Component({
selector: 'app-test-son',
template: `<div>son component, count 值为:{{ count }}</div>`,
styles: [''],
})
export class TestSonComponent {
count: number = 0;
addCount() {
this.count++;
}
}
(3)@ViewChild
// 父组件
import { Component, ViewChild } from '@angular/core';
import { TestSonComponent } from './test-son.component';
@Component({
selector: 'app-test-demo',
template: `<div>parent component</div><app-test-son #child /><button type="button" (click)="add(2)">count++</button>`,
styles: [''],
})
export class TestDemoComponent {
// 第一种:传入组件引用名child
// @ViewChild('child') private child: any;
// 第二种:传入组件实例ChildComponent
@ViewChild(TestSonComponent) private child!: TestSonComponent;
add(num: number) {
this.child.addCount(num);
}
}
// 子组件
import { Component } from '@angular/core';
@Component({
selector: 'app-test-son',
template: `<div>son component, count 值为:{{ count }}</div>`,
styles: [''],
})
export class TestSonComponent {
count: number = 0;
addCount(num: number) {
this.count = this.count + num;
}
}
5.3 不相关组件
5.3.1 service服务方式
// a组件
import { Component } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-a',
template:
'a组件传递数据:<input type="text" [(ngModel)]="inputValue" /><button type="button" (click)="send()">传递数据</button>',
styles: [''],
})
export class AComponent {
constructor(public dataService: DataService) {}
inputValue: string = '';
send(): void {
this.dataService.subject.next(this.inputValue);
}
}
// b组件
import { Component } from '@angular/core';
import { Subscription } from 'rxjs';
import { DataService } from './data.service';
@Component({
selector: 'app-b',
template: '<div>b组件接受数据:{{data}}</div>',
styles: [''],
})
export class BComponent {
data: any;
subscription: Subscription;
constructor(public DataService: DataService) {
this.subscription = this.DataService.subject.subscribe((data) => {
this.data = data;
});
}
ngOndestry(): void {
this.subscription.unsubscribe();
}
}
5.3.2 路由传参方式
(1)三种传参方式
- 新建路由模块
app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DetailComponent } from './detail.component';
const routes: Routes = [
{
path: 'detail',
component: DetailComponent,
data: { class: 'lesson 1', title: 'nice to meet you' },
},
{
path: 'detail/:id/:type',
component: DetailComponent,
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
app.module.ts中引入路由模块app.component.html中设置路由链接,添加路由占位符
<a [routerLink]="['/detail']" [queryParams]="{ name: 'falcon', age: 20 }"
>查询参数、路由配置中传递参数</a
><br />
<a [routerLink]="['/detail', 1, 'test']">动态路由传值</a>
<router-outlet></router-outlet>
detail.component.ts中获取路由参数并展示
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
@Component({
selector: 'app-detail',
template:
'<div>detail page</div><p>在查询参数中传递数据:{{name}} - {{age}}</p><p>在路由路径中传递数据:{{id}} - {{type}}</p><p>在路由配置中传递数据:{{class}} - {{title}}</p>',
styles: [''],
})
export class DetailComponent implements OnInit {
name!: string;
age!: number;
id!: number;
type!: string;
class!: string;
title!: string;
constructor(private routeInfo: ActivatedRoute) {}
ngOnInit(): void {
// 在查询参数中传递数据
this.routeInfo.queryParams.subscribe((queryParams) => {
this.name = queryParams['name'];
this.age = queryParams['age'];
});
// 在路由路径中传递数据
this.routeInfo.params.subscribe((params: Params) => {
this.id = params['id'];
this.type = params['type'];
});
// 在路由配置中传递数据
this.routeInfo.data.subscribe((params: Params) => {
this.class = params['class'];
this.title = params['title'];
});
}
}
(2)参数快照和参数订阅
this.id = this.routeInfo.snapshot.params['id']“参数快照”的取参方式。存在的问题:一个组件从自身路由再次回到自身路由时,参数不会更新(因为第一次的时候ngOnInit()方法已经被调用,会保留第一次的数据值)- 解决:使用上述subscribe参数订阅的方式。
- 确定一个组件不会从自身路由到自身,可以通过参数快照方式获取参数。不确定,则用参数订阅方式获取。
5.4 localstorage方式
// a组件-存储数据
import { Component } from '@angular/core';
@Component({
selector: 'app-a',
template:
'<input type="text" [(ngModel)]="inputValue" /><button type="button" (click)="send()">发送数据</button><br/>',
styles: [''],
})
export class AComponent {
inputValue: string = '';
send(): void {
window.localStorage.setItem('cValue', this.inputValue);
}
}
// b组件-接收数据
import { Component } from '@angular/core';
@Component({
selector: 'app-b',
template:
'<button type="button" (click)="get()">接收数据</button><span>接受到的数据值: {{data}}</span>',
styles: [''],
})
export class BComponent {
data: any;
get() {
this.data = window.localStorage.getItem('cValue');
}
}
5.5 服务端通信方式
借助后台完成数据的传输功能。(例如:在A组件中通过接口调用传入了数据,在B组件中通过接口调用获取传入的数据并展示)
6.内容投影
6.1 单槽内容投影
import { Component } from '@angular/core';
@Component({
selector: 'app-demo-test',
template: '<p>单插槽内容投影</p><ng-content></ng-content>',
styles: [''],
})
export class DemoTestComponent {}
<app-demo-test>
<div>这是单槽内容投影内容</div>
</app-demo-test>
6.2 多槽内容投影
import { Component } from '@angular/core';
@Component({
selector: 'app-demo-test',
template:
'<p>多槽内容投影</p><ng-content select=[question]></ng-content><ng-content select="label"></ng-content><ng-content select=".desc"></ng-content>',
styles: [''],
})
export class DemoTestComponent {}
<app-demo-test>
// 通过属性选择投影位置
<div question>多槽内容投影1</div>
// 通过标签选择投影位置
<label>多槽内容投影2</label>
// 通过class选择投影位置
<div class="desc">多槽内容投影3</div>
</app-demo-test>
6.3 条件内容投影
(1)ng-container
ng中常见错误之一的for和if不能写在同一标签上(在一个宿主元素上只能应用一个结构型指令),利用ng-container标签可以在实现功能的基础上减少层级的嵌套
<ul>
<ng-container *ngFor="let item of items">
<li>{{ item .name}}</li>
<li>{{ item .age}}</li>
<li>{{ item .sex}}</li>
</ng-container>
</ul>
(2)ng-template
- 使用
<ng-template>元素,你可以让组件根据你想要的任何条件显式渲染内容,并可以进行多次渲染。在显式渲染<ng-template>元素之前,Angular 不会初始化该元素的内容。
<ng-template [ngIf]="true">
<p> ngIf with a ng-template.</p>
</ng-template>
<ng-container *ngIf="true">
<p> ngIf with an ng-container.</p>
</ng-container>
ngIf with a ng-template.
ngIf with an ng-container.
(3)ng-container和ng-template一起使用
import { Component } from '@angular/core';
@Component({
selector: 'app-demo-test',
template: `<ng-container *ngIf="isShow; else empty"
>这是需要展示的内容部分</ng-container
><ng-template #empty>抱歉,内容不可展示</ng-template>`,
styles: [''],
})
export class DemoTestComponent {
isShow = false;
}
(4)ngTemplateOutlet
- 插入
ng-template创建的内嵌视图。ngTemplateOutlet是一个结构型指令,接收一个TemplateRef类型的值 *ngTemplateOutlet = "templateRefExp; content: contentExp"- templateRefExp:
ng-template元素的#ID - contextExp:可空参数,
content是一个对象,这个对象可以包含一个$implicit的key作为默认值, 使用时在模板中用let-key语句进行绑定,content的非默认字段需要使用let-templateKey=contentKey语句进行绑定
import { Component } from '@angular/core';
@Component({
selector: 'app-demo-test',
template: `
<ng-container *ngTemplateOutlet="greet"></ng-container>
<hr />
<ng-container *ngTemplateOutlet="eng; context: myContext"></ng-container>
<hr />
<ng-container *ngTemplateOutlet="svk; context: myContext"></ng-container>
<hr />
<ng-template #greet><span>hello world</span></ng-template>
<ng-template #eng let-name
><span>hello world, {{ name }}!</span></ng-template
>
<ng-template #svk let-person="localSk"
><span>hello world, {{ person }}!</span></ng-template
>
`,
styles: [''],
})
export class DemoTestComponent {
myContext = { $implicit: 'World', localSk: 'Svet' };
}
- 利用
ngTemplateOutlet进行内容投影
// 父组件
import { Component } from '@angular/core';
import { DemoSonComponent } from './demo-son.component';
@Component({
selector: 'app-demo-test',
template: `
<app-demo-son [render]="outTpl"></app-demo-son>
<ng-template #outTpl let-key let-val="value">
<p>
外部传入模板内容 -- 组件内部传递过来的变量:{{ key }} , value:{{ val }}
</p>
</ng-template>
`,
styles: [''],
})
export class DemoTestComponent {}
// 子组件
import { Component, Input, TemplateRef } from '@angular/core';
@Component({
selector: 'app-demo-son',
template: `
<ng-container
[ngTemplateOutlet]="render || render"
[ngTemplateOutletContext]="ctx"
></ng-container>
<ng-template #defaultTpl>组件默认内容</ng-template>
`,
styles: [''],
})
export class DemoSonComponent {
@Input() render!: TemplateRef<any>;
ctx = { $implicit: 'let-key的默认内容', value: 'let-关键字指定的内容' };
}
6.4 在更复杂的环境中投影内容
import { Component } from '@angular/core';
@Component({
selector: 'app-demo-test',
template: `<div class="card">
<div class="header">
<ng-content select="header"></ng-content>
</div>
<div class="content">
<ng-content select="content"></ng-content>
</div>
<div class="footer">
<ng-content select="footer"></ng-content>
</div>
<ng-content></ng-content>
</div>`,
styles: [''],
})
export class DemoTestComponent {}
<app-demo-test>
<!-- 由于 ng-container 的存在,header 部分并没有被渲染到我们想要渲染的插槽中,而是渲染到了没有提供任何 selector 的 ng-content 中 -->
<!-- <ng-container>
<header>
<h1>hello world</h1>
</header>
</ng-container> -->
<!-- 在某些情况下,我们需要使用 ng-container 把一些内容包裹起来传递到组件中。大多数情况是因为我们需要使用结构型指令像 ngIf 或者 ngSwitch 等,使用 ngProjectAs 帮助我们正确渲染 -->
<ng-container ngProjectAs="header">
<header>
<h1>hello world</h1>
</header>
</ng-container>
<content>this is content</content>
<footer>this is footer</footer>
</app-demo-test>