模板引用变量通常用来引用模板中的某个Dom元素。它还可以引用angular组件或指令或WebComponent。模板引用变量可以让我们在页面上声明变量并使用,也是一个可以让ts控制html的桥梁。
使用井号#
来声明引用变量,也可以使用ref-
前缀来代替#
:
1 | <input type="text" #code/> |
我们同时写了个方法将模板引用变量输出看看是什么东西:
1 | consoleValue(value: any) { |
运行发现,这个模板引用变量就是一个Dom元素(这不是废话么!)。
根据模板引用变量对应的Dom元素,它可以有不同的行为和属性。我们这里引用的事一个input元素,所以我们可以通过code.value
来查看输入的内容。
模板引用变量的作用范围是整个模板,所以不要在同一个模板里面多次定义同一个变量名,否则它在运行期间的值是无法确定的。
如何在ts中使用模板引用变量?
我们需要用到ViewChild
。使用ViewChild
可以声明一个视图查询,变更检测器会在视图的DOM中查找能匹配上该选择器的第一个元素或指令。如果视图的DOM发生了变化,出现了匹配该选择器的子节点,该属性会被更新。支持的选择器包括:
- 任何带有
@Component
或@Directive
装饰器的类,比如可以使用@ViewChild(Pane)
来查询声明的指令export class Pane
- 字符串形式的模板引用变量,比如可以使用
@ViewChild('cmp')
来查询<my-component #cmp></my-component>
- 组件树种任何当前子组件所定义的提供商
- 任何通过字符串令牌定义的提供商
- TemplateRef,比如可以使用
@ViewChild(TemplateRef) template
来查询<ng-template></ng-template>
在这里我们是使用ViewChild
查询一个模板引用变量,所以:
1 | @ViewChild('code') codeEl: ElementRef; |
这里我们的模板引用变量是声明在一个DOM元素上,所以我们给它的类型是ElementRef
。ElementRef
是声明了一原生元素的包装器,允许直接访问DOM元素。
注意:当需要访问DOM元素时,这个API作为最后的选择,优先使用Angular提供的模板和数据绑定机制。或者可以使用Render2,它提供了可安全使用的API(即使环境没有提供直接访问原生元素的功能)。
其他的先不管,我们可以使用ElementRef
来操作DOM的属性。比如设置这个input的value:
1 | this.codeEl.nativeElement.value = 'set value'; |
运行后我们可以看到在页面上,set value
这个字符串会显示在input输入框里面。
使用渲染器操作DOM
渲染器是angular的一种内置服务,用于执行UI渲染。在浏览器中,渲染是将模型映射到视图的过程,也就是「从JavaScript中的原始数据或其他数据对象」–TS 到「页面中的段落、表单、按钮等其他页面元素」–DOM。
在实例化一个组件时,Angular会调用renderComponent()
方法并将其获取的渲染器与该组件实例相关联,angular会在渲染组件时通过渲染器执行相应的操作,比如:创建元素、设置属性、添加样式和订阅事件等。
值得注意的是,在Angular4.x+版本中,我们使用Renderer2
来替代Renderer
,通过观察相关抽象类可以发现,除了Render2
定义了更多的抽象方法之外,在一些主要方法上新增了namespace
这个参数(namespace作用于xml中,html中基本没用)。
值得注意的是,尽管可以不用渲染器也可以使用document
来创造元素,但是使用document
创建的元素不具备_ngcontent
这个属性,而这个属性恰恰会标记处该元素是属于按个宿主的,所以使用document
创建的元素无法应用当前组件的css样式,而使用renderer2
渲染器创建的Dom会自动附加这个_ngcontent
属性。
引入Renderer2
:
1 | constructor( |
获得当前组件的宿主元素
我们接下来会进行一些元素的添加或删除等操作,这前提就是我们需要获得当前组件的宿主元素,要不然也无法新增元素。那怎么获取当前组件的宿主元素呢?
我们可以在构造函数中注入ElementRef
,这样声明的这个ElementRef
就是我们当前的组件的宿主元素:
1 | constructor( |
上面我们在当前组件的构造函数中注入了ElementRef
,出于考虑安全问题,我们将dom操作都放在ngAfterViewInit
生命周期里面,输出我们的el
可看到输出的是当前组件的宿主元素。
创建元素 createElement
使用Renderer2
的createElement
方法来创建元素,语法:
1 | createElement(name: string, namespace?: string): any |
创建一个宿主元素,name
为新元素的标识名,在指定的命名空间内应该是唯一的;namespace
表示新元素的命名空间。
例如,创建一个input元素:
1 | const dom = this.render.createElement('input'); |
可以看到,输出的是带当前组件层级_ngcontent
的input元素,
同样的创建函数有:
createCommont(value: string)
添加一个注释的DOMcreateText(value: string)
添加一个文本的DOM
上面的三个创建DOM的方法只是被创建了DOM而已,还没有被渲染到视图界面上,也就是页面上根本不存在这个新创建的元素。还需要通过appendChild
或者insertBefor
方法来添加到视图中去。
将DOM添加到视图中 appendChild
通过appendChild可以将一个创建的元素追加到一个父元素下面,语法:
1 | appendChild(parent: any, newChild: any): void |
比如,我们要在当前组件中添加上面我们创建的input元素:
1 | const dom = this.render.createElement('input'); |
这样可以在页面上看到确实是有了一个input元素。前面说过我们通过注入ElementRef
获取到了当前组件的宿主元素,然后通过appendChild
方法向当前组件中添加了一个input元素。
同样的方法有insertBefor
,语法为:
1 | insertBefore(parent: any, newChild: any, refChild: any): void |
在宿主元素中父节点的指定位置之前插入新元素,例如:
1 | const dom = this.render.createElement('input'); |
文本元素将会被插入在input元素之前。
选择器方法
不仅仅是创建元素,我们还需要获取DOM中的元素。
parentNode(node: any): any
,用来获取宿主元素中的DOM中指定元素的父节点,如果没有则为null。nextSibling(node: any): any
,用来获取宿主元素中的DOM中指定元素的下一个兄弟节点,如果没有则为null。selectRootElement(selectorOrNode: any)
返回将其作为根元素进行引导的元素。
设置/移除元素属性
使用setAttribute()
方法可以设置指定元素的属性。语法:setAttribute(el: any, name: string, value: string, namespace?: string): void
。
- el 目标元素
- name 属性名称
- value 属性值
使用removeAttribute()
方法可以从某个元素上移除某个属性,语法:removeAttribute(el: any, name: string, namespace?: string): void
。
- el 目标元素
- name 属性名称
1 | const p = this.render.createElement('p'); |
上面代码添加了一个p元素,然后给这个p元素添加了id、class和里面的文字。
当然,也对应着移除属性的方法:removeAttribute(el: any, name: string, namespace?: string): void
。例如,移除上面添加的id;
1 | const p = document.getElementById('test'); |
设置样式
- 添加类:
addClass(el: any, name: string): void
- 移除类:
removeClass(el: any, name: string): void
- 设置样式:
setStyle(el: any, name: string, value: any, flags?): void
例如:
1 | this.render.addClass(p, 'test2'); |
给p元素添加test2
类,并删除test1
类,并修改样式width
为300px。
移除元素
方法removeChild(parent: any, oldChild: any): void
可以从父节点中移除子节点。
比如,移除我们添加的那个p元素:
1 | this.render.removeChild(this.el.nativeElement, p); |
设置事件监听
使用listen
函数可以给元素设置监听事件:
1 | listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void |
参数:
- target 要监听事件的上下文,可以是整个窗口或文档,也可以是body或指定的DOM元素。
- eventName 要监听的事件
- callback 当事件发生时的处理回调。
返回一个取消监听的函数,用于解除该监听事件。
模板输入变量
模板输入变量是可以在单个实例的模板中引用值的变量。可以使用let关键字在模板中声明一个输入变量。这个变量的范围被限制在所重复的模板的单一实例上。可以在其他结构型指令上使用同样的变量名,它们互不影响。例如:
1 | <p *ngFor="let item of [1, 2, 3]">{{item}}</p> |
这里let item
就是声明了一个模板输入变量,迭代数组的值。
写了一大堆,就着文档来总结,归根结底还需要在真正使用的时候再完善一些用法和没注意的地方。