Angular/SSRでブラウザにあってNodejsにないJavascript APIクラスを使う際に気を使うこと
※ 当ページには【広告/PR】を含む場合があります。
2025/07/09

ERROR ReferenceError: Image is not defined
at e.ngOnInit (file:///var/task/lambda.mjs:1086:1059)
#...
ERROR ReferenceError: IntersectionObserver is not defined
at e._subscribe (file:///var/task/lambda.mjs:1073:3367)
#...
実装例①〜Imageクラスをサーバーで動作を防止する
HTMLImageElement
import {
Component, OnInit,
} from '@angular/core';
import { SafeResourceUrl } from '@angular/platform-browser';
@Component({
selector: 'app-imgsrc',
template: `
<img [src]="imgsrc1"
[width]="imgWidth || 100"
[height]="imgHeight || 100"/>
`,
standalone: true,
})
export class ImgsrcComponent implements OnInit {
imgsrc1: SafeResourceUrl = '';
imgWidth: number| undefined;
imgHeight: number| undefined;
ngOnInit() {
const _imgpath = /* 画像のURL */;
this.imgsrc1 = _imgpath;
//👇サーバーではImageクラスが無いので、ブラウザ側のみで処理される
if (typeof Image !== 'undefined') {
const img = new Image();
img.onload = () => {
const size: {
width: number;
height: number;
} = {
width: img.naturalWidth,
height: img.naturalHeight,
};
this.imgWidth = size.width;
this.imgHeight = size.height;
};
img.src = _imgpath;
}
}
//...
}
typeof Image
import { inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
//...
platformId = inject(PLATFORM_ID);
//...
if (isPlatformBrowser(this.platformId)) {
//...ブラウザ側だけの処理
} else {
//...サーバー側だけの処理
}
//...
@angular/ssr
「No hydration info in server response」
実装例②〜IntersectionObserverクラスをサーバーで動作を防止する
import { inject, Injectable, ElementRef } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import {
Observable, fromEvent, defer, concat, combineLatest,
map, mergeMap, distinctUntilChanged,
} from 'rxjs';
@Injectable({
providedIn: 'any'
})
export class VisibilityService {
document: Document = inject(DOCUMENT);
private pageVisible$: Observable<boolean>;
constructor() {
this.pageVisible$ = concat(
defer(() => of(!this.document.hidden)),
fromEvent(this.document, 'visibilitychange').pipe(
map(e => !this.document.hidden),
)
);
}
elementInSight(element: ElementRef | Element): Observable<boolean> | undefined {
//👇サーバー側ではIntersectionObserverが無いため、ブラウザのみで動作する
if (typeof IntersectionObserver === 'undefined') {
return undefined;
}
const elementVisible$ = new Observable<IntersectionObserverEntry[]>(observer => {
const intersectionObserver = new IntersectionObserver(entries => {
observer.next(entries);
});
if (element instanceof ElementRef) {
intersectionObserver.observe(element.nativeElement);
}
else if (element instanceof Element) {
intersectionObserver.observe(element);
}
return () => { intersectionObserver.disconnect(); };
}).pipe (
mergeMap((entries: IntersectionObserverEntry[]) => entries),
map((entry: IntersectionObserverEntry) => entry.isIntersecting),
distinctUntilChanged()
);
return combineLatest([this.pageVisible$, elementVisible$]).pipe (
map((flags) => flags[0] && flags[1]),
distinctUntilChanged()
);
}
}
typeof IntersectionObserver
import {
Component, OnInit, inject,
ElementRef, OnDestroy, ViewChild,
} from '@angular/core';
import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser';
import {
Subscription,
of, filter, take,
mergeMap
} from 'rxjs';
//👇先程のサービス
import { VisibilityService } from '../service';
@Component({
selector: 'app-imgsrc',
template: `
<div #img_canvas>
<img [src]="imgsrc1"
[width]="imgWidth || 100"
[height]="imgHeight || 100"/>
</div>
`,
standalone: true,
})
export class ImgsrcComponent implements OnInit, OnDestroy {
@ViewChild('img_canvas', {static: true}) wrapper: ElementRef | undefined;
imgsrc1: SafeResourceUrl = '';
imgWidth: number| undefined;
imgHeight: number| undefined;
inSight$: Subscription| undefined;
private domSanitizer = inject(DomSanitizer);
private visibility = inject(VisibilityService);
ngOnInit() {
const _imgpath = /* 画像のURL */;
//👇ブラウザ側のみで動作する
this.inSight$ = this.visibility.elementInSight(this.wrapper!)?.pipe(
filter(visible => visible),
take(1),
mergeMap(_ => of(_imgpath))
).subscribe((url: string) => {
this.imgsrc1 = this.domSanitizer.bypassSecurityTrustResourceUrl(url);
const img = new Image();
img.onload = () => {
const size: {
width: number;
height: number;
} = {
width: img.naturalWidth,
height: img.naturalHeight,
};
this.imgWidth = size.width;
this.imgHeight = size.height;
};
img.src = url;
});
}
ngOnDestroy() {
if (this.inSight$) {this.inSight$.unsubscribe();}
}
}
まとめ
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー