Zdarzenie zmiany rozmiaru okna kątowego

232

Chciałbym wykonać niektóre zadania na podstawie zdarzenia zmiany rozmiaru okna (przy obciążeniu i dynamicznie).

Obecnie mam swój DOM w następujący sposób:

<div id="Harbour">
    <div id="Port" (window:resize)="onResize($event)" >
        <router-outlet></router-outlet>
    </div>
</div>

Zdarzenie poprawnie się uruchamia

export class AppComponent {
   onResize(event) {
        console.log(event);
    }
}

Jak pobrać szerokość i wysokość z tego obiektu zdarzenia?

Dzięki.

DanAbdn
źródło
6
Nie do końca Angularowe pytanie. spójrz na obiekt okna . Możesz dostać to innerHeighti innerWidthwłaściwości ..
Sasxa
1
@Sasxa ma rację, po prostu musisz to zrobićconsole.log(event.target.innerWidth )
Pankaj Parkar
Dzięki za informację Sasxa / Pankaj - nie byłem pewien, czy to zwykła javascript, czy maszynopis, czy wydarzenie Angular. Wspinam się tutaj po bardzo stromej krzywej uczenia się i doceniam twój wkład.
DanAbdn

Odpowiedzi:

526
<div (window:resize)="onResize($event)"
onResize(event) {
  event.target.innerWidth;
}

lub używając dekoratora HostListener :

@HostListener('window:resize', ['$event'])
onResize(event) {
  event.target.innerWidth;
}

Obsługiwane są globalne cele window, documentoraz body.

Dopóki https://github.com/angular/angular/issues/13248 nie zostanie zaimplementowany w Angular, lepiej jest wydajnie subskrybować zdarzenia DOM i używać RXJS w celu zmniejszenia liczby zdarzeń, jak pokazano w niektórych innych odpowiedziach.

Günter Zöchbauer
źródło
15
Czy istnieje dokumentacja dotycząca używanej składni: window: resize ?
Klemens,
3
Dokładnie. Możesz użyć document, windowi body github.com/angular/angular/angular/blob/…
Günter Zöchbauer
5
idealna odpowiedź. Uważam, że @HostListener to czystszy sposób :), ale najpierw importuj HostListener przy użyciuimport { Component, OnInit, HostListener } from '@angular/core';
Gaurav Sharma
4
Szybka wskazówka: jeśli chcesz wyzwolić także przy pierwszym obciążeniu, zaimplementuj ngAfterViewInit z @ angular / core. angular.io/api/core/AfterViewInit
Zymotik
7
Na pewno jednak dla tych, którzy chcą wiedzieć, jak działa HostListener z debounceTime, skorzystaj z linku poniżej plnkr.co/edit/3J0dcDaLTJBxkzo8Akyg?p=preview
jcdsr
65

@ Odpowiedź Güntera jest poprawna. Chciałem tylko zaproponować inną metodę.

Możesz także dodać powiązanie hosta wewnątrz @Component()-decoratora. Możesz umieścić zdarzenie i pożądane wywołanie funkcji we właściwości host-metadata-w następujący sposób:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  host: {
    '(window:resize)': 'onResize($event)'
  }
})
export class AppComponent{
   onResize(event){
     event.target.innerWidth; // window width
   }
}
Jan
źródło
2
Wypróbowałem każdą metodę na tej stronie i wydaje się, że żadna z nich nie działa. Czy jest na to oficjalna dokumentacja?
Pomidor
Jaka jest wysokość rzutni uzyskana przy obciążeniu?
Giridhar Karnik
@GiridharKarnik, czy próbowałeś zrobić window.innerWidthwnętrze ngOnInitlub ngAfterViewInitmetody?
Jan
@Tomato Referencje API można znaleźć tutaj Wiele referencji jest generowanych automatycznie (przypuszczam) i brakuje w nich przykładów lub szczegółowych wyjaśnień. Niektóre referencje API mają wiele przykładów. Nie znalazłem jednak konkretnego przykładu z dokumentów na ten temat. Może jest gdzieś ukryty: P
Jan
1
tutaj jest odniesienie do tego, jak jest używany
Anand Rockzz
52

Wiem, że zapytano o to dawno temu, ale jest na to lepszy sposób! Nie jestem jednak pewien, czy ktokolwiek zobaczy tę odpowiedź. Oczywiście twój import:

import { fromEvent, Observable, Subscription } from "rxjs";

Następnie w swoim komponencie:

resizeObservable$: Observable<Event>
resizeSubscription$: Subscription

ngOnInit() {
    this.resizeObservable$ = fromEvent(window, 'resize')
    this.resizeSubscription$ = this.resizeObservable$.subscribe( evt => {
      console.log('event: ', evt)
    })
}

Następnie zrezygnuj z subskrypcji na zniszcz!

ngOnDestroy() {
    this.resizeSubscription$.unsubscribe()
}
Chris Stanley
źródło
Jedyny sposób, który zadziałał dla mnie. Dzięki!! :-) ... Musiałem tylko dostosować import. Być może moje rxjs jest nowsze:import { fromEvent, Observable,Subscription } from "rxjs";
Jette
Gdzie mogę w tym dodać debounce (1000)?
Deepak Thomas
3
Przepraszamy za późną odpowiedź: aby dodać debounce, będziesz chciał użyćthis.resizeSubscription$ = this.resizeObservable$.pipe(debounceTime(1000)).subscribe( evt => { console.log('event: ', evt) })
Chris Stanley
41

Prawidłowym sposobem na to jest użycie klasy EventManager do powiązania zdarzenia. Pozwala to na działanie kodu na alternatywnych platformach, na przykład renderowanie po stronie serwera za pomocą Angular Universal.

import { EventManager } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Injectable } from '@angular/core';

@Injectable()
export class ResizeService {

  get onResize$(): Observable<Window> {
    return this.resizeSubject.asObservable();
  }

  private resizeSubject: Subject<Window>;

  constructor(private eventManager: EventManager) {
    this.resizeSubject = new Subject();
    this.eventManager.addGlobalEventListener('window', 'resize', this.onResize.bind(this));
  }

  private onResize(event: UIEvent) {
    this.resizeSubject.next(<Window>event.target);
  }
}

Użycie w komponencie jest tak proste, jak dodanie tej usługi jako dostawcy do modułu app.module, a następnie zaimportowanie jej do konstruktora komponentu.

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

@Component({
  selector: 'my-component',
  template: ``,
  styles: [``]
})
export class MyComponent implements OnInit {

  private resizeSubscription: Subscription;

  constructor(private resizeService: ResizeService) { }

  ngOnInit() {
    this.resizeSubscription = this.resizeService.onResize$
      .subscribe(size => console.log(size));
  }

  ngOnDestroy() {
    if (this.resizeSubscription) {
      this.resizeSubscription.unsubscribe();
    }
  }
}
Cgatian
źródło
O ile mi wiadomo, to nigdy nie działa na urządzeniach mobilnych. Jak uzyskać początkowy rozmiar okna dzięki takiemu podejściu?
user3170530,
Mobile nie ma windowobiektu, tak jak serwer nie ma window. Nie jestem zaznajomiony z różnymi konfiguracjami mobilnymi, ale powinieneś być w stanie łatwo dostosować powyższy kod, aby połączyć się z właściwym globalnym odbiornikiem zdarzeń
cgatian
@cgatian Jestem noobem, ale wydaje się, że to właściwa odpowiedź. Niestety nie mam szczęścia, aby moja subskrypcja Zaloguj się do komponentu. Czy możesz dodać do odpowiedzi, jak zasubskrybować to w komponencie, aby można było zobaczyć aktualizacje?
Matthew Harwood,
@cgatian zrobię plunker, ale to chyba nie działa. Filtr w serwisie wydaje się dziwny, ponieważ stackoverflow.com/q/46397531/1191635
Matthew Harwood
3
@cgatian Mobile does not have a window object.... dlaczego twoim zdaniem przeglądarki mobilne nie mają obiektu okna?
Drenai
31

Oto lepszy sposób, aby to zrobić. Na podstawie odpowiedzi Birowsky'ego .

Krok 1: Utwórz angular servicez RxJS Observables .

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';

@Injectable()
export class WindowService {
    height$: Observable<number>;
    //create more Observables as and when needed for various properties
    hello: string = "Hello";
    constructor() {
        let windowSize$ = new BehaviorSubject(getWindowSize());

        this.height$ = (windowSize$.pluck('height') as Observable<number>).distinctUntilChanged();

        Observable.fromEvent(window, 'resize')
            .map(getWindowSize)
            .subscribe(windowSize$);
    }

}

function getWindowSize() {
    return {
        height: window.innerHeight
        //you can sense other parameters here
    };
};

Krok 2: Wstrzyknij powyższe servicei subskrybuj dowolne z Observablesutworzonych w usłudze miejsc, w których chcesz otrzymywać zdarzenia zmiany rozmiaru okna.

import { Component } from '@angular/core';
//import service
import { WindowService } from '../Services/window.service';

@Component({
    selector: 'pm-app',
    templateUrl: './componentTemplates/app.component.html',
    providers: [WindowService]
})
export class AppComponent { 

    constructor(private windowService: WindowService) {

        //subscribe to the window resize event
        windowService.height$.subscribe((value:any) => {
            //Do whatever you want with the value.
            //You can also subscribe to other observables of the service
        });
    }

}

Dobre zrozumienie programowania reaktywnego zawsze pomoże w przezwyciężeniu trudnych problemów. Mam nadzieję, że to komuś pomoże.

Giridhar Karnik
źródło
Uważam, że to błąd: this.height $ = (windowSize $ .pluck ('height') as Observable <number>) .distinctUntilChanged (); Observable <number>) .distinctUntilChanged (); wygląda na to, że wkleiłeś dwa razy z rzędu w odrębnymUntilChanged ()
Zuriel
Nie zrozumiałem cię, proszę o wypracowanie.
Giridhar Karnik
Wykrywanie zmian nie zostanie uruchomione, gdy robisz to wydarzenie poza Angularem, uwierz, że o to mu chodziło.
Mark Pieszak - Trilon.io
1
Miałem błąd informujący, że „typ” nie istnieje w typie BehaviourSubject. Zmiana kodu na this.height $ = windowSize $ .map (x => x.height) działało dla mnie.
Matt Sugden,
@GiridharKamik Czy możesz podać rozwiązanie, które subskrybowałoby szerokość i wysokość w tym samym czasie, które moglibyśmy zasubskrybować zarówno szerokość, jak i wysokość w 1 prostym subskrybowaniu
ghiscoding
11

Nie widziałem nikogo mówić o MediaMatcherod angular/cdk.

Możesz zdefiniować MediaQuery i dołączyć do niego detektor - wtedy w dowolnym miejscu szablonu (lub ts) możesz wywoływać różne rzeczy, jeśli Matcher jest dopasowany. LiveExample

App.Component.ts

import {Component, ChangeDetectorRef} from '@angular/core';
import {MediaMatcher} from '@angular/cdk/layout';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  mobileQuery: MediaQueryList;

  constructor(changeDetectorRef: ChangeDetectorRef, media: MediaMatcher) {
    this.mobileQuery = media.matchMedia('(max-width: 600px)');
    this._mobileQueryListener = () => changeDetectorRef.detectChanges();
    this.mobileQuery.addListener(this._mobileQueryListener);
  }

  private _mobileQueryListener: () => void;

  ngOnDestroy() {
    this.mobileQuery.removeListener(this._mobileQueryListener);
  }

}

App.Component.Html

<div [class]="mobileQuery.matches ? 'text-red' : 'text-blue'"> I turn red on mobile mode 
</div>

App.Component.css

.text-red { 
   color: red;
}

.text-blue {
   color: blue;
}

źródło: https://material.angular.io/components/sidenav/overview

Stavm
źródło
6

Zakładając, że <600px oznacza dla Ciebie mobilny, możesz użyć tego obserwowalnego i zasubskrybować:

Najpierw potrzebujemy bieżącego rozmiaru okna. Tworzymy więc obserwowalny, który emituje tylko jedną wartość: bieżący rozmiar okna.

initial$ = Observable.of(window.innerWidth > 599 ? false : true);

Następnie musimy utworzyć kolejny obserwowalny, abyśmy wiedzieli, kiedy zmienił się rozmiar okna. W tym celu możemy użyć operatora „fromEvent”. Aby dowiedzieć się więcej o operatorach rxjs, odwiedź: rxjs

resize$ = Observable.fromEvent(window, 'resize').map((event: any) => {
  return event.target.innerWidth > 599 ? false : true;
 });

Połącz te dwa strumienie, aby otrzymać nasz obserwowalny:

mobile$ = Observable.merge(this.resize$, this.initial$).distinctUntilChanged();

Teraz możesz subskrybować to w ten sposób:

mobile$.subscribe((event) => { console.log(event); });

Pamiętaj, aby wypisać się :)

Flosut Mözil
źródło
5

W kątowym CDK znajduje się usługa ViewportRuler . Działa poza strefą i działa również z renderowaniem po stronie serwera.

Totati
źródło
2
to powinna być zaakceptowana odpowiedź. Ma wszystko, czego potrzeba + jest to wbudowana funkcjonalność CDK.
Deniss M.
3

W oparciu o rozwiązanie @cgatian sugerowałbym następujące uproszczenie:

import { EventManager } from '@angular/platform-browser';
import { Injectable, EventEmitter } from '@angular/core';

@Injectable()
export class ResizeService {

  public onResize$ = new EventEmitter<{ width: number; height: number; }>();

  constructor(eventManager: EventManager) {
    eventManager.addGlobalEventListener('window', 'resize',
      e => this.onResize$.emit({
        width: e.target.innerWidth,
        height: e.target.innerHeight
      }));
  }
}

Stosowanie:

import { Component } from '@angular/core';
import { ResizeService } from './resize-service';

@Component({
  selector: 'my-component',
  template: `{{ rs.onResize$ | async | json }}`
})
export class MyComponent {
  constructor(private rs: ResizeService) { }
}
Johannes Hoppe
źródło
Znalazłem to jako najlepsze rozwiązanie, jednak działa to tylko przy zmianie rozmiaru okna, ale nie przy ładowaniu lub zmianie routera. Czy wiesz, jak zastosować przy zmianie routera, załadować ponownie lub załadować?
jcdsr
Możesz dodać funkcję do usługi i uruchomić ją w komponencie @jcdsr: getScreenSize () {this.onResize $ .emit ({width: window.innerWidth, height: window.innerHeight}); }
DanielWaw
3

To nie jest dokładnie odpowiedź na pytanie, ale może pomóc komuś, kto musi wykryć zmiany rozmiaru dowolnego elementu.

Utworzyłem bibliotekę, która dodaje resizedzdarzenie do dowolnego elementu - Angular Resize Event .

Wykorzystuje wewnętrznie ResizeSensorz zapytań o elementy CSS .

Przykładowe użycie

HTML

<div (resized)="onResized($event)"></div>

Maszynopis

@Component({...})
class MyComponent {
  width: number;
  height: number;

  onResized(event: ResizedEvent): void {
    this.width = event.newWidth;
    this.height = event.newHeight;
  }
}
Martin Volek
źródło
jeśli chcesz wykryć zmiany rozmiaru okna, to Angular Material Breakpoint Observer obsługuje już material.angular.io/cdk/layout/overview
Darren Street
Ale to nie wykrywa tylko zmiany rozmiaru okien, ale zmiany rozmiaru dowolnego elementu.
Martin Volek
2

Napisałem tę bibliotekę, aby znaleźć raz zmianę rozmiaru granicy komponentu (zmianę rozmiaru) w Angular, może to pomóc innym ludziom. Możesz umieścić go na komponencie głównym, zrobi to samo, co zmiana rozmiaru okna.

Krok 1: Zaimportuj moduł

import { BoundSensorModule } from 'angular-bound-sensor';

@NgModule({
  (...)
  imports: [
    BoundSensorModule,
  ],
})
export class AppModule { }

Krok 2: Dodaj dyrektywę jak poniżej

<simple-component boundSensor></simple-component>

Krok 3: Odbierz szczegóły rozmiaru granicy

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

@Component({
  selector: 'simple-component'
  (...)
})
class SimpleComponent {
  @HostListener('resize', ['$event'])
  onResize(event) {
    console.log(event.detail);
  }
}
MODLITWA
źródło
1

W Angular2 (2.1.0) używam ngZone do przechwytywania zdarzenia zmiany ekranu.

Spójrz na przykład:

import { Component, NgZone } from '@angular/core';//import ngZone library
...
//capture screen changed inside constructor
constructor(private ngZone: NgZone) {
    window.onresize = (e) =>
    {
        ngZone.run(() => {
            console.log(window.innerWidth);
            console.log(window.innerHeight);
        });
    };
}

Mam nadzieję, że ta pomoc!

Anh Hoang
źródło
1

Poniższy kod pozwala zaobserwować dowolną zmianę rozmiaru dla dowolnego div w Angular.

<div #observed-div>
</div>

następnie w komponencie:

oldWidth = 0;
oldHeight = 0;

@ViewChild('observed-div') myDiv: ElementRef;
ngAfterViewChecked() {
  const newWidth = this.myDiv.nativeElement.offsetWidth;
  const newHeight = this.myDiv.nativeElement.offsetHeight;
  if (this.oldWidth !== newWidth || this.oldHeight !== newHeight)
    console.log('resized!');

  this.oldWidth = newWidth;
  this.oldHeight = newHeight;
}
abedfar
źródło
Chcę zmienić varibale z counter = 6 na count = 0 przy max-width 767px, jak to zrobić?
Thanveer Shah,
0

wszystkie rozwiązania nie działają po stronie serwera lub w Angular Universal

jcdsr
źródło
0

Sprawdziłem większość tych odpowiedzi. następnie postanowiłem sprawdzić dokumentację Angular dotyczącą układu .

Angular ma własnego obserwatora do wykrywania różnych rozmiarów i można go łatwo wdrożyć do komponentu lub usługi.

prostym przykładem byłoby:

import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';

@Component({...})
class MyComponent {
  constructor(breakpointObserver: BreakpointObserver) {
    breakpointObserver.observe([
      Breakpoints.HandsetLandscape,
      Breakpoints.HandsetPortrait
    ]).subscribe(result => {
      if (result.matches) {
        this.activateHandsetLayout();
      }
    });
  }
}

mam nadzieję, że to pomoże

Mj Jameel
źródło