angular基础知识

201 阅读5分钟

image.png

开发环境搭建

1.node 安装

通过 node -v 以及 npm -v 来检查是否安装成功

image.png

2.脚手架安装

npm install -g @angular/cli  // 装的是最新的版本
npm install -g @angular/cli@xx.xx.x // 也可以安装指定版本

image.png

3.创建项目并运行

ng new project_name  // project_name用户可以自定义项目名
cd project_name
ng serve
浏览器输入http://localhost:4200 便可看到相关页面

image.png image.png image.png

组件

1.概念

app 模块是根模块,管理整个单页面应用中涉及到的其他模块和组件 image.png

2.hello world 案例

2.1 手动创建组件

  • 创建组件:在 app 文件夹下,新建 hello-world.component.ts image.png
  • 组件声明:在 App 文件夹下,在 app.module.ts 中声明组件 image.png
  • 组件引用:将声明好的组件,在 app.component.html 中引用 image.png

2.2 命令创建组件

  • 运行 ng generate component <component-name> 命令,其中 <component-name> 是新组件的名字

image.png image.png

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

image.png

4视图封装

4.1 三种模式

模式作用
ShadowDom组件样式不进不出,全局样式进不来,组件样式出不去
Emulated组件样式只进不出,全局样式能进来,组件样式出不去
None组件样式能进能出,直接把 css 添加到全局样式中

4.2 案例

image.png

// 父组件
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 {}

image.png image.png image.png

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

image.png image.png

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

image.png

(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++;
  }
}

image.png

(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;
  }
}

image.png

5.3 不相关组件

5.3.1 service服务方式

image.png

// 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();
  }
}

image.png

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 中引入路由模块 image.png
  • 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'];
    });
  }
}

image.png image.png

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

image.png

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>

image.png

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-containerng-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>

7.动态组件