Angular 基础 2 - 组件相关
Posted GoldenaArcher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Angular 基础 2 - 组件相关相关的知识,希望对你有一定的参考价值。
Angular 基础 2 - 组件相关
涉及到:
- 跨组件交流(传属性与事件)
- 生命周期
- 跨组件交流的其他方法(local reference 与 content)
跨组件交流的方式会通过案例的方法进行实现,毕竟这样看起来比较有视觉上的效果,不太干。再基础一点的知识看这里:Angular 基础。
实现大致如下:
项目结构如下:
|- app
| |- cockpit
| |- server-element
其中 app 包含 servers 的数组,cockpit 显示添加 server 部分功能,而 server-element 负责渲染数组的部分。
跨组件传递自定义属性
即 custom property,这里的即 custom property 指的是 server,即通过 app 将 server 的属性传递到 server-element 进行渲染。
-
app 部分代码
import Component from '@angular/core'; import Server from './server'; export interface Server type: string; name: string; content: string; @Component( selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], ) export class AppComponent serverElements: Server[] = [ type: 'server', name: 'Test Server', content: 'a test' , ];
<div class="container"> <div class="row"> <div class="col-xs-12"> <app-server-element *ngFor="let serverElement of serverElements" [element]="serverElement" > </app-server-element> </div> </div> </div>
这里直接使用 property binding 将数组中的值传入到
app-server-element
中去。 -
server-element 部分代码为:
import Component, OnInit from '@angular/core'; @Component( selector: 'app-server-element', templateUrl: './server-element.component.html', styleUrls: ['./server-element.component.css'], ) export class ServerElementComponent implements OnInit element: Server; constructor() ngOnInit(): void
<div class="panel panel-default"> <div class="panel-heading"> element.name </div> <div class="panel-body"> <p> <strong *ngIf="element.type === 'server'" style="color: red" > element.content </strong > <em *ngIf="element.type === 'blueprint'"> element.content </em> </p> </div> </div>
在Angular 基础的同一个组件的案例中,这样的代码是可以跑通的,不过这里会报错:
因为 Angular 默认数据绑定的作用域在同一个组件中,父组件中只使用 2 way binding,自组件也无法获得相应的数据,这时候需要使用 Angular 提供的装饰器去进行实现:
export class ServerElementComponent implements OnInit
// @Input('alias')
@Input() element: Server;
这时候,自组件就可以获取父组件传来的值,并正常渲染:
装饰器中可以传入字符串,当父组件与自组件的变量名完全一致时,这个参数要不要都无所谓,但是如果父组件传到自组件的变量名为 somethingElse
,自组件想要将其重命名为 element
,就可以使用 @Input('somethingElse')
进行重命名。
跨组件传递自定义事件
servers 的数组存在于 app 中,因此作为自组件的 cockpit 必须要触发父组件中的函数去对原有的数据进行修改,其本身也负责从 DOM 节点中取值。
-
app 部分代码
主要 controller 中新增两个 onChangeHandler(也可以只使用一个,这个完全看个人偏好),模板中新增对 cockpit 的渲染
export class AppComponent // 其余部分保持一致 onServerAdded(serverData: serverName: string; serverContent: string ) this.serverElements.push( type: 'server', name: serverData.serverName, content: serverData.serverContent, ); onBlueprintAdded(blueprintData: blueprintName: string; blueprintContent: string; ) this.serverElements.push( type: 'blueprint', name: blueprintData.blueprintName, content: blueprintData.blueprintContent, ); // 其他的写法也可以使用: // HTML 和 组件中的代码也需要对应更新 /** * * onServerAdded(serverData: * type: 'server' | 'blueprint'; * name: string; * content: string; * ) * this.serverElements.push(serverData); * */
<div class="container"> <!-- 同样为新增部分,其余不变,同样将函数传到子组件中 --> <app-cockpit (serverCreated)="onServerAdded($event)" (blueprintCreated)="onBlueprintAdded($event)" ></app-cockpit> <hr /> </div>
-
cockpit 部分代码
同样因为作用域的关系,如果单纯的调用从 app 中传来的函数,就会报错,因此 cockpit 中也需要用装饰器对函数进行绑定。
不过因为事件需要传输数据,这里用的装饰器不是 Input,而是 Output。Output 的 alias 使用方法于 Input 相同,不过绑定事件的方式与绑定变量还是不太一样的,后面需要跟一个 EventEmitter,并且规定 EventEmitter 中传输的数据类型。
import Component, ElementRef, EventEmitter, OnInit, Output, from '@angular/core'; @Component( selector: 'app-cockpit', templateUrl: './cockpit.component.html', styleUrls: ['./cockpit.component.css'], ) export class CockpitComponent implements OnInit // @Output('aliasName') @Output() serverCreated = new EventEmitter< serverName: string; serverContent: string; >(); @Output() blueprintCreated = new EventEmitter< blueprintName: string; blueprintContent: string; >(); newServerName = ''; newServerContent = ''; constructor() ngOnInit(): void onAddServer() this.serverCreated.emit( serverName: this.newServerName, serverContent: this.newServerContent, ); onAddBlueprint() this.blueprintCreated.emit( blueprintName: this.newServerName, blueprintContent: this.newServerContent, );
这里数据进行 2 way binding
<div class="row"> <div class="col-xs-12"> <p>Add new Servers or blueprints!</p> <label>Server Name</label> <input type="text" class="form-control" [(ngModel)]="newServerName" /> <label>Server Content</label> <input type="text" class="form-control" [(ngModel)]="newServerContent" /> <br /> <button class="btn btn-primary" (click)="onAddServer()" style="margin-right: 15px" > Add Server </button> <button class="btn btn-primary" (click)="onAddBlueprint()"> Add Server Blueprint </button> </div> </div>
至此,主要的功能就实现了:
View Encapsulation
默认情况下,Angular 的 CSS 只会应用于当前组件,如在 app.css 中加入 p color: blue;
这一段代码后,效果如下:
可以看到 cockpit 中的 p 标签并没有受到 CSS 的影响,而 app 模板中的 CSS 则是呈现蓝色。这是因为 Angular 有一个叫做 View Encapsulation 的实现,换言之,每个模板引入的 CSS 文件,只会通过 HTML attribute 的绑定,对当前组件内的元素奇效:
关闭 View Encapsulation 的方法也很简单,在想要关闭 View Encapsulation 的子组件中更新如下代码:
@Component(
selector: 'app-server-element',
templateUrl: './server-element.component.html',
styleUrls: ['./server-element.component.css'],
encapsulation: ViewEncapsulation.None,
)
p
color: blue;
这样这块 css 的代码会被应用到所有关闭 View Encapsulation 的代码。
ViewEncapsulation 有三个选项:None,Emulated(即使用 attributes 绑定样式),
ShadowDown(Using shadow DOM)。
Shadow DOM 不在本篇讨论范围之内。
生命周期
Angular 现在有 8 个 hooks,不过官网上好像没有像 React 一样有一个比较直观的流程图,不过提供了一个案例,其调用结果如下:
其说明如下:
HOOK METHOD | PURPOSE |
---|---|
ngOnChanges() | 设置或重设 input properties 时被调用(可多次) |
ngOnInit() | 组件初始化时被调用(只有一次) |
ngDoCheck() | 每次监测到变动时就被调用(可多次) |
ngAfterContentInit() | 在 ngDoCheck() 第一次调用后被调用,将外部内容投影到当前组件中 (只有一次)这个被调用主要也是为了联系上下文(父组件),毕竟父组件可以通过 ng-content 向子组件投射额外的内容 |
ngAfterContentChecked() | 检查 content 被投影后的内容,在 ngAfterContentInit() 和 ngDoCheck() 调用后被调用 |
ngAfterViewInit() | ngAfterContentChecked() 第一次调用后被调用,用以检查当前 View |
ngAfterViewChecked() | ngAfterViewInit() 和 ngAfterContentChecked() 被调用后调用,功能就是用来检测 View 中所有的变化都渲染成功 |
ngOnDestroy() | 组件被毁灭时调用 |
其他
跨组件传递自定义属性 和 跨组件传递自定义事件 应该是可以满足大部分的需求了,所以这个以下的部分可以选看。
local reference
与其使用 2 way binding,local reference 可以直接获得当前 DOM 元素,修改方法如下:
-
模板部分
<!-- 其余维持不变,只修改获取 server name 和 提交部分 --> <!-- <input type="text" class="form-control" [(ngModel)]="newServerName" /> --> <input type="text" class="form-control" #serverNameInput /> <button class="btn btn-primary" (click)="onAddServer(serverNameInput)" style="margin-right: 15px" > Add Server </button> <button class="btn btn-primary" (click)="onAddBlueprint(serverNameInput)"> Add Server Blueprint </button>
在声明了
#serverNameInput
后,就可以在当前模板中使用这个变量了。 -
controller 部分
onAddServer(serverNameInput: HTMLInputElement) this.serverCreated.emit( serverName: serverNameInput.value, serverContent: this.newServerContent, ); onAddBlueprint(serverNameInput: HTMLInputElement) this.blueprintCreated.emit( blueprintName: serverNameInput.value, blueprintContent: this.newServerContent, );
local reference 的主要用途就是在模板中更加简单的访问当前元素,尽管也可以直接将值传入函数内,使得该元素在组件内被访问。
⚠️:Angular 不推荐直接通过元素修改 HTML 属性。
@ViewChild
@ViewChild()
是 Angular 提供的装饰器,如果说使用 local reference 让在同一模板内访问访问该元素变得更简单,那么使用装饰器则可以更简单的在组件内访该元素:
-
模板部分
<!-- 其余不变 --> <input type="text" class="form-control" #serverContentInput />
模板中需要新增对应的 local reference 让
@ViewChild
去获取对应的元素。 -
controller 部分
export class CockpitComponent implements OnInit // 第一个参数与 local reference名称一致 @ViewChild('serverContentInput', static: true ) // ElementRef 需要从 Angular 中导入 serverContentInput: ElementRef; onAddBlueprint(serverNameInput: HTMLInputElement) console.log(this.serverContentInput); this.blueprintCreated.emit( blueprintName: serverNameInput.value, blueprintContent: this.serverContentInput.nativeElement.value, );
⚠️:Angular 不推荐直接通过 element ref 修改 HTML 属性。
ng-content
假设有一个比较少见的 case,就是迭代需要在父组件中实现,即子组件基本为一个空的 template,Angular 也有办法通过 ng-content directive 实现:
-
app 模板
<div class="row"> <div class="col-xs-12"> <app-server-element *ngFor="let serverElement of serverElements" [element]="serverElement" > <p> <strong *ngIf="serverElement.type === 'server'" style="color: red" > serverElement.content </strong > <em *ngIf="serverElement.type === 'blueprint'" > serverElement.content </em > </p> </app-server-element> </div> </div>
这里将一部分的 server-element 中的模板内容放了进来
-
server-element 模板
Angular 可以通过 ng-content 获取标签内的元素进行替换渲染
<div class="panel panel-default"> <div class="panel-heading"> element.name </div> <div class="panel-body"> <ng-content></ng-content> </div> </div>
@ContentChild
另外,像 @ViewContent
的用法,使用 @ContentChild
可以在子组件中获取父组件中的元素,获取的数据类型也是 ElementRef
。
以上是关于Angular 基础 2 - 组件相关的主要内容,如果未能解决你的问题,请参考以下文章