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


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.