I am trying to set up IntersectionObserver
in Angular but I can't get it to work. I want to load data when I scroll down using HttpClient
from the server. I'm doing something like this:
In HTML on the bottom of the page I have this:
<div #ob></div>
In the .ts file I am using ViewChild
to grab the element and pass it to the IntersectionObserverService:
@ViewChild('ob') ob: ElementRef;
constructor(
private inter: IntersectionObserverService) { }
ngAfterViewInit(): void {
this.inter.createAndObserve(this.ob);
}
Then in the service I observe the passed in element and try to console.log
the entries but nothing is happening.
@Injectable({
providedIn: 'root'
})
export class IntersectionObserverService {
_observer: IntersectionObserver | undefined;
constructor() { }
createAndObserve(element: ElementRef) {
const options = {
root: null,
threshold: 0.1
};
this._observer = new IntersectionObserver((entries, observer) => {
console.log('on')
entries.forEach((entry: IntersectionObserverEntry) => {
console.log(entry);
console.log('okk');
// observer.unobserve(entry.target);
});
}, options);
this._observer.observe(element.nativeElement);
}
}
I would want here to pass a callback to get my data from the backend when I intersect <div #ob></div>
. Then pass it forward. How can I make the IntersectionObserver
to observe it and display entries?
I am trying to set up IntersectionObserver
in Angular but I can't get it to work. I want to load data when I scroll down using HttpClient
from the server. I'm doing something like this:
In HTML on the bottom of the page I have this:
<div #ob></div>
In the .ts file I am using ViewChild
to grab the element and pass it to the IntersectionObserverService:
@ViewChild('ob') ob: ElementRef;
constructor(
private inter: IntersectionObserverService) { }
ngAfterViewInit(): void {
this.inter.createAndObserve(this.ob);
}
Then in the service I observe the passed in element and try to console.log
the entries but nothing is happening.
@Injectable({
providedIn: 'root'
})
export class IntersectionObserverService {
_observer: IntersectionObserver | undefined;
constructor() { }
createAndObserve(element: ElementRef) {
const options = {
root: null,
threshold: 0.1
};
this._observer = new IntersectionObserver((entries, observer) => {
console.log('on')
entries.forEach((entry: IntersectionObserverEntry) => {
console.log(entry);
console.log('okk');
// observer.unobserve(entry.target);
});
}, options);
this._observer.observe(element.nativeElement);
}
}
I would want here to pass a callback to get my data from the backend when I intersect <div #ob></div>
. Then pass it forward. How can I make the IntersectionObserver
to observe it and display entries?
2 Answers
Reset to default 17In your component do:
@ViewChild('ob', { read: ElementRef })
ob: ElementRef;
ngAfterViewInit(): void {
this.inter.createAndObserve(this.ob).pipe(
filter((isVisible: boolean) => isVisible),
switchMap(() => this.yourService.loadData())
).subscribe(data => { ... });
}
Then go to your service and update your method, so now it will return an observable that will emit a boolean event everytime you intersect your host element:
createAndObserve(element: ElementRef): Observable<boolean> {
return new Observable(observer => {
const intersectionObserver = new IntersectionObserver(entries => {
observer.next(entries);
});
intersectionObserver.observe(element.nativeElement);
return () => { intersectionObserver.disconnect(); };
}).pipe(
mergeMap((entries: IntersectionObserverEntry[]) => entries),
map(entry => entry.isIntersecting),
distinctUntilChanged()
);
}
I highly suggest you create a directive to do this.
Single IntersectionObserver approach
Parent Component
@Component({
selector: 'app-log-view',
template: `
<ul>
<li *ngFor="let log of this.logs$ | async" appObserverChild [observer]="this.observer">
<p>{{ log.message }}</p>
<small>{{ log.createdAt | date : 'short' }}</small>
</li>
</ul>
`,
styleUrls: ['./log-view.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LogViewComponent {
readonly observer = new IntersectionObserver((entries) => console.log(entries));
constructor(private logService: LogService, private route: ActivatedRoute) {}
}
appObserverChild Directive
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
@Directive({
selector: '[appObserverChild]',
})
export class ObserverChildDirective implements AfterViewInit {
@Input() observer!: IntersectionObserver;
constructor(private el: ElementRef) {}
ngAfterViewInit(): void {
this.observer.observe(this.el.nativeElement)
}
static ngTemplateContextGuard(directive: ObserverChildDirective, context: unknown): context is ObserverChildContext {
return true;
}
}
interface ObserverChildContext {
observer: IntersectionObserver;
}
Intersection Observer for single element
I would not suggest using this approach if you want to observer multiple elements. Performance will get really choppy.
@Directive({
selector: '[appObservable]',
})
export class ObservableDirective {
private observer: IntersectionObserver;
@Output() intersection = new EventEmitter<void>();
constructor(public el: ElementRef) {
this.observer = new IntersectionObserver(this.callback, { rootMargin: '100px', threshold: 0.5, root: null });
this.observer.observe(this.el.nativeElement);
}
private callback: ConstructorParameters<typeof IntersectionObserver>[0] = (entries) =>
entries
.filter((entry) => entry.isIntersecting)
.forEach((_entry) => {
this.intersection.emit();
});
}
Using a directive like this will make your life easier.