ฉันกำลังเขียนบริการ Angular 2 ใน TypeScript ซึ่งจะใช้ประโยชน์จากlocalstorage
ไฟล์. ฉันต้องการที่จะฉีดอ้างอิงถึงเบราว์เซอร์window
วัตถุในการให้บริการของฉันตั้งแต่ฉันไม่ต้องการที่จะอ้างอิงตัวแปรใด ๆ ทั่วโลกเช่น $window
1.x
ฉันจะทำอย่างไร?
ฉันกำลังเขียนบริการ Angular 2 ใน TypeScript ซึ่งจะใช้ประโยชน์จากlocalstorage
ไฟล์. ฉันต้องการที่จะฉีดอ้างอิงถึงเบราว์เซอร์window
วัตถุในการให้บริการของฉันตั้งแต่ฉันไม่ต้องการที่จะอ้างอิงตัวแปรใด ๆ ทั่วโลกเช่น $window
1.x
ฉันจะทำอย่างไร?
คำตอบ:
สิ่งนี้ใช้งานได้สำหรับฉันในขณะนี้ (2018-03, เชิงมุม 5.2 พร้อม AoT, ทดสอบใน angular-cli และสร้าง webpack ที่กำหนดเอง):
ขั้นแรกสร้างบริการแบบฉีดที่ให้การอ้างอิงถึงหน้าต่าง:
import { Injectable } from '@angular/core';
// This interface is optional, showing how you can add strong typings for custom globals.
// Just use "Window" as the type if you don't have custom global stuff
export interface ICustomWindow extends Window {
__custom_global_stuff: string;
}
function getWindow (): any {
return window;
}
@Injectable()
export class WindowRefService {
get nativeWindow (): ICustomWindow {
return getWindow();
}
}
ตอนนี้ลงทะเบียนบริการนั้นกับ AppModule รูทของคุณเพื่อให้สามารถฉีดได้ทุกที่:
import { WindowRefService } from './window-ref.service';
@NgModule({
providers: [
WindowRefService
],
...
})
export class AppModule {}
จากนั้นต่อมาในตำแหน่งที่คุณต้องฉีดwindow
:
import { Component} from '@angular/core';
import { WindowRefService, ICustomWindow } from './window-ref.service';
@Component({ ... })
export default class MyCoolComponent {
private _window: ICustomWindow;
constructor (
windowRef: WindowRefService
) {
this._window = windowRef.nativeWindow;
}
public doThing (): void {
let foo = this._window.XMLHttpRequest;
let bar = this._window.__custom_global_stuff;
}
...
คุณอาจต้องการเพิ่มnativeDocument
และ globals อื่น ๆ ในบริการนี้ในลักษณะเดียวกันหากคุณใช้สิ่งเหล่านี้ในแอปพลิเคชันของคุณ
แก้ไข: อัปเดตด้วยคำแนะนำ Truchainz แก้ไข 2: อัปเดตสำหรับเชิงมุม 2.1.2 แก้ไข 3: เพิ่มบันทึกย่อ AoT แก้ไข 4: การเพิ่มany
ประเภทการแก้ไขหมายเหตุแก้ไขปัญหาชั่วคราว 5: อัปเดตโซลูชันเพื่อใช้ WindowRefService ซึ่งแก้ไขข้อผิดพลาดที่ฉันได้รับเมื่อใช้โซลูชันก่อนหน้ากับ build edit ที่แตกต่างกัน 6: เพิ่มตัวอย่างการพิมพ์หน้าต่างที่กำหนดเอง
@Inject
ฉันไม่ได้รับNo provider for Window
ข้อผิดพลาด ดีมากที่ไม่ต้องใช้คู่มือ@Inject
!
@Inject(Window)
เพื่อให้ได้ผล
window
ได้ แต่ด้วยบริการระหว่างนั้นจะช่วยให้สามารถแยกwindow
เนื้อหาพื้นเมืองออกในการทดสอบหน่วยได้และตามที่คุณพูดถึง SSR สามารถให้บริการทางเลือกซึ่งจะแสดงหน้าต่างจำลอง / Noop สำหรับเซิร์ฟเวอร์ เหตุผลที่ฉันพูดถึง AOT เป็นวิธีแก้ปัญหาเบื้องต้นหลายประการสำหรับการตัดหน้าต่างใน AOT เมื่ออัปเดต Angular
ด้วยการเปิดตัว Angular 2.0.0-rc.5 NgModule วิธีแก้ปัญหาก่อนหน้านี้หยุดทำงานสำหรับฉัน นี่คือสิ่งที่ฉันทำเพื่อแก้ไข:
app.module.ts:
@NgModule({
providers: [
{ provide: 'Window', useValue: window }
],
declarations: [...],
imports: [...]
})
export class AppModule {}
ในองค์ประกอบบางส่วน:
import { Component, Inject } from '@angular/core';
@Component({...})
export class MyComponent {
constructor (@Inject('Window') window: Window) {}
}
คุณยังสามารถใช้OpaqueTokenแทนสตริง 'Window'
แก้ไข:
AppModule ใช้ในการบูตแอปพลิเคชันของคุณใน main.ts ดังนี้:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ NgModule โปรดอ่านเอกสาร Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html
คุณสามารถรับหน้าต่างจากเอกสารที่ฉีดเข้าไป
import { Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
export class MyClass {
constructor(@Inject(DOCUMENT) private document: Document) {
this.window = this.document.defaultView;
}
check() {
console.log(this.document);
console.log(this.window);
}
}
คุณสามารถฉีดได้หลังจากตั้งค่าผู้ให้บริการแล้ว:
import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);
constructor(private window: Window) {
// this.window
}
window.var
เนื้อหาของหน้าจะไม่เปลี่ยน
เพื่อให้มันทำงานบน Angular 2.1.1 ฉันต้อง@Inject
หน้าต่างโดยใช้สตริง
constructor( @Inject('Window') private window: Window) { }
แล้วล้อเลียนแบบนี้
beforeEach(() => {
let windowMock: Window = <any>{ };
TestBed.configureTestingModule({
providers: [
ApiUriService,
{ provide: 'Window', useFactory: (() => { return windowMock; }) }
]
});
และโดยปกติ@NgModule
ฉันจัดให้เป็นแบบนี้
{ provide: 'Window', useValue: window }
ใน Angular RC4 ผลงานต่อไปนี้ซึ่งเป็นการรวมกันของคำตอบข้างต้นบางส่วนในแอปรูทของคุณเพิ่มผู้ให้บริการ:
@Component({
templateUrl: 'build/app.html',
providers: [
anotherProvider,
{ provide: Window, useValue: window }
]
})
จากนั้นในบริการของคุณและอื่น ๆ ให้ฉีดเข้าไปในตัวสร้าง
constructor(
@Inject(Window) private _window: Window,
)
ก่อนการประกาศ @Component คุณสามารถทำได้เช่นกัน
declare var window: any;
คอมไพเลอร์จะช่วยให้คุณเข้าถึงตัวแปร global window ได้ทันทีเนื่องจากคุณประกาศว่าเป็นตัวแปร global ที่สมมติด้วย type any
ฉันไม่แนะนำให้เข้าถึงหน้าต่างทุกที่ในแอปพลิเคชันของคุณคุณควรสร้างบริการที่เข้าถึง / แก้ไขแอตทริบิวต์ของหน้าต่างที่จำเป็น (และฉีดบริการเหล่านั้นในส่วนประกอบของคุณ) เพื่อกำหนดขอบเขตสิ่งที่คุณสามารถทำได้กับหน้าต่างโดยไม่ต้องให้พวกเขาแก้ไข วัตถุหน้าต่างทั้งหมด
ฉันใช้OpaqueTokenสำหรับสตริง 'Window':
import {unimplemented} from '@angular/core/src/facade/exceptions';
import {OpaqueToken, Provider} from '@angular/core/index';
function _window(): any {
return window;
}
export const WINDOW: OpaqueToken = new OpaqueToken('WindowToken');
export abstract class WindowRef {
get nativeWindow(): any {
return unimplemented();
}
}
export class BrowserWindowRef extends WindowRef {
constructor() {
super();
}
get nativeWindow(): any {
return _window();
}
}
export const WINDOW_PROVIDERS = [
new Provider(WindowRef, { useClass: BrowserWindowRef }),
new Provider(WINDOW, { useFactory: _window, deps: [] }),
];
และใช้เพื่อนำเข้าWINDOW_PROVIDERS
ใน bootstrap ใน Angular 2.0.0-rc-4
แต่ด้วยการเปิดตัว Angular 2.0.0-rc.5 ฉันต้องสร้างโมดูลแยกต่างหาก:
import { NgModule } from '@angular/core';
import { WINDOW_PROVIDERS } from './window';
@NgModule({
providers: [WINDOW_PROVIDERS]
})
export class WindowModule { }
และกำหนดไว้ในคุณสมบัติการนำเข้าของ main app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { WindowModule } from './other/window.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule, WindowModule ],
declarations: [ ... ],
providers: [ ... ],
bootstrap: [ AppComponent ]
})
export class AppModule {}
เชิงมุม 4 แนะนำ InjectToken และพวกเขายังสร้างโทเค็นสำหรับเอกสารที่เรียกว่าเอกสาร ฉันคิดว่านี่เป็นวิธีแก้ปัญหาอย่างเป็นทางการและใช้ได้ใน AoT
ฉันใช้ตรรกะเดียวกันเพื่อสร้างไลบรารีขนาดเล็กที่เรียกว่าngx-window-tokenเพื่อป้องกันไม่ให้ทำสิ่งนี้ซ้ำแล้วซ้ำอีก
ฉันได้ใช้มันในโครงการอื่นและสร้างใน AoT โดยไม่มีปัญหา
นี่คือวิธีที่ฉันใช้ในแพ็คเกจอื่น
นี่คือผู้พลัดถิ่น
ในโมดูลของคุณ
imports: [ BrowserModule, WindowTokenModule ]
ในส่วนประกอบของคุณ
constructor(@Inject(WINDOW) _window) { }
ณ วันนี้ (เมษายน 2016) รหัสในโซลูชันก่อนหน้าไม่ทำงานฉันคิดว่าเป็นไปได้ที่จะฉีดหน้าต่างลงใน App.ts โดยตรงจากนั้นรวบรวมค่าที่คุณต้องการในบริการสำหรับการเข้าถึงทั่วโลกในแอป แต่ หากคุณต้องการสร้างและฉีดบริการของคุณเองวิธีที่ง่ายกว่าคือวิธีนี้
https://gist.github.com/WilldelaVega777/9afcbd6cc661f4107c2b74dd6090cebf
//--------------------------------------------------------------------------------------------------
// Imports Section:
//--------------------------------------------------------------------------------------------------
import {Injectable} from 'angular2/core'
import {window} from 'angular2/src/facade/browser';
//--------------------------------------------------------------------------------------------------
// Service Class:
//--------------------------------------------------------------------------------------------------
@Injectable()
export class WindowService
{
//----------------------------------------------------------------------------------------------
// Constructor Method Section:
//----------------------------------------------------------------------------------------------
constructor(){}
//----------------------------------------------------------------------------------------------
// Public Properties Section:
//----------------------------------------------------------------------------------------------
get nativeWindow() : Window
{
return window;
}
}
นี่คืออีกวิธีหนึ่งที่ฉันคิดขึ้นเมื่อไม่นานมานี้หลังจากฉันเบื่อที่จะได้รับdefaultView
จากDOCUMENT
โทเค็นในตัวและตรวจสอบว่าเป็นโมฆะ:
import {DOCUMENT} from '@angular/common';
import {inject, InjectionToken} from '@angular/core';
export const WINDOW = new InjectionToken<Window>(
'An abstraction over global window object',
{
factory: () => {
const {defaultView} = inject(DOCUMENT);
if (!defaultView) {
throw new Error('Window is not available');
}
return defaultView;
}
});
@Inject(WINDOW) private _window: any
และใช้มันเหมือนกับโทเค็นการฉีด DOCUMENT ที่จัดทำโดย Angular?
ก็เพียงพอแล้วที่จะทำ
export class AppWindow extends Window {}
และทำ
{ provide: 'AppWindow', useValue: window }
เพื่อให้ทอท. มีความสุข
มีโอกาสสำหรับการเข้าถึงโดยตรงไปยังวัตถุของหน้าต่างผ่านเอกสาร
document.defaultView == window
ฉันรู้ว่าคำถามคือวิธีการฉีดวัตถุหน้าต่างลงในส่วนประกอบ แต่คุณกำลังทำสิ่งนี้เพื่อไปที่ localStorage ดูเหมือนว่า หากคุณจริงๆเพียงแค่ต้องการ localStorage ทำไมไม่ใช้บริการที่ exposes เพียงว่าเหมือนh5webstorage จากนั้นคอมโพเนนต์ของคุณจะอธิบายการอ้างอิงที่แท้จริงซึ่งทำให้โค้ดของคุณอ่านง่ายขึ้น
นี่เป็นคำตอบที่สั้นที่สุด / สะอาดที่สุดที่ฉันพบเมื่อทำงานกับ Angular 4 AOT
ที่มา: https://github.com/angular/angular/issues/12631#issuecomment-274260009
@Injectable()
export class WindowWrapper extends Window {}
export function getWindow() { return window; }
@NgModule({
...
providers: [
{provide: WindowWrapper, useFactory: getWindow}
]
...
})
export class AppModule {
constructor(w: WindowWrapper) {
console.log(w);
}
}
คุณสามารถใช้ NgZone บน Angular 4:
import { NgZone } from '@angular/core';
constructor(private zone: NgZone) {}
print() {
this.zone.runOutsideAngular(() => window.print());
}
นอกจากนี้ยังควรทำเครื่องหมายDOCUMENT
ว่าเป็นทางเลือก ตามเอกสารเชิงมุม:
เอกสารอาจไม่พร้อมใช้งานใน Application Context เมื่อ Application และ Rendering Contexts ไม่เหมือนกัน (เช่นเมื่อรันแอพพลิเคชั่นใน Web Worker)
นี่คือตัวอย่างของการใช้DOCUMENT
เพื่อดูว่าเบราว์เซอร์รองรับ SVG หรือไม่:
import { Optional, Component, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common'
...
constructor(@Optional() @Inject(DOCUMENT) document: Document) {
this.supportsSvg = !!(
document &&
document.createElementNS &&
document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect
);
ขอบคุณสำหรับ @maxisam NGX หน้าต่าง-โทเค็น ฉันกำลังทำบางอย่างที่คล้ายกัน แต่เปลี่ยนมาใช้ของคุณ นี่คือบริการของฉันสำหรับฟังเหตุการณ์ปรับขนาดหน้าต่างและแจ้งเตือนสมาชิก
import { Inject, Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import { WINDOW } from 'ngx-window-token';
export interface WindowSize {
readonly width: number;
readonly height: number;
}
@Injectable()
export class WindowSizeService {
constructor( @Inject(WINDOW) private _window: any ) {
Observable.fromEvent(_window, 'resize')
.auditTime(100)
.map(event => <WindowSize>{width: event['currentTarget'].innerWidth, height: event['currentTarget'].innerHeight})
.subscribe((windowSize) => {
this.windowSizeChanged$.next(windowSize);
});
}
readonly windowSizeChanged$ = new BehaviorSubject<WindowSize>(<WindowSize>{width: this._window.innerWidth, height: this._window.innerHeight});
}
สั้นและหวานและใช้งานได้เหมือนมีเสน่ห์
การรับหน้าต่างวัตถุผ่าน DI (Dependency Injection) ไม่ใช่ความคิดที่ดีเมื่อสามารถเข้าถึงตัวแปรส่วนกลางได้ตลอดทั้งแอปพลิเคชัน
แต่ถ้าคุณไม่ต้องการใช้วัตถุหน้าต่างคุณสามารถใช้self
คำหลักที่ชี้ไปที่วัตถุหน้าต่างได้เช่นกัน
ทำให้มันง่ายคน!
export class HeroesComponent implements OnInit {
heroes: Hero[];
window = window;
}
<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
จริงๆแล้วมันง่ายมากในการเข้าถึงหน้าต่างวัตถุนี่คือส่วนประกอบพื้นฐานของฉันและฉันทดสอบว่ามันใช้งานได้
import { Component, OnInit,Inject } from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';
@Component({
selector: 'app-verticalbanners',
templateUrl: './verticalbanners.component.html',
styleUrls: ['./verticalbanners.component.css']
})
export class VerticalbannersComponent implements OnInit {
constructor(){ }
ngOnInit() {
console.log(window.innerHeight );
}
}
ORIGINAL EXCEPTION: No provider for Window!
. อย่างไรก็ตามการลบออกช่วยแก้ปัญหาให้ฉันได้ การใช้โกลบอล 2 บรรทัดแรกก็เพียงพอแล้วสำหรับฉัน