Angular DOM 操作
相关的APIs和类:
- 查询DOM节点
-
template variable ref
: 模版变量引用,相当于react中的ref
-
ViewChild
: 查询DOM,返回单个元素引用 -
ViewChildren
: 返回一个QueryList
对象,包含一系列元素
-
-
ElementRef
: 元素引用- 查询的方式获取,比如
@ViewChild('myInput') inputElm: ElementRef
- 依赖注入的方式,获取宿主元素,比如
constructor(private elem: ElementRef){}
- 查询的方式获取,比如
-
TemplateRef
: 模板引用- 查询的方式, 比如
- 查询的方式, 比如
-
ViewContainerRef
: 视图容器,包含创建angular视图的方法和操作视图的apis -
ViewRef
: 视图引用,angular最小的UI单元,创建的视图的返回类型就是ViewRef - angular中的2种类型的视图
- 插入式视图
Embedded Views
- 宿主视图
component instance views
,即组件实例视图
- 插入式视图
-
EmbeddedViewRef
: 插入式视图引用,上面创建插入式视图防护的类型 -
ComponentRef
: 组件视图引用,创建hostView时返回的类型
表示组件名
示例
创建视图并插入
import {
Component,
ViewChild,
TemplateRef,
ViewContainerRef,
ViewRef,
AfterViewInit
} from '@angular/core';
@Component({
selector: 'app-sample',
template: `
我是第一个span标签
我是第二个span标签
我是模版里面的div标签
`
})
export class SampleComponent implements AfterViewInit {
// 查询元素, {read: ViewContainerRef} 不能省略,因为angular无法推断出它是一个容器
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
// {read: TemplateRef} 可以省略
@ViewChild('tpl') tpl: TemplateRef;
constructor() {}
ngAfterViewInit() {
// 创建一个插入式视图, 一般插入式视图都对应的是模版视图
const tplView: ViewRef = this.tpl.createEmbeddedView(null);
// 插入到容器当中 使用视图容器操作视图的方法insert
this.vc.insert(tplView);
}
}
最后得到的结果是
我是第一个span标签
我是模版里面的div标签
我是第二个span标签
可以看出 ng-container
和 ng-template
最后编译后都变为了注释
使用 ngTemplateOutlet 指令
angular为创建插入式视图提供了 ngTemplateOutlet
指令,这个和 router-outlet
功能类似,为模版提供了一个进入的入口,上面的例子可以改写为
import {
Component,
} from '@angular/core';
@Component({
selector: 'app-sample',
template: `
我是第一个span标签
我是第二个span标签
我是模版里面的div标签
`
})
export class SampleComponent {
}
可以看出这种十分的方便,在饿了吗angular tooltip组件中就使用到了这个指令
@Component({
selector: 'el-tooltip',
template: `
# 此处使用到了ngTemplateOutlet指令
`,
animations: [fadeAnimation],
})
export class ElTooltip implements AfterContentInit {
@ContentChild('tip') tip: TemplateRef
}
使用时:
我是将要插入的模版内容
ngComponentOutlet
这个指令和上面的 ngTemplateOutlet
类似,但是它将创建一个 host view
(组件的实例),而不是插入式视图,使用方式
ViewContainerRef源码
import { Injector } from '../di/injector';
import { ComponentFactory, ComponentRef } from './component_factory';
import { ElementRef } from './element_ref';
import { NgModuleRef } from './ng_module_factory';
import { TemplateRef } from './template_ref';
import { EmbeddedViewRef, ViewRef } from './view_ref';
/**
* Represents a container where one or more Views can be attached.
* 表示能够被一个或者多个视图附着的容器
* The container can contain two kinds of Views. Host Views, created by instantiating a
* {@link Component} via {@link #createComponent}, and Embedded Views, created by instantiating an
* {@link TemplateRef Embedded Template} via {@link #createEmbeddedView}.
* 容器能够包含2种类型的视图: 宿主视图(组件实例) 和 插入式视图(使用模版创建的视图)
* The location of the View Container within the containing View is specified by the Anchor
* `element`. Each View Container can have only one Anchor Element and each Anchor Element can only
* have a single View Container.
*
* Root elements of Views attached to this container become siblings of the Anchor Element in
* the Rendered View.
* 插入的视图的根元素会成为视图容器的兄弟节点,而不是之间插入到容器中,这点和router-outlet插入组件的方式一致
*
* To access a `ViewContainerRef` of an Element, you can either place a {@link Directive} injected
* with `ViewContainerRef` on the Element, or you obtain it via a {@link ViewChild} query.
* 可以通过注入的方式范围viewContainerRef 和 通过 ViewChild 查询的方式获取viewContainerRef
* @stable
*/
export declare abstract class ViewContainerRef {
/**
* Anchor element that specifies the location of this container in the containing View.
*
*/
readonly abstract element: ElementRef;
readonly abstract injector: Injector; // 注入器, 用于动态创建组件中
readonly abstract parentInjector: Injector; // 父注入器, 如果组件自身没有提供注入器,使用父注入器
/**
* Destroys all Views in this container. 销毁容器内的所有视图
*/
abstract clear(): void;
/**
* Returns the {@link ViewRef} for the View located in this container at the specified index.
* 返回视图引用索引
*/
abstract get(index: number): ViewRef | null;
/**
* 获取视图容器的数量
* Returns the number of Views currently attached to this container.
*/
readonly abstract length: number;
/**
* 实例化一个插入式视图,可以插入到指定的索引位置,如果不指定索引,将放到最后面
* Instantiates an Embedded View based on the {@link TemplateRef `templateRef`} and inserts it
* into this container at the specified `index`.
*
* If `index` is not specified, the new View will be inserted as the last View in the container.
*
* Returns the {@link ViewRef} for the newly created View. 返回一个视图引用
*/
abstract createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): EmbeddedViewRef;
/**
* 实例化单个组件,插入到宿主视图中,可以插入到指定的索引位置,如果不指定索引,将放到最后面
* Instantiates a single {@link Component} and inserts its Host View into this container at the
* specified `index`.
*
* The component is instantiated using its {@link ComponentFactory} which can be
* obtained via {@link ComponentFactoryResolver#resolveComponentFactory}.
* 组件通过 ComponentFactory 实例化, 而组件工厂可以通过 ComponentFactoryResolver来创建
* If `index` is not specified, the new View will be inserted as the last View in the container.
*
* You can optionally specify the {@link Injector} that will be used as parent for the Component.
*
* Returns the {@link ComponentRef} of the Host View created for the newly instantiated Component.
*/
abstract createComponent(componentFactory: ComponentFactory, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef): ComponentRef;
/**
* 插入视图
* Inserts a View identified by a {@link ViewRef} into the container at the specified `index`.
*
* If `index` is not specified, the new View will be inserted as the last View in the container.
*
* Returns the inserted {@link ViewRef}.
*/
abstract insert(viewRef: ViewRef, index?: number): ViewRef;
/**
* 依据索引移动视图
* Moves a View identified by a {@link ViewRef} into the container at the specified `index`.
*
* Returns the inserted {@link ViewRef}.
*/
abstract move(viewRef: ViewRef, currentIndex: number): ViewRef;
/**
* 返回视图的索引位置
* Returns the index of the View, specified via {@link ViewRef}, within the current container or
* `-1` if this container doesn't contain the View.
*/
abstract indexOf(viewRef: ViewRef): number;
/**
* 移除视图
* Destroys a View attached to this container at the specified `index`.
*
* If `index` is not specified, the last View in the container will be removed.
*/
abstract remove(index?: number): void;
/**
* 将视图从当前容器中分离
* Use along with {@link #insert} to move a View within the current container.
*
* If the `index` param is omitted, the last {@link ViewRef} is detached.
*/
abstract detach(index?: number): ViewRef | null;
}
2种视图
模版视图
也称之为插入式视图
class SampleComponent implments AfterViewInit {
@ViewChild('tpl') tpl: Template;
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
ngAfterViewInit() {
let embeddedView: ViewRef = this.tpl.createEmbeddedView(null);
this.vc.insert(embeddedView);
}
}
宿主视图
和组件相关
class SampleComponent implments AfterViewInit {
@ViewChild('tpl') tpl: Template;
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
componentRef: ComponentRef;
constructor(private injector: Injector, provate cfr: ComponentFactoryResolver) {}
ngAfterViewInit() {
const factory = this.cfr.rosolveComponentFactory(ColorComponent); // 创建组件工厂
this.componentRef = this.vc.createComponent(factory); // 创建组件引用
// this.componentRef = factory.create(this.injector); // 创建注入器
// let view: ViewRef = componentRef.hostView; // 创建宿主视图
// this.vc.insert(view);
}
}
最后动态创建的组件需要添加到 entryComponent
中
示例:
- angular-hostView-example
文章参考:
- Exploring Angular DOM manipulation techniques using ViewContainerRef