Как вставить окно в сервис?


111

Я пишу сервис Angular 2 на TypeScript, который будет использовать localstorage. Я хочу , чтобы ввести ссылку на браузер windowобъекта в моей службы , так как я не хочу , чтобы ссылаться на какие - либо глобальные переменные , как Угловое 1.x $window.

Как я могу это сделать?

Ответы:


135

Это работает для меня в настоящее время (2018-03, angular 5.2 с AoT, протестировано в angular-cli и настраиваемой сборке веб-пакета):

Сначала создайте инъекционный сервис, который предоставляет ссылку на window:

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и другие глобальные переменные к этой службе аналогичным образом, если вы используете их в своем приложении.


edit: Обновлено с предложением Truchainz. edit2: Обновлено для angular 2.1.2 edit3: Добавлены примечания AoT edit4: Добавление anyпримечания обходного пути edit5: Обновленное решение для использования WindowRefService, которое исправляет ошибку, которую я получал при использовании предыдущего решения с другой сборкой edit6: добавление примера настраиваемого набора окон


1
Наличие @Inject в параметрах конструктора вызывало у меня кучу ошибок, таких как ORIGINAL EXCEPTION: No provider for Window!. Однако его удаление устранило проблему для меня. Мне было достаточно использовать только первые 2 глобальные строки.
TrieuNomad

Интересно ^^ Я должен попробовать это в пару более демонстрационных проектов - без @Injectя получаю No provider for Windowошибки. Это очень хорошо, что не требует руководства @Inject!
elwyn

На 2.1.2 мне пришлось использовать, @Inject(Window)чтобы это работало
Джеймс Клих

1
angular.io/docs/ts/latest/guide/… . О, мой плохой, не читал внимательно
Teedeez

2
@Brian: да, он все еще получает доступ 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содержимое страницы, не меняется
Равиндер Пайал

6
Это не работало в Safari, так как Window не является инъекционным. Мне пришлось создать свой собственный Injectable тип, который содержал необходимые мне свойства Window. Возможно, лучшим подходом было создание службы, как описано в других ответах
daveb

Этот подход не работает, потому что useValue фактически создает копию значения, которое вы ему предоставляете. См: github.com/angular/angular/issues/10788#issuecomment-300614425 . Этот подход будет работать, если вы измените его на useFactory и вернете значение из обратного вызова.
Леви Линдси

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 следующие работы, которые представляют собой комбинацию некоторых из приведенных выше ответов, в вашем корневом приложении app.ts добавьте его в поставщиков:

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

Затем в вашем сервисе и т. Д. Введите его в конструктор

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

10

Перед объявлением @Component это тоже можно сделать,

declare var window: any;

Компилятор фактически позволит вам получить доступ к глобальной переменной окна, поскольку вы объявляете ее как предполагаемую глобальную переменную с типом any.

Я бы не стал предлагать доступ к окну везде в вашем приложении, однако вам следует создать службы, которые обращаются / изменяют необходимые атрибуты окна (и внедряют эти службы в ваши компоненты), чтобы определить, что вы можете делать с окном, не позволяя им изменять объект всего окна.


Если вы выполняете рендеринг на стороне сервера, ваш код будет сломан, потому что на стороне сервера у вас нет оконного объекта, и вам нужно внедрить свой собственный.
Алекс Никулин

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в начальной загрузке в 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 { }

и только что определен в свойстве import моего основного 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

Angular 4 представляет InjectToken, а также создает токен для документа под названием DOCUMENT. . Я думаю, что это официальное решение, и оно работает в 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встроенного токена и проверять его на 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
Итак, я помещаю это в папку своих поставщиков (например), а затем использую в конструкторе своего компонента этот токен инъекции? @Inject(WINDOW) private _window: anyи использовать его как токен внедрения DOCUMENT, предоставленный Angular?
Sparker73,

Да, вот и все.
waterplea

Ага. Он отлично работает, танки для этого простого решения.
Sparker73,



3

Я знаю, что вопрос в том, как внедрить объект окна в компонент, но вы делаете это, кажется, только для того, чтобы добраться до localStorage. Если вам действительно нужен только localStorage, почему бы не использовать службу, которая предоставляет именно это, например h5webstorage . Затем ваш компонент опишет свои реальные зависимости, что сделает ваш код более читабельным.


2
Хотя эта ссылка может дать ответ на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если ссылка на страницу изменится.
All Workers Are Essential

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как необязательный. Согласно документации Angular:

Документ может быть недоступен в контексте приложения, если контексты приложения и визуализации не совпадают (например, при запуске приложения в 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-window-token . Я делал нечто подобное, но переключился на ваше. Это моя служба для прослушивания событий изменения размера окна и уведомления подписчиков.

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 (внедрение зависимостей) не является хорошей идеей, когда глобальные переменные доступны во всем приложении.

Но если вы не хотите использовать объект окна, вы также можете использовать selfключевое слово, которое также указывает на объект окна.


4
Это плохой совет. Внедрение зависимостей упрощает тестирование классов (компонентов, директив, служб, каналов и т. Д.) (Например, даже без браузера) и упрощает их повторное использование на различных платформах, таких как рендеринг на стороне сервера или веб-воркеры. Это может сработать для некоторых, и простота может иметь некоторую привлекательность, но препятствовать использованию DI - ИМХО плохой ответ.
Günter Zöchbauer

Если вы выполняете рендеринг на стороне сервера, ваш код будет сломан, потому что на стороне сервера у вас нет оконного объекта, и вам нужно внедрить свой собственный.
Алекс Никулин

-1

Будьте проще, ребята!

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

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

Если вы выполняете рендеринг на стороне сервера, ваш код будет сломан, потому что на стороне сервера у вас нет оконного объекта, и вам нужно внедрить свой собственный.
Алекс Никулин

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

}

Если вы выполняете рендеринг на стороне сервера, ваш код будет сломан, потому что на стороне сервера у вас нет оконного объекта, и вам нужно внедрить свой собственный.
Алекс Никулин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.