observers
提供了基于原生 Web 平台的观察者 API(MutationObserver) 的便捷指令,这里使用是对元素内容的的观察监测
cdkObserveContent
用于观察宿主元素的内容何时发生变化的指令。当观察到内容变化时,它就会发出一个事件
/**
* 提供一个工厂,创建了一个新的MutationObserver对象,便于单测
* @docs-private
*/
@Injectable({providedIn: 'root'})
export class MutationObserverFactory {
create(callback: MutationCallback): MutationObserver | null {
return typeof MutationObserver === 'undefined' ? null : new MutationObserver(callback);
}
}
// 监听元素内容是否发生更改
@Injectable({providedIn: 'root'})
export class ContentObserver implements OnDestroy {
/** 跟踪现有的mutationobserver,这样也可以重用. */
private _observedElements = new Map<
Element,
{
observer: MutationObserver | null;
readonly stream: Subject<MutationRecord[]>;
count: number; // 记录当前元素创建了多少个观察者 同一元素共用同一个subject, 一对多,这个多就是count个数
}
>();
constructor(private _mutationObserverFactory: MutationObserverFactory) {}
ngOnDestroy() {
this._observedElements.forEach((_, element) => this._cleanupObserver(element));
}
/**
* 观察元素的内容变化
*/
observe(element: Element): Observable<MutationRecord[]>;
observe(element: ElementRef<Element>): Observable<MutationRecord[]>;
observe(elementOrRef: Element | ElementRef<Element>): Observable<MutationRecord[]> {
const element = coerceElement(elementOrRef);
return new Observable((observer: Observer<MutationRecord[]>) => {
const stream = this._observeElement(element);
const subscription = stream.subscribe(observer);
// 取消当前订阅及断开元素观察
return () => {
subscription.unsubscribe();
this._unobserveElement(element);
};
});
}
/**
* 使用现有的MutationObserver观察给定的元素. 有就用现有的,没有就创建
*/
private _observeElement(element: Element): Subject<MutationRecord[]> {
if (!this._observedElements.has(element)) {
const stream = new Subject<MutationRecord[]>();
const observer = this._mutationObserverFactory.create(mutations => stream.next(mutations));
if (observer) {
observer.observe(element, {
characterData: true,
childList: true,
subtree: true,
});
}
this._observedElements.set(element, {observer, stream, count: 1});
} else {
this._observedElements.get(element)!.count++;
}
return this._observedElements.get(element)!.stream;
}
/**
*
*/
private _unobserveElement(element: Element) {
// 当元素存在时,count 减1
if (this._observedElements.has(element)) {
this._observedElements.get(element)!.count--;
// 当count 不存在时,说明没有订阅了,没有订阅也就没有必要还监测元素MutationObserver,所以要消除掉元素
if (!this._observedElements.get(element)!.count) {
this._cleanupObserver(element);
}
}
}
/** 清除指定元素的MutationObserver并删除观察元素 . */
private _cleanupObserver(element: Element) {
if (this._observedElements.has(element)) {
const {observer, stream} = this._observedElements.get(element)!;
if (observer) {
observer.disconnect();
}
stream.complete();
this._observedElements.delete(element);
}
}
}
/**
* 指令,当它的相关元素发生了变化时,触发一个回调
*/
@Directive({
selector: '[cdkObserveContent]',
exportAs: 'cdkObserveContent',
})
export class CdkObserveContent implements AfterContentInit, OnDestroy {
/** 在元素内每一个内容更改都会发送事件. */
@Output('cdkObserveContent') readonly event = new EventEmitter<MutationRecord[]>();
/**
* 是否禁用观察内容
*/
@Input('cdkObserveContentDisabled')
get disabled(): boolean {
return this._disabled;
}
set disabled(value: BooleanInput) {
this._disabled = coerceBooleanProperty(value);
this._disabled ? this._unsubscribe() : this._subscribe();
}
private _disabled = false;
/** 防抖时间. */
@Input()
get debounce(): number {
return this._debounce;
}
set debounce(value: NumberInput) {
this._debounce = coerceNumberProperty(value);
this._subscribe();
}
private _debounce: number;
private _currentSubscription: Subscription | null = null;
constructor(
private _contentObserver: ContentObserver,
private _elementRef: ElementRef<HTMLElement>,
private _ngZone: NgZone,
) {}
ngAfterContentInit() {
if (!this._currentSubscription && !this.disabled) {
this._subscribe();
}
}
ngOnDestroy() {
this._unsubscribe();
}
private _subscribe() {
this._unsubscribe();
const stream = this._contentObserver.observe(this._elementRef);
this._ngZone.runOutsideAngular(() => {
this._currentSubscription = (
this.debounce ? stream.pipe(debounceTime(this.debounce)) : stream
).subscribe(this.event);
});
}
private _unsubscribe() {
this._currentSubscription?.unsubscribe();
}
}
注意:cdkObserveContent回调内容是在angular工程检测外的,没有变更检测
使用
用于监听ng-content里面的内容 通过指令cdkObserveContent 回调监听内容变化
<div class="projected-content-wrapper" (cdkObserveContent)="projectContentChanged()">
<ng-content></ng-content>
</div>