서비스에 창을 삽입하는 방법은 무엇입니까?


111

TypeScript에서 localstorage. windowAngular 1.x와 같은 전역 변수를 참조하고 싶지 않기 때문에 브라우저 개체에 대한 참조를 내 서비스 에 삽입하고 싶습니다 $window.

어떻게하나요?

답변:


135

이것은 현재 나를 위해 일하고 있습니다 (2018-03, AoT가있는 angular 5.2, 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();
    }
}

이제 해당 서비스를 루트 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응용 프로그램에서 사용하는 경우 유사한 방식으로이 서비스에 및 기타 전역 을 추가 할 수도 있습니다 .


편집 : Truchainz 제안으로 업데이트되었습니다. edit2 : angular 2.1.2에 대해 업데이트 됨 edit3 : AoT 노트 추가 edit4 : any유형 해결 방법 note 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)이 작업을 수행하려면
제임스 Kleeh에게

1
angular.io/docs/ts/latest/guide/... . 내 나쁜 오,주의 깊게 읽어 보지 않았
Teedeez

2
@Brian 예, 여전히 액세스 window중이지만 그 사이에 서비스를 사용하면 window단위 테스트에서 기본 항목을 스터 빙 할 수 있으며 SSR에 대해 언급 했듯이 서버에 대한 mock / noop 창을 노출하는 대체 서비스를 제공 할 수 있습니다. AOT를 언급 한 이유는 Angular가 업데이트 될 때 AOT에서 창을 래핑하는 초기 솔루션 중 일부입니다.
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


19

제공자를 설정 한 후에 주입 할 수 있습니다.

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

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

하지만 window.var페이지 내용을 변경해도 변경되지 않습니다
Ravinder Payal

6
Window가 Injectable이 아니므로 Safari에서는 작동하지 않았습니다. 필요한 Window 속성이 포함 된 Injectable 유형을 직접 만들어야했습니다. 더 나은 접근 방식은 다른 답변에 설명 된대로 서비스를 만드는 것이었을 수 있습니다
daveb

useValue는 실제로 사용자가 제공 한 값의 복사본을 만들기 때문에이 방법은 작동하지 않습니다. 참조 : github.com/angular/angular/issues/10788#issuecomment-300614425 . 이 접근 방식은 useFactory를 사용하고 콜백에서 값을 반환하도록 변경 한 경우 작동합니다.
Levi Lindsey

19

삽입 된 문서에서 창을 가져올 수 있습니다.

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에서는 위의 답변 중 일부가 결합 된 다음 작업이 루트 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 유형의 가정 된 전역 변수로 선언하기 때문입니다.

응용 프로그램의 모든 곳에서 창에 액세스하는 것은 권장하지 않습니다. 필요한 창 속성에 액세스 / 수정하는 서비스를 만들고 구성 요소에 해당 서비스를 삽입하여 창으로 할 수있는 작업을 수정하지 않고 범위를 지정해야합니다. 전체 창 개체.


서버 측 렌더링을 수행하면 코드가 손상됩니다. srver 측에는 창 개체가 없으므로 직접 삽입해야합니다.
Alex Nikulin

9

'Window'문자열에 OpaqueToken 을 사용 했습니다 .

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_PROVIDERSAngular 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 { }

내 메인의 imports 속성에 정의되어 있습니다. 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 년 4 월) 현재 이전 솔루션의 코드가 작동하지 않습니다. 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

다음 defaultViewDOCUMENT내장 토큰 에서 가져 와서 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: anyAngular에서 제공하는 DOCUMENT 주입 토큰처럼 사용합니까?
Sparker73

예, 그게 전부입니다.
waterplea

예. 이 간단한 솔루션을 위해 완벽하게 작동합니다.
Sparker73

4

할 정도로 충분합니다

export class AppWindow extends Window {} 

그리고

{ provide: 'AppWindow', useValue: window } 

AOT를 행복하게 만들기 위해


4

문서를 통해 창 개체에 직접 액세스 할 수있는 기회가 있습니다.

document.defaultView == window

3

질문은 구성 요소에 창 개체를 주입하는 방법이지만 localStorage에 도달하기 위해 이것을 수행하는 것입니다. 정말로 localStorage를 원한다면 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

Angular 4에서 NgZone을 사용할 수 있습니다.

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

constructor(private zone: NgZone) {}

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

2

DOCUMENT를 선택 사항 으로 표시하는 것도 좋은 생각 입니다. Angular 문서에 따라 :

애플리케이션과 렌더링 컨텍스트가 동일하지 않은 경우 (예 : 애플리케이션을 웹 작업자로 실행하는 경우) 애플리케이션 컨텍스트에서 문서를 사용할 수 없습니다.

다음 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가리키는 키워드 도 사용할 수 있습니다 .


4
좋은 조언이 아닙니다. 종속성 주입을 사용하면 클래스 (구성 요소, 지시문, 서비스, 파이프 등)를보다 쉽게 ​​테스트 할 수 있고 (예 : 브라우저 없이도) 서버 측 렌더링 또는 웹 작업자와 같은 다른 플랫폼에서 쉽게 재사용 할 수 있습니다. 일부에게는 효과가있을 수 있고 단순함은 매력이있을 수 있지만 DI 사용을 권장하지 않는 것은 IMHO가 잘못된 대답입니다.
Günter Zö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.