我try 创建一个监听DOM property changes
的指令,以自动将属性值保存到LocalStorage,并在页面加载时自动恢复(以保存任何HTML元素的用户首选项).我需要它来监听输入的变化,但是因为我想要一个通用指令,所以我不想监听change
事件.
我用这个答案得到了一个解决方案:https://stackoverflow.com/a/55737231/7920723
我不得不将其转换为typescript并使其适应使用Observables
,这里是一个mvce:
import { AfterViewInit, Component, Directive, ElementRef, EventEmitter, Input, Output } from '@angular/core';
import { Observable } from 'rxjs';
@Directive({
selector: '[cdltStoreProperty]',
standalone: true
})
export class StoreAttributeDirective implements AfterViewInit {
id!: string;
@Input('cdltStoreProperty') attributeName: string | null = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@Output() readonly storedValue = new EventEmitter<any>();
constructor(
private ref: ElementRef
) {
console.log('test');
}
ngAfterViewInit(): void {
const element = this.ref.nativeElement;
if (this.attributeName) {
this.id = `${StoreAttributeDirective.name}_${element.getAttribute('id')}`;
console.log(`id = ${this.id}`);
const valueStored = this.restoreValue();
if (!valueStored) {
const value = element[this.attributeName];
this.saveValue(value);
this.storedValue.emit(value);
}
observePropertyChange(this.ref, this.attributeName).subscribe(newValue => {
console.log("property changed");
this.saveValue(newValue)
});
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
restoreValue() : any {
const valueStored = window.localStorage.getItem(this.id);
console.log(`valueStored = ${valueStored} of type = ${typeof valueStored}`);
if (valueStored && this.attributeName) {
const value = JSON.parse(valueStored);
console.log(`Restoring value ${value} of type ${typeof value}`);
this.ref.nativeElement[this.attributeName] = value;
return value;
}
return undefined;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
saveValue(value: any) {
console.log(`Saving ${value} of type ${typeof value}`);
const valueToStore = JSON.stringify(value);
console.log(`Saving value to store ${valueToStore} of type ${typeof valueToStore}`);
window.localStorage.setItem(this.id, valueToStore);
}
}
@Component({
selector: 'cdlt-mvce',
standalone: true,
imports: [StoreAttributeDirective],
template: `
<input
id="remember-me-checkbox"
type="checkbox"
cdltStoreProperty="checked"
[checked]="checkedValue"
(storedValue)="loadStoredValue($event)"
(change)="checkedValue = !checkedValue"
>`,
styleUrl: './mvce.component.scss'
})
export class MvceComponent {
checkedValue = true;
loadStoredValue(value: any) {
this.checkedValue = value;
}
}
function observePropertyChange(elementRef: ElementRef, propertyName: string) : Observable<any> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const propertyObserver$ = new Observable<any>(observer => {
const superProps = Object.getPrototypeOf(elementRef.nativeElement);
const propertyDescriptor = Object.getOwnPropertyDescriptor(superProps, propertyName);
if (!propertyDescriptor) {
console.error(`No property descriptor for ${propertyName}`);
return;
}
const superGet = propertyDescriptor.get;
const superSet = propertyDescriptor.set;
if (!superGet) {
console.error(`No getter for ${propertyName}`);
return;
} else if (!superSet) {
console.error(`No setter for ${propertyName}`);
return;
}
const newProps = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get: function() : any {
return superGet.apply(this, []);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
set: function (t : any) : any {
observer.next(t);
return superSet.apply(this, [t]);
}
};
Object.defineProperty(elementRef.nativeElement, propertyName, newProps);
});
return propertyObserver$;
}
既然我是在AfterViewInit
阶段订阅的属性,你认为这是正确的吗?
这段代码有两个问题:
- 如果您在未选中
input
的情况下刷新,则恢复功能会引发ExpressionChangedAfterItHasBeenCheckedError
,所以我想这不是正确的方法 - 如果使用未绑定属性
checked
的输入,例如使用checked=false
,则观察器会在某个地方中断,原因我不明白,就好像在视图初始化后替换了nativeElement
或checked
属性.
你知道实现这一指令的正确方式是什么吗?