网络角度介绍访问百度的过程
Have you wondered how Angular forms work with HTML elements where user enters data?
您是否想知道Angular表单如何与用户输入数据HTML元素一起使用?
From the beginning, it was a responsibility of ControlValueAccessor. Angular has several accessors out of the box: for checkboxes, inputs, selects. Let’s say you develop a chat application. You need to be able to change style of the text. Make it bold or underlined, add emojis and so on. You would most likely use contenteditable attribute to handle formatting.
从一开始,它就是ControlValueAccessor的责任。 Angular开箱即用有几个访问器:对于复选框,输入和选择。 假设您开发了一个聊天应用程序。 您需要能够更改文本样式。 使其加粗或加下划线,添加表情符号等。 您很可能会使用contenteditable属性来处理格式。
Angular does not have an accessor for contenteditable, so if you want to use it with forms you will have to write one.
Angular没有contenteditable的访问器,因此,如果要在表单中使用它,则必须编写一个。
Directive that we will create is going to react to contenteditable attribute. And it will implement ControlValueAccessor interface:
我们将创建的指令将对contenteditable属性做出React。 它将实现ControlValueAccessor接口:
interface ControlValueAccessor { writeValue(value: any): void registerOnChange(fn: (value: any) => void): void registerOnTouched(fn: () => void): void setDisabledState(isDisabled: boolean)?: void }To support both reactive and template forms we will provide a special InjectionToken:
为了同时支持React形式和模板形式,我们将提供一个特殊的InjectionToken :
@Directive({ selector: '[contenteditable][formControlName],' + '[contenteditable][formControl],' + '[contenteditable][ngModel]', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ContenteditableValueAccessor), multi: true, }, ], }) export class ContenteditableValueAccessor implements ControlValueAccessor { // ... }We need to implement 3 required and 1 optional methods:
我们需要实现3种必需方法和1种可选方法:
registerOnChange — this method will receive a function during control initialization. Our directive needs to call that function with a new value when user enters something in an editable element. This would propagate value to the control.
registerOnChange —在控件初始化期间,此方法将接收一个函数。 当用户在可编辑元素中输入内容时,我们的指令需要使用新值调用该函数。 这会将值传播到控件。
registerOnTouched — this method will receive a function during control initialization. We need to call it when user left our element so control would become touched. It is important for validation.
registerOnTouched该方法将在控件初始化期间接收一个函数。 当用户离开元素时,我们需要调用它,以便控件能够被touched 。 这对于验证很重要。
writeValue — this method will be called by control to pass value to our directive. It is used to sync value when it is changed externally (setValue or ngModel variable change), and to set initial value.
writeValue —该方法将由控件调用以将值传递给我们的指令。 在外部更改值( setValue或ngModel变量更改)时,它用于同步值,并设置初始值。
Worth mentioning that template forms have a bug related to this method. Initial value is provided with a slight delay and writeValue is called twice, first time with null:https://github.com/angular/angular/issues/14988
值得一提的是,模板表单存在与此方法相关的错误。 提供了初始值,并稍有延迟,并且两次调用writeValue ,第一次使用null : https : //github.com/angular/angular/issues/14988
setDisabledState (optional) — this method will be called by control when disabled state is changed. Even though it is optional it is a good idea to have it, otherwise we cannot have our control disabled.
setDisabledState (可选) —更改disabled状态时,控件将调用此方法。 即使它是可选的,最好还是有它,否则我们不能禁用控件。
To work with DOM element we need Renderer2 and element itself so let’s add them to constructor:
要使用DOM元素,我们需要Renderer2和元素本身,因此我们将它们添加到构造函数中:
constructor( private readonly elementRef: ElementRef, private readonly renderer: Renderer2, ) {}We will store references to the methods control gives us:
我们将存储对控件给我们的方法的引用:
private onTouched = () => {}; private onChange: (value: string) => void = () => {}; registerOnChange(onChange: (value: string) => void) { this.onChange = onChange; } registerOnTouched(onTouched: () => void) { this.onTouched = onTouched; }disabled for contenteditable is equal to contenteditable="false". Setting value is the same as setting innerHTML for DOM element. Value change and focus loss can be tracked with events:
disabled contenteditable等于contenteditable="false" 。 设置值与为DOM元素设置innerHTML相同。 可以通过事件跟踪价值的变化和焦点的丢失:
@HostListener('input') onInput() { this.onChange(this.elementRef.nativeElement.innerHTML); } @HostListener('blur') onBlur() { this.onTouched(); } setDisabledState(disabled: boolean) { this.renderer.setAttribute( this.elementRef.nativeElement, 'contenteditable', String(!disabled), ); } writeValue(value: string) { this.renderer.setProperty( this.elementRef.nativeElement, 'innerHTML', value, ); }That’s it. This is enough to make Angular forms work with contenteditable elements.
而已。 这足以使Angular表单与可编辑contenteditable元素一起使用。
There are couple of “buts”.
有几个“ buts”。
First, initial control value is null and after writeValue in IE11 this is what we will see in the template, literal “null” string. To handle this we need to normalize value:
首先,初始控制值为null ,在IE11中的writeValue之后,这就是我们将在模板中看到的文字“ null”字符串。 为了解决这个问题,我们需要标准化值:
writeValue(value: string | null) { this.renderer.setProperty( this.elementRef.nativeElement, 'innerHTML', ContenteditableValueAccessor.processValue(value), ); } private static processValue(value: string | null): string { const processed = value || ''; return processed.trim() === '<br>' ? '' : processed; }Here we also take care of the following case. Say element had some HTML tags inside. If we just select everything and hit backspace — content gets replaced with a single <br> tag. It’s best to consider it equal to empty string so our control thinks it’s empty.
在这里,我们还要注意以下情况。 说元素里面有一些HTML标签。 如果我们仅选择所有内容并按退格键,则内容将被替换为单个<br>标签。 最好认为它等于空字符串,因此我们的控件认为它为空。
Second, Internet Explorer does not support input event for contenteditable elements. So we are gonna have to make a fallback with aMutationObserver.
其次,Internet Explorer不支持contenteditable元素的input事件。 因此,我们将不得不使用MutationObserver进行回退。
By the way, if you want to use MutationObserver as a declarative Angular directive or an Observable-based service you are welcome to take a look at our little library: https://github.com/ng-web-apis/mutation-observer
顺便说一句,如果您想将MutationObserver用作声明性Angular指令或基于Observable的服务,欢迎您来看看我们的小图书馆: https : //github.com/ng-web-apis/mutation-observer
private readonly observer = new MutationObserver(() => { this.onChange( ContenteditableValueAccessor.processValue( this.elementRef.nativeElement.innerHTML, ), ); }); ngAfterViewInit() { this.observer.observe(this.elementRef.nativeElement, { characterData: true, childList: true, subtree: true, }); } ngOnDestroy() { this.observer.disconnect(); }Instead of checking the browser we will just turn MutationObserver off on a first input event:
无需检查浏览器,我们只需在第一个input事件时关闭MutationObserver :
@HostListener('input') onInput() { this.observer.disconnect(); // ... }Now our component works in IE11 and we are proud of ourselves!
现在我们的组件可以在IE11中使用,我们为自己感到骄傲!
Unfortunately, IE11 will not let go so easily. Apparently it has a bug in MutationObserver. Say contenteditable has tags, for example some <b>text</b>. Removing text that would cause a whole tag to be removed (word text in this example), observer callback will be triggered before actual changes to the DOM!
不幸的是,IE11不会轻易放手。 显然,它在MutationObserver有一个错误。 说contenteditable具有标签,例如some <b>text</b> 。 删除将导致删除整个标签的text在此示例中为单词text ),将在对DOM进行实际更改之前触发观察者回调!
Unfortunately, we have to call it a loss and resort to using setTimeout:
不幸的是,我们不得不称其为损失,而不得不使用setTimeout :
private readonly observer = new MutationObserver(() => { setTimeout(() => { this.onChange( ContenteditableValueAccessor.processValue( this.elementRef.nativeElement.innerHTML, ), ); }); });Considering Angular has to support IE9, 10 and 11 we can understand why they did not make such accessor built-in.
考虑到Angular必须支持IE9、10和11,我们可以理解为什么它们没有内置此类访问器。
We need to remember that HTML can have malicious code placed in it. We shouldn’t blindly add it do the DOM.
我们需要记住,HTML中可能包含恶意代码。 我们不应该盲目地将其添加到DOM中。
And we cannot trust users either so we must watch out for paste and drop events. Such content has to be sanitized. A good and trusted tool for that is DOMPurify. Read here how we implemented it in an Angular app or just download our package:
我们不能信任任何用户,所以我们必须留意paste和drop的事件。 此类内容必须进行清理。 一个出色且值得信赖的工具是DOMPurify 。 在这里阅读我们如何在Angular应用中实现它,或者只是下载我们的软件包:
You can also grab the accessor we’ve created in this article as a tiny package that works in Angular 4+:
您也可以将我们在本文中创建的访问器作为一个可在Angular 4+中使用的微型程序包:
A live playground:
现场游乐场:
演示地址
翻译自: https://medium.com/its-tinkoff/controlvalueaccessor-and-contenteditable-in-angular-6ebf50b7475e
网络角度介绍访问百度的过程