文章目录
1. ViewContainerRef 视图容器
ViewContainerRef 可以将一个或多个视图附着到组件中的容器。通过它可以动态创建内嵌视图、动态创建组件,可以添加、移动、删除视图容器中的视图。
abstract class ViewContainerRef {
abstract element: ElementRef
abstract injector: Injector
abstract parentInjector: Injector
abstract length: number
abstract clear(): void
abstract get(index: number): ViewRef | null
// 创建内嵌视图(实例化一个内嵌视图,并把它插入到该容器中),参数1:模板引用TemplateRef,参数2:要挂在在内嵌视图上的上下文对象。参数3:从 0 开始的索引,表示新视图要插入到当前容器的哪个位置。 如果没有指定,就把新的视图追加到最后。最终返回值是个内嵌视图EmbeddedViewRef。
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C> ⭐️⭐️
// 在视图容器上动态创建组件
abstract createComponent<C>(componentType: Type<C>, options?: { index?: number; injector?: Injector; ngModuleRef?: NgModuleRef<unknown>; projectableNodes?: Node[][]; }): ComponentRef<C> ⭐️⭐️
// 往视图容器中插入视图
abstract insert(viewRef: ViewRef, index?: number): ViewRef ⭐️⭐️
// 移动视图容器中的某个视图
abstract move(viewRef: ViewRef, currentIndex: number): ViewRef ⭐️⭐️
// 根据给定的视图检索它在视图容器中的index
abstract indexOf(viewRef: ViewRef): number ⭐️⭐️
// 将要移除的视图在视图容器中的对应的index传给remove方法来进行视图删除
abstract remove(index?: number): void ⭐️⭐️
abstract detach(index?: number): ViewRef | null
}
2. EmbeddedViewRef 嵌入式视图
- 表示视图容器中的 Angular 视图。嵌入视图可以从在模板中定义它的宿主组件之外的组件中引用,也可以由 TemplateRef 进行独立定义。
- 通过在
视图容器ViewContainerRef
中插入,移动或删除嵌套视图EmbeddedViewRef
来更改元素的结构。
abstract class EmbeddedViewRef<C> extends ViewRef {
// 该视图的上下文,继承自锚点元素。
abstract context: C ⭐️⭐️
// 该内嵌视图的根节点
abstract rootNodes: any[]
// 继承自 core/ViewRef
abstract destroyed: boolean
abstract destroy(): void
abstract onDestroy(callback: Function): any
// 继承自 core/ChangeDetectorRef
abstract markForCheck(): void
abstract detach(): void
abstract detectChanges(): void
abstract checkNoChanges(): void
abstract reattach(): void
}
3. ViewRef 视图
表示一个 Angular 视图。其子类:EmbeddedViewRef。
(1)视图 view
-
视图是可显示元素的最小分组单位,它们会被同时创建和销毁。
Angular 在一个或多个指令 (directive) 的控制下渲染视图
。 -
组件 (component) 类及其关联的模板 (template)定义了一个视图。 具体实现上,视图由一个与该组件相关的
ViewRef 实例
表示。 直属于某个组件的视图叫做宿主视图
。 通常会把视图组织成一些视图树
(view hierarchies)。 -
视图中各个元素的属性可以动态修改以响应用户的操作,而这些元素的结构(数量或顺序)则不能。我们可以通过在它们的视图容器中插入、移动或移除
内嵌视图
来修改这些元素的结构。 -
当用户在应用中导航时(比如使用路由器),视图树可以动态加载或卸载。
(2)视图树
-
一棵相关视图的树,它们可以作为一个整体行动。其根视图就是组件的宿主视图。
-
宿主视图可以是内嵌视图树的根,它被收集到了宿主组件上的一个
视图容器(ViewContainerRef)
中。视图树是 Angular 变更检测的关键部件之一。 -
视图树和组件树并不是一一对应的。那些嵌入到指定视图树上下文中的视图也可能是其它组件的宿主视图。那些组件可能和宿主组件位于同一个 NgModule 中,也可能属于其它 NgModule。
4. TemplateRef 模板引用
它表示一个内嵌模板,它可用于实例化内嵌的视图。 要想根据模板实例化内嵌的视图,请使用 ViewContainerRef 的 createEmbeddedView() 方法。
abstract class TemplateRef<C> {
abstract elementRef: ElementRef
abstract createEmbeddedView(context: C): EmbeddedViewRef<C>
}
5. 实际应用 – ngTemplateOutlet指令的实现
import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
@Directive({selector: '[ngTemplateOutlet]'})
export class NgTemplateOutlet implements OnChanges {
private _viewRef: EmbeddedViewRef<any> | null = null;
// 附加到EmbeddedViewRef的上下文对象。 这应该是一个对象,对象的键将可用于本地模板 `let` 的绑定声明。在上下文对象中使用键 $implicit 会将其值设置为默认值。
@Input() public ngTemplateOutletContext: Object | null = null;
// 定义模板引用和可选的模板上下文对象的字符串。
@Input() public ngTemplateOutlet: TemplateRef<any> | null = null;
constructor(
// 注入视图容器
private _viewContainerRef: ViewContainerRef
) {}
/**
* OnChanges 是一个生命周期钩子,当指令的任何一个可绑定属性(ngTemplateOutlet或ngTemplateOutletContext)发生变化时调用。
* 定义一个 ngOnChanges() 方法来处理这些变更。
* @param changes
*/
ngOnChanges(changes: SimpleChanges) {
if (changes['ngTemplateOutlet']) {
// 1 如果传入了一个新的目标引用,则移除当前的视图。
const viewContainerRef = this._viewContainerRef;
if (this._viewRef) {
// 1.1 如果当前有视图,找到该视图在当前视图容器中的索引,并根据索引移除当前视图
viewContainerRef.remove(viewContainerRef.indexOf(this._viewRef));
}
// 1.2 在当前视图容器中添加新的内嵌视图,参数1是模板引用TemplateRef,参数2是要挂载在该内嵌视图的上下文对象。
this._viewRef = this.ngTemplateOutlet
? viewContainerRef.createEmbeddedView(this.ngTemplateOutlet, this.ngTemplateOutletContext)
: null;
} else if (
// 如果传入了一个新的上下文对象 且 当前有视图,则更新当前视图的上下文。
this._viewRef &&
changes['ngTemplateOutletContext'] &&
this.ngTemplateOutletContext
) {
this._viewRef.context = this.ngTemplateOutletContext;
}
}
}
End,看Angular的源码实现是一件很开心很幸福的事情😄