angular11源码探索十一【ViewContainerRef动态组件】
ViewContainerRef
锚元素,指定该容器在包含视图中的位置。在渲染好的视图中会变成锚点元素的兄弟。可以在元素上放置注入了 ViewContainerRef
的 Directive
来访问元素的 ViewContainerRef
是不是听着有点蒙蔽,没事让我慢慢帮你理解本质
angular-master\angular-master\packages\core\test\acceptance\view_insertion_spec.ts
插入的参数
createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number):
EmbeddedViewRef<C> {
const viewRef = templateRef.createEmbeddedView(context || <any>{});
this.insert(viewRef, index);
return viewRef;
}
index未指定插入最后,我们可以知道,索引默认从0开始,填写第几个就是在哪个位置插入
<ng-template #simple>
<app-a></app-a>
</ng-template>
<div #container></div>
export class TwoComponent implements OnInit, AfterViewInit{
@ViewChild('container', {read: ViewContainerRef, static: true})
container: ViewContainerRef = null!;
@ViewChild('simple', {read: TemplateRef, static: true})
simple: TemplateRef<any> = null!;
constructor(private changeDetector: ChangeDetectorRef) {}
ngAfterViewInit() {
//直接插入
this.container.createEmbeddedView(this.simple)
// 指定特定的插入位置
this.container.createEmbeddedView(this.simple, {}, 2)
// 如果插入的是组件需要运行变更检测,如果插入的是dom,就不需要,主要看是否有值传入里面
this.changeDetector.detectChanges();
}
}
子代的插入问题
<app-a>
<div>xxx</div>
<app-b></app-b>
</app-a>
app-a
<ng-template #projection>
<ng-content></ng-content>
</ng-template>
<div #container>
<h1>dddd</h1>
</div>
export class AComponent implements OnInit, AfterViewInit, AfterContentInit, OnDestroy, AfterContentChecked {
@ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef = null!;
@ViewChild('projection',{read:TemplateRef}) projection: TemplateRef<any> = null!;
ngAfterViewInit() {
// 默认插入的位置
this.container.createEmbeddedView(this.projection);
// 修改位置,我们发现dom在下面了,组件在上面,因为ng-content只能有一个,所以重新插入会替换掉之前的
this.container.createEmbeddedView(this.projection,{},0);
}
}
插入的时候特殊的问题点,报错的解决方案
<ng-template #subContainer>
<div class="dynamic" *ngIf="true">test</div>
</ng-template>
<div #container></div>
export class TwoComponent implements OnInit, AfterViewInit, AfterViewChecked, AfterContentInit, AfterContentChecked {
constructor(private changeDetector: ChangeDetectorRef) {
}
@ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef = null!;
@ViewChild('subContainer', {read: TemplateRef}) subContainer: TemplateRef<any> = null!;
ngAfterViewInit() {
this.view1 = this.container.createEmbeddedView(this.subContainer);
this.view1 = this.container.createEmbeddedView(this.subContainer, null, 0);
//如果不写下面的可以会报错,在这里运行变更检测来避免 因为值被传递给ngIf
this.changeDetector.detectChanges();
}
}
指令的插入问题
<ng-template #insert>
<div>insertA</div>
</ng-template>
<ng-template #before>
<div>insertB</div>
</ng-template>
<div>
// 介绍下为什么要 #vi="vi"
// #vi 是描点定义的变量 'vi' 是别名的使用,就类似 #a="vi"
// vi的别名复制给 #a
// 页面页面上面的其他元素通过 vi.方法 使用指令里面的方法
// 第二点,在ts中我们使用@ViewChild 也需要#vi 指定的变量拿到其中的位置
<ng-template #vi="vi" viewInserter>
</ng-template>
</div>
<button (click)="insertA()">Click</button>
----------
@ViewChild('before', {static: true}) beforeTpl!: TemplateRef<{}>;
@ViewChild('insert', {static: true}) insertTpl!: TemplateRef<{}>;
@ViewChild('vi', {static: true}) viewInsertingDir!: ViewInsertingDir;
insertA() {
const beforeView = this.beforeTpl.createEmbeddedView({});
// 变更检测“before视图”来创建所有子视图
beforeView.detectChanges();
this.viewInsertingDir.insert(beforeView, this.insertTpl);
}
-----------
指令
@Directive({
selector: '[viewInserter]',
exportAs:'vi'
})
export class ViewInserterDirective {
constructor(private _vcRef:ViewContainerRef) { }
insert(beforeView: ViewRef, insertTpl: TemplateRef<{}>) {
this._vcRef.insert(beforeView, 0);
this._vcRef.createEmbeddedView(insertTpl, {}, 0);
}
}
在动态组件视图前插入
指令
@Directive({
selector: '[viewInserter]',
exportAs:'vi'
})
export class ViewInserterDirective {
constructor(private _vcRef:ViewContainerRef) { }
insert(beforeView: ViewRef, insertTpl: TemplateRef<{}>) {
this._vcRef.insert(beforeView, 0);
this._vcRef.createEmbeddedView(insertTpl, {}, 0);
}
}
<ng-template #insert>insert</ng-template>
<div><ng-template #vi="vi" viewInserter></ng-template></div>
<button (click)="insertA()">Click</button>
export class TwoComponent implements OnInit{
constructor(private _cfr: ComponentFactoryResolver, private _injector: Injector) {}
@ViewChild('insert', {static: true}) insertTpl!: TemplateRef<{}>;
@ViewChild('vi', {static: true}) viewInserterDirective!: ViewInserterDirective;
insertA() {
// 创建一个动态组件视图,作为“insert before”视图
const componentFactory = this._cfr.resolveComponentFactory(AComponent);
// 添加服务
const beforeView = componentFactory.create(this._injector).hostView;
// 变更检测“before视图”来创建所有子视图
beforeView.detectChanges();
this.viewInserterDirective.insert(beforeView, this.insertTpl);
}
}
突然疑惑一点为啥我加入一个新的变量的时候,就会报错找不到vi
@ViewChild('vi', {static: true})
其实我们把{static:true} 去掉或者改成false就可以啦
组件的增删改查
<ng-template #tpl1>
<div #foo>Foo 1</div>
</ng-template>
<div #foo>Betwean tpl _definitions_</div>
<ng-template #tpl2 let-idx="idx">
<div #foo>Foo 2</div>
</ng-template>
<ng-template viewInserter #vi="vi"></ng-template>
<hr>
<button (click)="vi.insert(tpl1)">Insert Foo1</button>
<button (click)="vi.insert(tpl2)">Insert Foo2</button>
<button (click)="vi.clear()">clear</button>
<button (click)="vi.remove()">删除第4个</button>
<!--另一种使用方式-->
<button (click)="clickMode()">click</button>
@ViewChild(ViewInserterDirective) vc: ViewInserterDirective;
// 这也是一种方式
clickMode(){
this.vc.insert(this.tpl2)
}
指令中
@Directive({
selector: '[viewInserter]',
exportAs:'vi'
})
export class ViewInserterDirective {
constructor(private _vcRef: ViewContainerRef,private changeDetector: ChangeDetectorRef) {}
insert(tpl: TemplateRef) {
this._vcRef.createEmbeddedView(tpl);
}
clear() {
this._vcRef.clear();
}
//默认出现报错记得检测更新
remove(index?: number) {
this._vcRef.remove(index);
}
//可以还不懂
move(viewRef: ViewRef, index: number) {
this._vcRef.move(viewRef, index);
}
}
试不试感觉模模糊糊的,那我们重新编写让大家看的更清晰些
案例
<ng-template #one>
<div>one</div>
</ng-template>
<ng-template #two>
<div>two</div>
</ng-template>
<ng-template #three>
<div>three</div>
</ng-template>
<h1>--------</h1>
<div viewInserter></div>
<button (click)="increase()">增加one</button>
<button (click)="increaseT(two)">增加two另一种模式</button>
<button (click)="increaseT(three)">增加two另一种模式</button>
<button (click)="remove()">删除1</button>
<button (click)="clickMove(0,3)">移动0,3</button>
<button (click)="clear()">删除全部</button>
export class TwoComponent implements OnInit, AfterViewInit {
constructor(private _cfr: ComponentFactoryResolver, private _injector: Injector) {
}
@ViewChild('one') one!: TemplateRef<any>;
@ViewChild('two') two!: TemplateRef<any>;
@ViewChild('three') three!: TemplateRef<any>;
@ViewChild(ViewInserterDirective) vd: ViewContainerRef;
// 移动
clickMove(start, end) {
// 查询
this.vd.move(start,end)
}
ngOnInit(): void {
}
//增
increase() {
this.vd.insert(this.one)
}
//增
increaseT(tpl: Comp) {
this.vd.insert(tpl)
}
//删除
remove(){
this.vd.remove(1)
}
//删除全部
clear(){
this.vd.clear()
}
}
指令
@Directive({
selector: '[viewInserter]',
})
export class ViewInserterDirective {
constructor(private _vcRef: ViewContainerRef, private changeDetector: ChangeDetectorRef) {
}
insert(tpl: TemplateRef<unknown>) {
this._vcRef.createEmbeddedView(tpl);
}
clear() {
this._vcRef.clear();
}
//默认出现报错记得检测更新
remove(index?: number) {
this._vcRef.remove(index);
}
move(start, end) {
this._vcRef.move(this._vcRef.get(start), end);
}
}
突然想想那默认探究下ViewContainerRef
具体有哪些api呢
explore(){
// 查找找不到范围-1
// this._vcRef.indexOf(this._vcRef.get(0));
// 拿到当前指令的dom
// console.log(this._vcRef.element.nativeElement);
// 插入
// insert(viewRef: ViewRef, index?: number): ViewRef
//从这个容器中分离视图而不销毁它。
// *与' insert() '一起使用来移动当前容器中的视图。
// * @param index要分离的视图基于0的索引。
// *如果不指定,容器中的最后一个视图将被分离。
this._vcRef.detach(3)
}
动态组件插入
@NgModule({
declarations: [ AComponent],
entryComponents:[AComponent],// 动态组件需要在模块中引入
<ng-container #container></ng-container>
<button (click)="createComp()">++</button>
export class TwoComponent implements OnInit, AfterViewInit {
constructor(private _cfr: ComponentFactoryResolver) { }
@ViewChild('container', {read: ViewContainerRef}) vcRef!: ViewContainerRef;
createComp() {
const factory = this._cfr.resolveComponentFactory(AComponent);
this.vcRef.createComponent(factory)
}
}
templateRef和ElementRef
搞混
ElementRef
DOM
templateRef
就是ng-template
上的
<ng-template #foo></ng-template>
@ViewChild('foo', {static: true}) foo!: TemplateRef<any>;
<span #foo></span>
@ViewChild('foo', {static: true}) foo!: ElementRef;
决定自己的高度的是你的态度,而不是你的才能
记得我们是终身初学者和学习者
总有一天我也能成为大佬

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
2018-12-27 一些被忽略掉的面试题