I have updated my angular project to Angular v11 which successfully updated. This brought a warning that TSLint is deprecated and following the steps outlined Here - Migrating from Codelyzer and TSLint I managed to migrate this.
After this update, I received several errors which I have an idea of how to solve, except this one error
Error: Debug Failure. False expression: position cannot precede the beginning of the file
at computeLineOfPosition (C:\Path\to\Project\node_modules\typescript\lib\typescript.js:8934:22)
at computeLineAndCharacterOfPosition (C:\Path\to\Project\node_modules\typescript\lib\typescript.js:8912:26)
at Object.getLineAndCharacterOfPosition C:\Path\to\Project\node_modules\typescript\lib\typescript.js:8953:16)
at SourceFileObject.getLineAndCharacterOfPosition (C:\Path\to\Project\node_modules\typescript\lib\typescript.js:143309:23)
at preprocess (C:\Path\to\Project\node_modules\#angular-eslint\eslint-plugin-template\dist\index.js:1:1430)
at Linter._verifyWithProcessor (C:\Path\to\Project\node_modules\eslint\lib\linter\linter.js:1292:30)
at Linter._verifyWithConfigArray (C:\Path\to\Project\node_modules\eslint\lib\linter\linter.js:1264:25)
at Linter.verify (C:\Path\to\Project\node_modules\eslint\lib\linter\linter.js:1226:25)
at Linter.verifyAndFix (C:\Path\to\Project\node_modules\eslint\lib\linter\linter.js:1416:29)
at verifyText (C:\Path\to\Project\node_modules\eslint\lib\cli-engine\cli-engine.js:239:48)
preprocess: ERROR could not parse #Component() metadata C:\Path\to\Project\src\app\components\label-star-required\label-star-required.component.ts
Error: Debug Failure. False expression: position cannot precede the beginning of the file
at computeLineOfPosition (C:\Path\to\Project\node_modules\typescript\lib\typescript.js:8934:22)
at computeLineAndCharacterOfPosition (C:\Path\to\Project\node_modules\typescript\lib\typescript.js:8912:26)
at Object.getLineAndCharacterOfPosition (C:\Path\to\Project\node_modules\typescript\lib\typescript.js:8953:16)
at SourceFileObject.getLineAndCharacterOfPosition (C:\Path\to\Project\node_modules\typescript\lib\typescript.js:143309:23)
at preprocess (C:\Path\to\Project\node_modules\#angular-eslint\eslint-plugin-template\dist\index.js:1:1430)
at Linter._verifyWithProcessor (C:\Path\to\Project\node_modules\eslint\lib\linter\linter.js:1292:30)
at Linter._verifyWithConfigArray (C:\Path\to\Project\node_modules\eslint\lib\linter\linter.js:1264:25)
at Linter.verify (C:\Path\to\Project\node_modules\eslint\lib\linter\linter.js:1226:25)
at Linter.verifyAndFix (C:\Path\to\Project\node_modules\eslint\lib\linter\linter.js:1416:29)
at verifyText (C:\Path\to\Project\node_modules\eslint\lib\cli-engine\cli-engine.js:239:48)
preprocess: ERROR could not parse #Component() metadata C:\Path\to\Project\src\app\components\skip-link\skip-link.component.ts
Below are the two components throwing this error
label-star-required.component.ts
import {Component} from '#angular/core';
#Component({
selector: 'app-label-star-required',
templateUrl: './label-star-required.component.html',
styleUrls: ['./label-star-required.component.css']
})
export class LabelStarRequiredComponent {
constructor() {
}
}
#Component({
selector: 'app-star-required',
template: `
<span class='icon-star required'></span>
`,
styleUrls: ['./label-star-required.component.css']
})
export class StarRequiredComponent {
constructor() {
}
}
skip-link.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Router, NavigationStart } from '#angular/router';
import { filter, takeWhile, map } from 'rxjs/operators';
#Component({
selector: 'app-skip-link',
styleUrls: ['./skip-link.component.css'],
template: `
<a [href]="skipLinkPath" class="skip-main">Skip to main content</a>
`
})
export class SkipLinkComponent implements OnInit, OnDestroy {
skipLinkPath: string;
componentIsActive: boolean;
constructor(
private router: Router
) { }
ngOnInit() {
this.componentIsActive = true;
this.skipLinkPath = `${this.router.url.replace('#main', '')}#main`;
this.router.events.pipe(filter(event => event instanceof NavigationStart))
.pipe(takeWhile(() => this.componentIsActive))
.pipe(map(event => (event as any).url ))
.subscribe(url => {
if (!/(.)#main$/.test(url)) {
this.skipLinkPath = `${url}#main`;
}
});
}
ngOnDestroy() {
this.componentIsActive = false;
}
}
From the above two components, I can see that the common similarity is that both the files are using
inline template: . Might this be causing the error while running ng lint ? How do I solve this?
My workaround was to move the html code in the component template to templateUrl.
#Component({
selector: 'app-star-required',
template: `<span class='icon-star required'></span>`,
styleUrls: ['./label-star-required.component.css']
})
Becomes
#Component({
selector: 'app-star-required',
templateUrl: './label-star-required.component.html',
styleUrls: ['./label-star-required.component.css']
})
Seems to be a bug with ESlint/Angular configuration.
As a workaround, i added "endOfLine": "lf" to my prettier config which fixed the issue after reformatting the project.
See https://github.com/angular-eslint/angular-eslint/issues/185
I noticed this error too with templates that had escaped double quotes in them:
template: "<div class=\"my-class\"></div><ng-content></ng-content>"
I solved it by switching to single quotes:
template: "<div class='my-class'></div><ng-content></ng-content>"
I defined a property here in my function
evs: string
...
openArticle(url){
this.evs = url
console.log(this.evs)
this.navCtrl.navigateForward('/url-page')
}
And I a trying to pass the value of 'this.evs' to another ts file and use its value but I do not know how to do this. I tried exporting it like this.
export const webpage = this.evs
but this.evs has no value until someone performs the openArticle function ad so I keep getting the error. "Cannot read property 'evs' of undefined"
What i need to do is tranfer the variable to the 'url-page' page and use the value of this.evs only after the openArticle function has bee called. How do I go about this?
As per my understanding you are trying to share data between two components.
So choose one of them as per your requirements.
Parent to Child: Sharing Data via Input().
Child to Parent: Sharing Data via Output() and EventEmitter.
Unrelated Components: Sharing Data with a Service.
This link will be helpful.
If the components have a parent/child relationship, You can share data between them via #Inpput() and #Output() decorators.
Sharing data from Parent to Child using #Input() :
<h3>Parent Component</h3>
<label>Parent Component</label>c
<input type="number" [(ngModel)]='parentValue'/>
<p>Value of child component is: </p>
<app-child [value]='parentValue'></app-child>
And in the child component, the 'parentValue' can be received as :
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() value: number;
constructor() { }
ngOnInit() {
}
}
Now, in the case of sending data from Child to Parent, we can use an #Output() event emitter. So the parent would have a function to receive the emitted data from child as :
parent-app.component.html
<app-child [value]="parentValue" (childEvent)="childEvent($event)"></app-child>
parent-app.component.ts
childEvent(event) {
console.log(event);
}
And, the child.component.ts would look like :
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() PData: number;
#Output() childEvent = new EventEmitter();
constructor() { }
onChange(value) {
this.childEvent.emit(value);
}
ngOnInit() {
}
}
If the components do not have a parent/child relationship, a shared service can be used, say, SharedService which has a BehavioralSubject, that emits value from either component, and the other component can then catch the changed value.
Eg:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export class SharedService {
comp1Val: string;
_comp1ValueBS = new BehaviorSubject<string>('');
comp2Val: string;
_comp2ValueBS = new BehaviorSubject<string>('');
constructor() {
this.comp1Val;
this.comp2Val;
this._comp1ValueBS.next(this.comp1Val);
this._comp2ValueBS.next(this.comp2Val);
}
updateComp1Val(val) {
this.comp1Val = val;
this._comp1ValueBS.next(this.comp1Val);
}
updateComp2Val(val) {
this.comp2Val = val;
this._comp2ValueBS.next(this.comp2Val);
}
And component1 as follows :
import { Injectable } from '#angular/core';
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export class SharedService {
comp1Val: string;
_comp1ValueBS = new BehaviorSubject<string>('');
comp2Val: string;
_comp2ValueBS = new BehaviorSubject<string>('');
constructor() {
this.comp1Val;
this.comp2Val;
this._comp1ValueBS.next(this.comp1Val);
this._comp2ValueBS.next(this.comp2Val);
}
updateComp1Val(val) {
this.comp1Val = val;
this._comp1ValueBS.next(this.comp1Val);
}
updateComp2Val(val) {
this.comp2Val = val;
this._comp2ValueBS.next(this.comp2Val);
}
Component 2 :
import { Component, AfterContentChecked } from '#angular/core';
import { SharedService } from "../../common/shared.service";
#Component({
selector: 'app-component2',
templateUrl: './component2.component.html',
styleUrls: ['./component2.component.css']
})
export class Component2Component implements AfterContentChecked {
comp1Val: string;
comp2Val: string;
constructor(private sharedService: SharedService) {
this.sharedService.comp2Val = "Component 2 initial value";
}
ngAfterContentChecked() {
this.comp1Val = this.sharedService.comp1Val;
}
addValue(str) {
this.sharedService.updateComp2Val(str);
}
}
You can find more on different types of subjects here
Trying to do child to parent communication with #Output event emitter but is no working
here is the child component
import { Component, OnInit, Output, Input, EventEmitter } from '#angular/core';
#Component({
selector: 'app-emiter',
templateUrl: './emiter.component.html',
styleUrls: ['./emiter.component.css']
})
export class EmiterComponent implements OnInit {
#Output() emitor: EventEmitter<any>
constructor() { this.emitor = new EventEmitter()}
touchHere(){this.emitor.emit('Should Work');
console.log('<><><><>',this.emitor) // this comes empty
}
ngOnInit() {
}
}
this is the html template
<p>
<button (click)=" touchHere()" class="btn btn-success btn-block">touch</button>
</p>
The console.log inside the touchHere it shows nothing
even if I put this inside the parent component it show nothing as well
parent component
import { Component , OnInit} from '#angular/core';
// service I use for other stuff//
import { SenderService } from './sender.service';
// I dont know if I have to import this but did it just in case
import { EmiterComponent } from './emiter/emiter.component'
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
user: any;
touchThis(message: string) {
console.log('Not working: ${message}');
}
constructor(private mySessionService: SenderService) { }
}
and here is the html template
<div>
<app-emiter>(touchHere)='touchThis($event)'</app-emiter>
</div>
Parent component template:
<app-emitor (emitor)='touchThis($event)'></app-emiter>
In parent template #Output should be 'called', not the child method.
Also, see: https://angular.io/guide/component-interaction#parent-listens-for-child-event
Here’s an example of how we write a component that has outputs:
#Component({
selector: 'single-component',
template: `<button (click)="liked()">Like it?</button>`
})
class SingleComponent {
#Output() putRingOnIt: EventEmitter<string>;
constructor() {
this.putRingOnIt = new EventEmitter();
}
liked(): void {
this.putRingOnIt.emit("oh oh oh");
}
}
Notice that we did all three steps: 1. specified outputs, 2. created an EventEmitter that we attached
to the output property putRingOnIt and 3. Emitted an event when liked is called.
If we wanted to use this output in a parent component we could do something like this:
#Component({
selector: 'club',
template: `
<div>
<single-component
(putRingOnIt)="ringWasPlaced($event)"
></single-component>
</div>`
})
class ClubComponent {
ringWasPlaced(message: string) { console.log(`Put your hands up: ${message}`);
} }
// logged -> "Put your hands up: oh oh oh"
Again, notice that:
putRingOnIt comes from the outputs of SingleComponent
ringWasPlaced is a function on the ClubComponent
$event contains the thing that wasemitted, in this case a string
<app-emiter (emitor)="touchThis($event)" ></app-emiter>
By using #Output() you should apply the event you need to emit in the directive of the emitter component.Adding the name of the variable to the the directive and but the emitted over function inside the quotation passing the $event.
touchHere() is the method from which you are binding some value to emit with your EventEmitter. And your EventEmitter is 'emitor'.
So your code will work if you simply do the below:
<app-emiter (emitor)='touchThis($event)'></app-emiter>
I would like to perform some tasks based on the window re-size event (on load and dynamically).
Currently I have my DOM as follows:
<div id="Harbour">
<div id="Port" (window:resize)="onResize($event)" >
<router-outlet></router-outlet>
</div>
</div>
The event correctly fires
export class AppComponent {
onResize(event) {
console.log(event);
}
}
How do I retrieve the Width and Height from this event object?
Thanks.
<div (window:resize)="onResize($event)"
onResize(event) {
event.target.innerWidth;
}
or using the HostListener decorator:
#HostListener('window:resize', ['$event'])
onResize(event) {
event.target.innerWidth;
}
Supported global targets are window, document, and body.
Until https://github.com/angular/angular/issues/13248 is implemented in Angular it is better for performance to subscribe to DOM events imperatively and use RXJS to reduce the amount of events as shown in some of the other answers.
I know this was asked a long time ago, but there is a better way to do this now! I'm not sure if anyone will see this answer though. Obviously your imports:
import { fromEvent, Observable, Subscription } from "rxjs";
Then in your component:
resizeObservable$: Observable<Event>
resizeSubscription$: Subscription
ngOnInit() {
this.resizeObservable$ = fromEvent(window, 'resize')
this.resizeSubscription$ = this.resizeObservable$.subscribe( evt => {
console.log('event: ', evt)
})
}
Then be sure to unsubscribe on destroy!
ngOnDestroy() {
this.resizeSubscription$.unsubscribe()
}
#Günter's answer is correct. I just wanted to propose yet another method.
You could also add the host-binding inside the #Component()-decorator. You can put the event and desired function call in the host-metadata-property like so:
#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
}
}
The correct way to do this is to utilize the EventManager class to bind the event. This allows your code to work in alternative platforms, for example server side rendering with 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);
}
}
Usage in a component is as simple as adding this service as a provider to your app.module and then importing it in the constructor of a component.
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();
}
}
}
There's a ViewportRuler service in angular CDK. It runs outside of the zone, supports orientationchange and resize. It works with server side rendering too.
#Component({
selector: 'my-app',
template: `
<p>Viewport size: {{ width }} x {{ height }}</p>
`
})
export class AppComponent implements OnDestroy {
width: number;
height: number;
private readonly viewportChange = this.viewportRuler
.change(200)
.subscribe(() => this.ngZone.run(() => this.setSize()));
constructor(
private readonly viewportRuler: ViewportRuler,
private readonly ngZone: NgZone
) {
// Change happens well, on change. The first load is not a change, so we init the values here. (You can use `startWith` operator too.)
this.setSize();
}
// Never forget to unsubscribe!
ngOnDestroy() {
this.viewportChange.unsubscribe();
}
private setSize() {
const { width, height } = this.viewportRuler.getViewportSize();
this.width = width;
this.height = height;
}
}
Stackblitz example for ViewportRuler
The benefit is, that it limits change detection cycles (it will trigger only when you run the callback in the zone), while (window:resize) will trigger change detection every time it gets called.
Here is a better way to do it. Based on Birowsky's answer.
Step 1: Create an angular service with 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
};
};
Step 2: Inject the above service and subscribe to any of the Observables created within the service wherever you would like to receive the window resize event.
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
});
}
}
A sound understanding of Reactive Programming will always help in overcoming difficult problems. Hope this helps someone.
I haven't seen anyone talking about MediaMatcher of angular/cdk.
You can define a MediaQuery and attach a listener to it - then anywhere on your template (or ts) you can invoke stuff if the Matcher is matched.
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;
}
source: https://material.angular.io/components/sidenav/overview
Assuming that < 600px means mobile to you, you can use this observable and subscribe to it:
First we need the current window size. So we create an observable which only emits a single value: the current window size.
initial$ = Observable.of(window.innerWidth > 599 ? false : true);
Then we need to create another observable, so that we know when the window size was changed. For this we can use the "fromEvent" operator. To learn more about rxjs`s operators please visit: rxjs
resize$ = Observable.fromEvent(window, 'resize').map((event: any) => {
return event.target.innerWidth > 599 ? false : true;
});
Merg these two streams to receive our observable:
mobile$ = Observable.merge(this.resize$, this.initial$).distinctUntilChanged();
Now you can subscribe to it like this:
mobile$.subscribe((event) => { console.log(event); });
Remember to unsubscribe :)
I checked most of these answers. then decided to check out Angular documentation on Layout.
Angular has its own Observer for detecting different sizes and it is easy to implement into the component or a Service.
a simpl example would be:
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();
}
});
}
}
hope it helps
If you want just one event after the resize is finished, it's better to use RxJS with debounceTime :
debounceTime: Discard emitted values that take less than the specified time between output.
He waits > 0.5s between 2 events emitted before running the code.
In simpler terms, it waits for the resizing to be finished before executing the next code.
// RxJS v6+
import { fromEvent } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';
...
const resize$ = fromEvent(window, 'resize');
resize$
.pipe(
map((i: any) => i),
debounceTime(500) // He waits > 0.5s between 2 events emitted before running the next.
)
.subscribe((event) => {
console.log('resize is finished');
});
StackBlitz
Based on the solution of #cgatian I would suggest the following simplification:
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
}));
}
}
Usage:
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) { }
}
This is not exactly answer for the question but it can help somebody who needs to detect size changes on any element.
I have created a library that adds resized event to any element - Angular Resize Event.
It internally uses ResizeSensor from CSS Element Queries.
Example usage
HTML
<div (resized)="onResized($event)"></div>
TypeScript
#Component({...})
class MyComponent {
width: number;
height: number;
onResized(event: ResizedEvent): void {
this.width = event.newWidth;
this.height = event.newHeight;
}
}
I wrote this lib to find once component boundary size change (resize) in Angular, may this help other people. You may put it on the root component, will do the same thing as window resize.
Step 1: Import the module
import { BoundSensorModule } from 'angular-bound-sensor';
#NgModule({
(...)
imports: [
BoundSensorModule,
],
})
export class AppModule { }
Step 2: Add the directive like below
<simple-component boundSensor></simple-component>
Step 3: Receive the boundary size details
import { HostListener } from '#angular/core';
#Component({
selector: 'simple-component'
(...)
})
class SimpleComponent {
#HostListener('resize', ['$event'])
onResize(event) {
console.log(event.detail);
}
}
Below code lets observe any size change for any given div in Angular.
<div #observed-div>
</div>
then in the Component:
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;
}
On Angular2 (2.1.0) I use ngZone to capture the screen change event.
Take a look on the example:
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);
});
};
}
I hope this help!
Here is an update to #GiridharKamik answer above with the latest version of Rxjs.
import { Injectable } from '#angular/core';
import { Observable, BehaviorSubject, fromEvent } from 'rxjs';
import { pluck, distinctUntilChanged, map } from 'rxjs/operators';
#Injectable()
export class WindowService {
height$: Observable<number>;
constructor() {
const windowSize$ = new BehaviorSubject(getWindowSize());
this.height$ = windowSize$.pipe(pluck('height'), distinctUntilChanged());
fromEvent(window, 'resize').pipe(map(getWindowSize))
.subscribe(windowSize$);
}
}
function getWindowSize() {
return {
height: window.innerHeight
//you can sense other parameters here
};
};
Here is a simple and clean solution I created so I could inject it into multiple components.
ResizeService.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ResizeService {
constructor() {
window.addEventListener('resize', (e) => {
this.onResize.next();
});
}
public onResize = new Subject();
}
In use:
constructor(
private resizeService: ResizeService
) {
this.subscriptions.push(this.resizeService.onResize.subscribe(() => {
// Do stuff
}));
}
private subscriptions: Subscription[] = [];
What I did is as follows, much like what Johannes Hoppe suggested:
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',
event => this.onResize$.emit({
width: event.target.innerWidth,
height: event.target.innerHeight
}));
}
getWindowSize(){
this.onResize$.emit({
width: window.innerWidth,
height: window.innerHeight
});
}
}
In app.component.ts:
Import { ResizeService } from ".shared/services/resize.service"
import { Component } from "#angular/core"
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent{
windowSize: {width: number, height: number};
constructor(private resizeService: ResizeService){
}
ngOnInit(){
this.resizeService.onResize$.subscribe((value) => {
this.windowSize = value;
});
this.resizeService.getWindowSize();
}
}
Then in your app.component.html:
<router-outlet *ngIf = "windowSize?.width > 1280 && windowSize?.height > 700; else errorComponent">
</router-outlet>
<ng-template #errorComponent>
<app-error-component></app-error-component>
</ng-template>
Another approach that I took was
import {Component, OnInit} from '#angular/core';
import {fromEvent} from "rxjs";
import {debounceTime, map, startWith} from "rxjs/operators";
function windowSizeObserver(dTime = 300) {
return fromEvent(window, 'resize').pipe(
debounceTime(dTime),
map(event => {
const window = event.target as Window;
return {width: window.innerWidth, height: window.innerHeight}
}),
startWith({width: window.innerWidth, height: window.innerHeight})
);
}
#Component({
selector: 'app-root',
template: `
<h2>Window Size</h2>
<div>
<span>Height: {{(windowSize$ | async)?.height}}</span>
<span>Width: {{(windowSize$ | async)?.width}}</span>
</div>
`
})
export class WindowSizeTestComponent {
windowSize$ = windowSizeObserver();
}
here the windowSizeObserver can be reused in any component