จะฉีดหน้าต่างลงในบริการได้อย่างไร?


111

ฉันกำลังเขียนบริการ Angular 2 ใน TypeScript ซึ่งจะใช้ประโยชน์จากlocalstorageไฟล์. ฉันต้องการที่จะฉีดอ้างอิงถึงเบราว์เซอร์windowวัตถุในการให้บริการของฉันตั้งแต่ฉันไม่ต้องการที่จะอ้างอิงตัวแปรใด ๆ ทั่วโลกเช่น $window1.x

ฉันจะทำอย่างไร?

คำตอบ:


135

สิ่งนี้ใช้งานได้สำหรับฉันในขณะนี้ (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: เพิ่มตัวอย่างการพิมพ์หน้าต่างที่กำหนดเอง


1
การมี @Inject ในพารามิเตอร์ตัวสร้างทำให้เกิดข้อผิดพลาดมากมายสำหรับฉันเช่นORIGINAL EXCEPTION: No provider for Window!. อย่างไรก็ตามการลบออกช่วยแก้ปัญหาให้ฉันได้ การใช้โกลบอล 2 บรรทัดแรกก็เพียงพอแล้วสำหรับฉัน
TrieuNomad

น่าสนใจ ^^ ฉันจะต้องลองในโครงการสาธิตอีกสองสามโครงการ - โดยที่@Injectฉันไม่ได้รับNo provider for Windowข้อผิดพลาด ดีมากที่ไม่ต้องใช้คู่มือ@Inject!
elwyn

ในวันที่ 2.1.2 ฉันต้องใช้@Inject(Window)เพื่อให้ได้ผล
James Kleeh

1
angular.io/docs/ts/latest/guide/… . แย่จังไม่ได้อ่านอย่างละเอียด
Teedeez

2
@ ไบรอันใช่มันยังคงเข้าถึงwindowได้ แต่ด้วยบริการระหว่างนั้นจะช่วยให้สามารถแยกwindowเนื้อหาพื้นเมืองออกในการทดสอบหน่วยได้และตามที่คุณพูดถึง SSR สามารถให้บริการทางเลือกซึ่งจะแสดงหน้าต่างจำลอง / Noop สำหรับเซิร์ฟเวอร์ เหตุผลที่ฉันพูดถึง AOT เป็นวิธีแก้ปัญหาเบื้องต้นหลายประการสำหรับการตัดหน้าต่างใน AOT เมื่ออัปเดต Angular
elwyn

34

ด้วยการเปิดตัว 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


20

คุณสามารถรับหน้าต่างจากเอกสารที่ฉีดเข้าไป

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);
  }

}

19

คุณสามารถฉีดได้หลังจากตั้งค่าผู้ให้บริการแล้ว:

import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);

constructor(private window: Window) {
    // this.window
}

แต่เมื่อฉันเปลี่ยนwindow.varเนื้อหาของหน้าจะไม่เปลี่ยน
Ravinder Payal

7
สิ่งนี้ใช้ไม่ได้ใน Safari เนื่องจาก Window ไม่สามารถแทรกได้ ฉันต้องสร้างประเภท Injectable ของตัวเองซึ่งมีคุณสมบัติของ Window ที่ฉันต้องการ แนวทางที่ดีกว่าคือการสร้างบริการตามที่อธิบายไว้ในคำตอบอื่น ๆ
daveb

วิธีนี้ใช้ไม่ได้ผลเนื่องจาก useValue สร้างสำเนาของค่าที่คุณระบุไว้ ดู: github.com/angular/angular/issues/10788#issuecomment-300614425 วิธีนี้จะใช้ได้ผลถ้าคุณเปลี่ยนไปใช้ useFactory และส่งคืนค่าจากการเรียกกลับ
Levi Lindsey

15

เพื่อให้มันทำงานบน 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 }

10

ใน Angular RC4 ผลงานต่อไปนี้ซึ่งเป็นการรวมกันของคำตอบข้างต้นบางส่วนในแอปรูทของคุณเพิ่มผู้ให้บริการ:

@Component({
    templateUrl: 'build/app.html',
    providers: [
        anotherProvider,
        { provide: Window, useValue: window }
    ]
})

จากนั้นในบริการของคุณและอื่น ๆ ให้ฉีดเข้าไปในตัวสร้าง

constructor(
      @Inject(Window) private _window: Window,
)

10

ก่อนการประกาศ @Component คุณสามารถทำได้เช่นกัน

declare var window: any;

คอมไพเลอร์จะช่วยให้คุณเข้าถึงตัวแปร global window ได้ทันทีเนื่องจากคุณประกาศว่าเป็นตัวแปร global ที่สมมติด้วย type any

ฉันไม่แนะนำให้เข้าถึงหน้าต่างทุกที่ในแอปพลิเคชันของคุณคุณควรสร้างบริการที่เข้าถึง / แก้ไขแอตทริบิวต์ของหน้าต่างที่จำเป็น (และฉีดบริการเหล่านั้นในส่วนประกอบของคุณ) เพื่อกำหนดขอบเขตสิ่งที่คุณสามารถทำได้กับหน้าต่างโดยไม่ต้องให้พวกเขาแก้ไข วัตถุหน้าต่างทั้งหมด


หากคุณทำการเรนเดอร์ฝั่งเซิร์ฟเวอร์โค้ดของคุณจะใช้งานไม่ได้เนื่องจากในฝั่ง srver คุณไม่มีวัตถุหน้าต่างใด ๆ และคุณต้องฉีดของคุณเอง
Alex Nikulin

9

ฉันใช้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 {}

7

เชิงมุม 4 แนะนำ InjectToken และพวกเขายังสร้างโทเค็นสำหรับเอกสารที่เรียกว่าเอกสาร ฉันคิดว่านี่เป็นวิธีแก้ปัญหาอย่างเป็นทางการและใช้ได้ใน AoT

ฉันใช้ตรรกะเดียวกันเพื่อสร้างไลบรารีขนาดเล็กที่เรียกว่าngx-window-tokenเพื่อป้องกันไม่ให้ทำสิ่งนี้ซ้ำแล้วซ้ำอีก

ฉันได้ใช้มันในโครงการอื่นและสร้างใน AoT โดยไม่มีปัญหา

นี่คือวิธีที่ฉันใช้ในแพ็คเกจอื่น

นี่คือผู้พลัดถิ่น

ในโมดูลของคุณ

imports: [ BrowserModule, WindowTokenModule ] ในส่วนประกอบของคุณ

constructor(@Inject(WINDOW) _window) { }


6

ณ วันนี้ (เมษายน 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;
    }
}

5

นี่คืออีกวิธีหนึ่งที่ฉันคิดขึ้นเมื่อไม่นานมานี้หลังจากฉันเบื่อที่จะได้รับ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;
        }
    });

1
ดังนั้นฉันจึงใส่สิ่งนี้ไว้ในโฟลเดอร์ผู้ให้บริการของฉัน (เช่น) จากนั้นฉันใช้ในตัวสร้างส่วนประกอบของฉันโทเค็นฉีดนี้? @Inject(WINDOW) private _window: anyและใช้มันเหมือนกับโทเค็นการฉีด DOCUMENT ที่จัดทำโดย Angular?
Sparker73

ใช่นั่นคือทั้งหมดที่มีให้
waterplea

ได้. มันทำงานได้อย่างสมบูรณ์รถถังสำหรับวิธีง่ายๆนี้
Sparker73

4

ก็เพียงพอแล้วที่จะทำ

export class AppWindow extends Window {} 

และทำ

{ provide: 'AppWindow', useValue: window } 

เพื่อให้ทอท. มีความสุข



3

ฉันรู้ว่าคำถามคือวิธีการฉีดวัตถุหน้าต่างลงในส่วนประกอบ แต่คุณกำลังทำสิ่งนี้เพื่อไปที่ localStorage ดูเหมือนว่า หากคุณจริงๆเพียงแค่ต้องการ localStorage ทำไมไม่ใช้บริการที่ exposes เพียงว่าเหมือนh5webstorage จากนั้นคอมโพเนนต์ของคุณจะอธิบายการอ้างอิงที่แท้จริงซึ่งทำให้โค้ดของคุณอ่านง่ายขึ้น


2
แม้ว่าลิงก์นี้อาจตอบคำถามได้ แต่ควรรวมส่วนสำคัญของคำตอบไว้ที่นี่และระบุลิงก์เพื่อการอ้างอิง คำตอบแบบลิงก์เท่านั้นอาจไม่ถูกต้องหากหน้าที่เชื่อมโยงเปลี่ยนไป
พนักงานทุกคนมีความจำเป็น

3

นี่เป็นคำตอบที่สั้นที่สุด / สะอาดที่สุดที่ฉันพบเมื่อทำงานกับ 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);
  }
}

2

คุณสามารถใช้ NgZone บน Angular 4:

import { NgZone } from '@angular/core';

constructor(private zone: NgZone) {}

print() {
    this.zone.runOutsideAngular(() => window.print());
}

2

นอกจากนี้ยังควรทำเครื่องหมาย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
);

0

ขอบคุณสำหรับ @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});
}

สั้นและหวานและใช้งานได้เหมือนมีเสน่ห์


0

การรับหน้าต่างวัตถุผ่าน DI (Dependency Injection) ไม่ใช่ความคิดที่ดีเมื่อสามารถเข้าถึงตัวแปรส่วนกลางได้ตลอดทั้งแอปพลิเคชัน

แต่ถ้าคุณไม่ต้องการใช้วัตถุหน้าต่างคุณสามารถใช้selfคำหลักที่ชี้ไปที่วัตถุหน้าต่างได้เช่นกัน


4
นั่นไม่ใช่คำแนะนำที่ดี Dependency Injection ทำให้คลาส (คอมโพเนนต์คำสั่งบริการไปป์ ... ) ทดสอบได้ง่ายขึ้น (เช่นแม้จะไม่มีเบราว์เซอร์) และนำมาใช้ซ้ำบนแพลตฟอร์มต่างๆเช่นการแสดงผลฝั่งเซิร์ฟเวอร์หรือ Web Workers ได้ง่ายขึ้น อาจได้ผลสำหรับบางคนและความเรียบง่ายอาจมีความดึงดูด แต่การใช้ DI IMHO เป็นคำตอบที่ไม่ดี
GünterZöchbauer

หากคุณทำการเรนเดอร์ฝั่งเซิร์ฟเวอร์โค้ดของคุณจะใช้งานไม่ได้เนื่องจากในฝั่ง srver คุณไม่มีวัตถุหน้าต่างใด ๆ และคุณต้องฉีดของคุณเอง
Alex Nikulin

-1

ทำให้มันง่ายคน!

export class HeroesComponent implements OnInit {
  heroes: Hero[];
  window = window;
}

<div>{{window.Object.entries({ foo: 1 }) | json}}</div>

หากคุณทำการเรนเดอร์ฝั่งเซิร์ฟเวอร์โค้ดของคุณจะใช้งานไม่ได้เนื่องจากในฝั่ง srver คุณไม่มีวัตถุหน้าต่างใด ๆ และคุณต้องฉีดของคุณเอง
Alex Nikulin

-2

จริงๆแล้วมันง่ายมากในการเข้าถึงหน้าต่างวัตถุนี่คือส่วนประกอบพื้นฐานของฉันและฉันทดสอบว่ามันใช้งานได้

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 );
  }

}

หากคุณทำการเรนเดอร์ฝั่งเซิร์ฟเวอร์โค้ดของคุณจะใช้งานไม่ได้เนื่องจากในฝั่ง srver คุณไม่มีวัตถุหน้าต่างใด ๆ และคุณต้องฉีดของคุณเอง
Alex Nikulin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.