Як вставити вікно в службу?


111

Я пишу сервіс Angular 2 у TypeScript, який використовуватиму localstorage. Я хочу , щоб ввести посилання на браузер windowоб'єкта в моїй служби , так як я не хочу , щоб посилатися на які - або глобальні змінні , як Кутове 1.x $window.

Як це зробити?

Відповіді:


135

Наразі це працює для мене (2018-03, кутовий 5.2 з AoT, випробуваний у angular-cli та спеціальна збірка веб-пакетів):

По-перше, створіть ін'єкційну службу, яка надає посилання на вікно:

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

Тепер зареєструйте цю службу у вашому root 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подібні способи до цієї послуги, якщо ви використовуєте їх у своїй програмі.


редагувати: Оновлено пропозицією Трушенца. edit2: Оновлено для кутового 2.1.2 редагування3: Додано примітки 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

З випуском кутового 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 замість рядка "Вікно"

Редагувати:

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


19

Ви можете просто ввести його після встановлення постачальника:

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

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

але коли я змінюю window.varвміст сторінки, не змінюється
Ravinder Payal

6
Це не спрацювало в Safari, оскільки Window не ін'єкційний. Мені довелося створити власний тип Injectable, який містив потрібні мені властивості Window. Кращий підхід , можливо, були створити службу , як описано в інших відповідях
daveb

Такий підхід не працює, оскільки useValue фактично створює копію вказаного вами значення. Дивіться: github.com/angular/angular/isissue/10788#issuecomment-300614425 . Цей підхід спрацював би, якщо ви змінили його на useFactory і повернули значення з зворотного виклику.
Леві Ліндсі

18

Ви можете отримати вікно з введеного документа.

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

Щоб змусити його працювати на 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 наступні роботи, що є комбінацією деяких з вищезазначених відповідей, у вашому root 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 { }

і щойно визначений у моєму імпорті головного 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

Станом на сьогодні (квітень 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;
    }
}

6

Кутовий 4 вводить InjectToken, і вони також створюють маркер для документа під назвою ДОКУМЕНТ . Я думаю, що це офіційне рішення, і воно працює в AoT.

Я використовую ту саму логіку, щоб створити невелику бібліотеку під назвою ngx-window-token, щоб не робити цього знову і знову.

Я використовував його в іншому проекті і без проблем будував AoT.

Ось як я використовував його в іншій упаковці

Ось плункер

У вашому модулі

imports: [ BrowserModule, WindowTokenModule ] У вашому компоненті

constructor(@Inject(WINDOW) _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і використовувати його як DOKUMENT маркер для ін'єкцій, наданий Angular?
Sparker73

Так, це все є.
waterplea

Так. Це прекрасно працює, резервуари для цього простого рішення.
Sparker73



3

Я знаю, питання полягає в тому, як вставити об’єкт вікна в компонент, але ви робите це лише для того, щоб дістатися до localStorage. Якщо ви справді просто хочете localStorage, чому б не скористатись службою, яка виставляє саме це, як h5webstorage . Потім компонент опише його реальні залежності, що зробить ваш код більш читабельним.


2
Хоча це посилання може відповісти на питання, краще включити сюди суттєві частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо пов’язана сторінка зміниться.
Усі працівники найважливіші.

3

Це найкоротший / найчистіший відповідь, який я знайшов, працюючи з Angular 4 AOT

Джерело: https://github.com/angular/angular/isissue/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як необов'язкове. За кутовими документами:

Документ може бути недоступним у контексті програми, коли контексти програми та візуалізації не однакові (наприклад, під час запуску програми у веб-роботодавця).

Ось приклад використання значка, 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 (Dependency Injection) не є хорошою ідеєю, коли глобальні змінні доступні у всій програмі.

Але якщо ви не хочете використовувати об'єкт вікна, то ви також можете використовувати selfключове слово, яке також вказує на об’єкт вікна.


3
Це непогана порада. Dependency Injection полегшує тестування класів (компонентів, директив, служб, труб, ...) (наприклад, навіть без браузера) і легше використовувати їх на різних платформах, таких як візуалізація на стороні сервера або веб-робітників. Це може спрацювати для деяких, і простота може мати певну привабливість, але відмовляти від використання DI - IMHO - це погана відповідь.
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.