Làm thế nào để đưa cửa sổ vào một dịch vụ?


110

Tôi đang viết một dịch vụ Angular 2 trong TypeScript sẽ sử dụng localstorage. Tôi muốn tiêm một tham chiếu đến các trình duyệt windowđối tượng vào dịch vụ của tôi kể từ khi tôi không muốn tham khảo bất kỳ biến toàn cầu như 1.x góc $window.

Làm thế nào để làm điều đó?

Câu trả lời:


134

Điều này hiện đang hoạt động đối với tôi (2018-03, angle 5.2 với AoT, được thử nghiệm trong angle-cli và bản dựng webpack tùy chỉnh):

Đầu tiên, tạo một dịch vụ có thể tiêm cung cấp tham chiếu đến cửa sổ:

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

Bây giờ, hãy đăng ký dịch vụ đó với AppModule gốc của bạn để nó có thể được đưa vào mọi nơi:

import { WindowRefService } from './window-ref.service';

@NgModule({        
  providers: [
    WindowRefService 
  ],
  ...
})
export class AppModule {}

và sau đó là nơi bạn cần tiêm 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;
    }
...

Bạn cũng có thể muốn thêm nativeDocumentvà các hình cầu khác vào dịch vụ này theo cách tương tự nếu bạn sử dụng chúng trong ứng dụng của mình.


chỉnh sửa: Đã cập nhật với gợi ý của Truchainz. edit2: Cập nhật cho góc 2.1.2 chỉnh sửa3: Thêm ghi chú AoT chỉnh sửa4: Thêm anyloại ghi chú giải pháp chỉnh sửa5: Giải pháp cập nhật để sử dụng WindowRefService khắc phục lỗi tôi gặp phải khi sử dụng giải pháp trước đó với một bản chỉnh sửa bản dựng khác6: thêm ví dụ nhập Window tùy chỉnh


1
Có @Inject trong các tham số phương thức khởi tạo đã gây ra một loạt lỗi cho tôi chẳng hạn như ORIGINAL EXCEPTION: No provider for Window!. Tuy nhiên, việc loại bỏ nó đã khắc phục sự cố cho tôi. Tôi chỉ sử dụng 2 dòng toàn cầu đầu tiên là đủ.
TrieuNomad

Thật thú vị ^^ Tôi sẽ phải thử nó trong một vài dự án demo nữa - mà @Injecttôi không gặp No provider for Windowlỗi. Điều đó khá tốt không cần hướng dẫn sử dụng @Inject!
elwyn

Vào ngày 2.1.2, tôi phải sử dụng @Inject(Window)cái này để hoạt động
James Kleeh

1
angle.io/docs/ts/latest/guide/… . Oh xấu của tôi, đã không đọc một cách cẩn thận
Teedeez

2
@Brian vâng, nó vẫn đang truy cập window, nhưng với dịch vụ ở giữa, nó cho phép khai thác windownội dung gốc trong các bài kiểm tra đơn vị và như bạn đề cập đối với SSR, một dịch vụ thay thế có thể được cung cấp để hiển thị cửa sổ giả / noop cho máy chủ. Lý do tôi đề cập đến AOT là một số giải pháp ban đầu để gói cửa sổ bị hỏng trong AOT khi Angular cập nhật.
elwyn

34

Với việc phát hành NgModule 2.0.0-rc.5 góc cạnh đã được giới thiệu. Giải pháp trước đó đã ngừng hoạt động đối với tôi. Đây là những gì tôi đã làm để sửa nó:

app.module.ts:

@NgModule({        
  providers: [
    { provide: 'Window',  useValue: window }
  ],
  declarations: [...],
  imports: [...]
})
export class AppModule {}

Trong một số thành phần:

import { Component, Inject } from '@angular/core';

@Component({...})
export class MyComponent {
    constructor (@Inject('Window') window: Window) {}
}

Bạn cũng có thể sử dụng OpaqueToken thay vì chuỗi 'Window'

Biên tập:

AppModule được sử dụng để khởi động ứng dụng của bạn trong main.ts như sau:

import { platformBrowserDynamic  } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)

Để biết thêm thông tin về NgModule, hãy đọc tài liệu Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html


19

Bạn chỉ có thể tiêm nó sau khi đã đặt nhà cung cấp:

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

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

nhưng khi tôi thay đổi window.varnội dung của trang không thay đổi
Ravinder Payal

6
Điều này không hoạt động trong Safari vì Window không thể tiêm được. Tôi phải tạo kiểu Injectable của riêng mình chứa các thuộc tính của Window mà tôi yêu cầu. Một cách tiếp cận tốt hơn có thể là tạo ra một dịch vụ như được mô tả trong các câu trả lời khác
daveb

Cách tiếp cận này không hoạt động, vì useValue thực sự tạo ra một bản sao của giá trị bạn cung cấp cho nó. Xem: github.com/angular/angular/issues/10788#issuecomment-300614425 . Cách tiếp cận này sẽ hoạt động nếu bạn thay đổi nó để sử dụng useFactory và trả về giá trị từ một lệnh gọi lại.
Levi Lindsey

18

Bạn có thể lấy cửa sổ từ tài liệu được chèn.

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

}

15

Để làm cho nó hoạt động trên Angular 2.1.1, tôi phải mở @Injectcửa sổ bằng một chuỗi

  constructor( @Inject('Window') private window: Window) { }

và sau đó chế nhạo nó như thế này

beforeEach(() => {
  let windowMock: Window = <any>{ };
  TestBed.configureTestingModule({
    providers: [
      ApiUriService,
      { provide: 'Window', useFactory: (() => { return windowMock; }) }
    ]
  });

và thông thường @NgModuletôi cung cấp nó như thế này

{ provide: 'Window', useValue: window }

10

Trong Angular RC4, các hoạt động sau đây là sự kết hợp của một số câu trả lời ở trên, trong ứng dụng gốc của bạn. Hãy thêm nó các nhà cung cấp:

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

Sau đó, trong dịch vụ của bạn, v.v. đưa nó vào hàm tạo

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

10

Trước khi khai báo @Component, bạn cũng có thể làm điều đó,

declare var window: any;

Trình biên dịch thực sự sẽ cho phép bạn truy cập biến cửa sổ toàn cầu ngay bây giờ vì bạn khai báo nó như một biến toàn cục giả định với kiểu bất kỳ.

Tuy nhiên, tôi sẽ không đề xuất truy cập cửa sổ ở mọi nơi trong ứng dụng của bạn, Bạn nên tạo các dịch vụ truy cập / sửa đổi các thuộc tính cửa sổ cần thiết (và đưa các dịch vụ đó vào các thành phần của bạn) để phạm vi những gì bạn có thể làm với cửa sổ mà không cho phép họ sửa đổi toàn bộ đối tượng cửa sổ.


Nếu bạn thực hiện kết xuất phía máy chủ, mã của bạn sẽ bị hỏng. Vì ở phía máy chủ, bạn không có bất kỳ đối tượng cửa sổ nào và bạn cần chèn mã của riêng mình.
Alex Nikulin,

9

Tôi đã sử dụng OpaqueToken cho chuỗi 'Cửa sổ':

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: [] }),
];

Và được sử dụng chỉ để nhập WINDOW_PROVIDERSvào bootstrap trong Angular 2.0.0-rc-4.

Nhưng với việc phát hành Angular 2.0.0-rc.5, tôi cần tạo một mô-đun riêng:

import { NgModule } from '@angular/core';
import { WINDOW_PROVIDERS } from './window';

@NgModule({
    providers: [WINDOW_PROVIDERS]
})
export class WindowModule { }

và vừa được xác định trong thuộc tính nhập khẩu của 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 {}

6

Kể từ hôm nay (tháng 4 năm 2016), mã trong giải pháp trước đó không hoạt động, tôi nghĩ có thể đưa cửa sổ trực tiếp vào App.ts và sau đó thu thập các giá trị bạn cần vào một dịch vụ để truy cập toàn cầu trong Ứng dụng, nhưng nếu bạn thích tạo và đưa vào dịch vụ của riêng mình, một cách đơn giản hơn là giải pháp này.

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

6

Angular 4 giới thiệu InjectToken và họ cũng tạo một mã thông báo cho tài liệu có tên là DOCUMENT . Tôi nghĩ đây là giải pháp chính thức và nó hoạt động trong AoT.

Tôi sử dụng logic tương tự để tạo một thư viện nhỏ có tên ngx-window-token để ngăn việc làm này lặp đi lặp lại.

Tôi đã sử dụng nó trong dự án khác và xây dựng trong AoT mà không gặp vấn đề gì.

Đây là cách tôi sử dụng nó trong gói khác

Đây là plunker

Trong mô-đun của bạn

imports: [ BrowserModule, WindowTokenModule ] Trong thành phần của bạn

constructor(@Inject(WINDOW) _window) { }


4

Nó là đủ để làm

export class AppWindow extends Window {} 

và làm

{ provide: 'AppWindow', useValue: window } 

để làm cho rất nhiều hạnh phúc


4

Có cơ hội để truy cập trực tiếp vào đối tượng của window thông qua tài liệu

document.defaultView == window

4

Đây là một giải pháp tôi đã đưa ra thời gian gần đây sau khi tôi đã mệt mỏi nhận được defaultViewtừ DOCUMENTbuilt-in thẻ và kiểm tra nó cho null:

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
Vì vậy, tôi đặt nó trong thư mục nhà cung cấp của tôi (ví dụ) và sau đó tôi sử dụng mã thông báo tiêm này trong hàm tạo của thành phần của tôi? @Inject(WINDOW) private _window: anyvà sử dụng nó như mã thông báo tiêm DOCUMENT do Angular cung cấp?
Sparker73

Vâng, đó là tất cả những gì cần làm.
waterplea

Đúng vậy. Nó hoạt động hoàn hảo, xe tăng cho giải pháp đơn giản này.
Sparker73

3

Tôi biết câu hỏi là làm thế nào để đưa đối tượng cửa sổ vào một thành phần nhưng bạn đang làm điều này chỉ để truy cập localStorage. Nếu bạn thực sự chỉ muốn localStorage, tại sao không sử dụng một dịch vụ chỉ tiết lộ điều đó, như h5webstorage . Sau đó, thành phần của bạn sẽ mô tả các phụ thuộc thực sự của nó để làm cho mã của bạn dễ đọc hơn.


2
Mặc dù liên kết này có thể trả lời câu hỏi, nhưng tốt hơn hết bạn nên đưa các phần thiết yếu của câu trả lời vào đây và cung cấp liên kết để tham khảo. Các câu trả lời chỉ có liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi.
Tất cả công nhân đều cần thiết vào

3

Đây là câu trả lời ngắn gọn / rõ ràng nhất mà tôi thấy khi làm việc với Angular 4 AOT

Nguồn: 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

Bạn có thể sử dụng NgZone trên Angular 4:

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

constructor(private zone: NgZone) {}

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

2

Bạn cũng nên đánh dấu DOCUMENTlà tùy chọn. Theo tài liệu Angular:

Tài liệu có thể không khả dụng trong Bối cảnh ứng dụng khi Ứng dụng và Khung kết xuất không giống nhau (ví dụ: khi chạy ứng dụng vào Web Worker).

Dưới đây là một ví dụ về việc sử dụng DOCUMENTđể xem liệu trình duyệt có hỗ trợ SVG hay không:

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 cảm ơn vì ngx-window-token . Tôi đã làm một cái gì đó tương tự nhưng chuyển sang của bạn. Đây là dịch vụ của tôi để lắng nghe các sự kiện thay đổi kích thước cửa sổ và thông báo cho người đăng ký.

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

Ngắn gọn và ngọt ngào và hoạt động như một sự quyến rũ.


0

Lấy đối tượng cửa sổ thông qua DI (Dependency Injection) không phải là một ý tưởng hay khi các biến toàn cục có thể truy cập được trong toàn bộ ứng dụng.

Nhưng nếu bạn không muốn sử dụng đối tượng cửa sổ thì bạn cũng có thể sử dụng selftừ khóa cũng trỏ đến đối tượng cửa sổ.


3
Đó không phải là lời khuyên tốt. Dependency Injection làm cho các lớp (thành phần, chỉ thị, dịch vụ, đường ống, ...) dễ kiểm tra hơn (ví dụ ngay cả khi không có trình duyệt) và dễ dàng sử dụng lại trên các nền tảng khác nhau như kết xuất phía máy chủ hoặc Web worker. Nó có thể hoạt động đối với một số người và sự đơn giản có thể có một số hấp dẫn, nhưng không khuyến khích sử dụng DI là IMHO một câu trả lời tồi.
Günter Zöchbauer,

Nếu bạn thực hiện kết xuất phía máy chủ, mã của bạn sẽ bị hỏng. Vì ở phía máy chủ, bạn không có bất kỳ đối tượng cửa sổ nào và bạn cần chèn mã của riêng mình.
Alex Nikulin,

-1

Hãy giữ nó đơn giản, các bạn!

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

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

Nếu bạn thực hiện kết xuất phía máy chủ, mã của bạn sẽ bị hỏng. Vì ở phía máy chủ, bạn không có bất kỳ đối tượng cửa sổ nào và bạn cần chèn mã của riêng mình.
Alex Nikulin,

-2

Trên thực tế, nó rất đơn giản để truy cập đối tượng cửa sổ ở đây là thành phần cơ bản của tôi và tôi đã thử nghiệm hoạt động của nó

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

}

Nếu bạn thực hiện kết xuất phía máy chủ, mã của bạn sẽ bị hỏng. Vì ở phía máy chủ, bạn không có bất kỳ đối tượng cửa sổ nào và bạn cần chèn mã của riêng mình.
Alex Nikulin,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.