Angular window resize event - javascript

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

Related

ngDestroy lifecycle is not Triggering in dynamically created Angular component

ngDestroy lifecycle method is not triggering for dynamically created component.
I'm creating multiple component dynamically using ComponentFactoryResolver.
In my dynamically created component I'm fetching some data from API and I'm periodically fetching the data for every 5 mins using setInterval method. and I'm clearing the Interval instance inside ngDestroy method , while redirecting to different page, the component's ngDestroy is not triggering and the API is triggering even the component is not in the view.
This is how I'm creating components dynamically.
const factory = this.resolver.resolveComponentFactory(DynamicComponent); // Component Construction
const ref = factory.create(this.injector);
Here is my DynamicComponent which has the functionalities
import { Component, OnInit, OnDestroy } from "#angular/core";
#Component({
selector: "app-dynamic,
templateUrl: "./dynamic.component.html",
styleUrls: ["./dynamic.component.scss"]
})
export class DynamicComponent implements OnInit, OnDestroy {
loopCount: number;
autoRefreshInterval: any;
constructor() {}
ngOnInit() {
this.fetchData();
this.startAutoRefreshLoop();
}
ngOnDestroy(): void {
console.log("Destroying loop"); // ngOnDestroy is not triggering
this.clearAutoRefreshLoop();
}
clearAutoRefreshLoop() {
clearInterval(this.autoRefreshInterval);
}
/*
function for starting the Automatically recall the service for certain period of time
*/
startAutoRefreshLoop() {
console.log("starting loop");
this.loopCount = 10 * 1000;
this.autoRefreshInterval = setInterval(() => {
this.fetchData();
}, this.loopCount);
}
fetchData() {
// FETCHING DATA FROM API CODE ....
}
}
You need to destroy the dynamically loaded component by manually calling : this.componentRef.destroy(); to trigger ngOndestroy()
Example :
import {
Component,
ViewChild,
ViewContainerRef,
ComponentFactoryResolver,
ComponentRef,
ComponentFactory
} from '#angular/core';
import { DynamicComponent } from './dynamic.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'app';
componentRef: any;
#ViewChild('container', { read: ViewContainerRef }) entry: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) { }
createComponent(message) {
this.entry.clear();
const factory = this.resolver.resolveComponentFactory(DynamicComponent);
this.componentRef = this.entry.createComponent(factory);
}
destroyComponent() {
this.componentRef.destroy(); // you need to call this
}
}
For more information : how-to-dynamically-create-a-component-in-angular

Initial counter value not displaying on ChangeDetectionPush strategy

I am writing a simple counter. It has start,stop, toggle functionality in parent (app) and displaying changed value in child (counter) component using ChangeDetectionStrategy.OnPush.
Issue I am facing is not able to display initial counter value in child component on load.
Below are screenshot and code.
app.component.ts
import { Component } from '#angular/core';
import {BehaviorSubject} from 'rxjs';
#Component({
selector: 'app-root',
template: `<h1>Change Detection</h1>
<button (click)="start()">Start</button>
<button (click)="stop()">Stop</button>
<button (click)="toggleCD()">Toggle CD</button>
<hr>
<counter [data]="data$" [notifier]="notifier$"></counter>`,
})
export class AppComponent {
_counter = 0;
_interval;
_cdEnabled = false;
data$ = new BehaviorSubject({counter: 0});
notifier$ = new BehaviorSubject(false);
start() {
if (!this._interval) {
this._interval = setInterval((() => {
this.data$.next({counter: ++this._counter});
}), 10);
}
}
stop() {
clearInterval(this._interval);
this._interval = null;
}
toggleCD(){
this._cdEnabled = !this._cdEnabled;
this.notifier$.next(this._cdEnabled);
}
}
counter.component.ts
import {Component, Input, ChangeDetectionStrategy, OnInit, ChangeDetectorRef} from '#angular/core';
import {Observable} from 'rxjs/index';
#Component({
selector: 'counter',
template: `Items: {{_data.counter}}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent implements OnInit {
#Input() data: Observable<any>;
#Input() notifier: Observable<boolean>;
_data: any;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
this.data.subscribe((value) => {
/**
Below this._data.counter is showing 0 in console.log but
not in template
**/
this._data = value;
this.cd.markForCheck();
});
this.cd.detach();
this.notifier.subscribe((value) => {
if (value) {
this.cd.reattach();
} else {
this.cd.detach();
}
});
}
}
I'm using Angular 6.1.0
your AppComponent data$ is a BehaviorSubject, which you have given an initial value. your CounterComponent data expects an Observable, which you subscribe to. The defaulted BehaviorSubject does not fire until it changes. to get the value you have to query it upon load:
#Input() data: BehaviorSubject<any>;
ngOnInit() {
this._data = this.data.value; // get the initial value from the subject
this.data.subscribe((value) => {
this._data = value;
this.cd.markForCheck();
}
);
should do the trick.

Triggering change detection when i use service communication

So I have two not related components and I'm trying to communicate between them using a service and a BehaviorSubject. Everything is cool, data is exchanged, but when i call the service from one of the components, it doesn't trigger change detection on the other component.
So to show what I'm talking about in code:
The service:
import {Injectable, Optional, EventEmitter} from '#angular/core';
import {Http} from '#angular/http';
import 'rxjs/add/operator/map';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { forEach } from '#angular/router/src/utils/collection';
#Injectable()
export class SbkService {
items: any = [];
private _itemsSource = new BehaviorSubject<any>(0);
items$ = this._itemsSource.asObservable();
constructor (
private _localStorageService: LocalStorageService
) {}
storeSelection(item) {
this.items.push(item);
this.setLocalStorage();
}
removeSelection(selectionId) {
for (var i = this.items.length-1; i >= 0; i--) {
if (this.items[i].selectionId == selectionId)
this.items.splice(i, 1);
}
this.setLocalStorage();
return true;
}
getLocalStorage() {
this.items = this._localStorageService.get('items');
this._itemsSource.next(this.items);
return this.items;
}
setLocalStorage() {
this._localStorageService.set('items', this.items);
this._itemsSource.next(this.items);
return true;
}
}
Component 1:
import { Component, OnInit } from '#angular/core';
import { SbkService } from '../../services/sbk.service'
import {Subscription} from 'rxjs/Subscription';
#Component({
selector: 'app-right-sidebar',
template: `<ul>
<li *ngFor="let selection of selections">
{{selection.name}}
<span class="cutom-btn" (click)="remove(selection.selectionId)">
delete
</span>
</li>
</ul>`,
styles: []
})
export class RightSidebarComponent implements OnInit {
selections: any = [];
subscription:Subscription;
constructor (
private _sbkService: SbkService
) {
}
ngOnInit() {
this.subscription = this._sbkService.items$
.subscribe(selections => {
this.selections = selections })
this._sbkService.getLocalStorage();
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
remove(selectionId) {
this._sbkService.removeSelection(selectionId);
}
}
Component 2:
import { Component, ViewChild, ElementRef } from '#angular/core';
import 'rxjs/add/operator/map';
import {forEach} from '#angular/router/src/utils/collection';
import {SbkService} from '../services/sbk.service'
#Component({
selector: 'app-match-table',
template: `
<div (click)="addItem('mumble', 1)">Add mumble</div>
<div (click)="addItem('ts', 2)">Add ts</div>
<div (click)="addItem('discord', 3)">Add discord</div>
`,
styles: []
})
export class MatchTableComponent {
constructor(
private _sbkService: SbkService
) {}
//Place a bet in the betslip
public addItem = (name, selectionId) => {
item: Object = {};
item.selectionId = selectionId;
item.name = name;
this._sbkService.storeSelection(item);
}
}
So, when I click on a div from component 2 (MatchTableComponent) it updates the selections array in component 1 (RightSideBarComponent) but doesn't trigger a change detection, so the sorted list doesn't get updated until i refresh the page. When i click on delete from RightSideBarComponent template, it updates the selections array and triggers the change detection.
How can I make this work? I tried subscribing to an event from SbkService in the AppComponent and from there triggering the setLocalStorage from SbkService, but no luck...
If I'm not wrong, you should set the next "sequence" on your Observable "items" through your BehaviourSubject.
Could you modify and try this?:
storeSelection(item){
const itemsAux = this._itemsSource.getValues();
itemsAux.push(item);
this._itemsSource.next(itemsAux);
}
setLocalStorage(){
this._localStorageService('items', this._itemsSource.getValues();
return true;
}

Toggling ngIf with separate components using service?

So I'm trying to toggle an element in a separate component, while firing the function to change the boolean value from the ionic tabs component so far I have this.
//App.module.ts - Cut down for the sake of brevity
import { AppGlobals } from './globals';
#NgModule({
providers: [AppGlobals]
})
//Globals.ts
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/startWith';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class AppGlobals {
// use this property for property binding
public showSearch:BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
setShowSearch(search){
this.showSearch.next(search);
console.log(search);
}
}
// Tabs.ts
import { AppGlobals } from '../../app/globals';
constructor(private _appGlobals: AppGlobals) {
this._appGlobals.showSearch.subscribe(value => this.search = value);
}
toggleSearch() {
this.search = !this.search;
console.log(this.search);
}
//Tabs.html
(ionSelect)="toggleSearch();"
//This is on some HTML within the the separate component
<div *ngIf="search" [ngClass]="{'slideInRight':showSearch, 'fadeOut':!showSearch}" class="search-filters animated">
However this doesn't appear to be working, I'm toggling the value however the global "showSearch" seems to stay the same. What is the correct way of achieving the toggle of the element across the two components?
Any help at all is very appreciated.
your Tabs.ts will emit an event when toggleSearch() method call
toggleSearch() {
this.search = !this.search;
console.log(this.search);
this._appGlobals.showSearch.next(this.search);
}
in another component
import { AppGlobals } from 'path/to/app/globals';
#Component({...})
export class SubComponent implements OnInit {
showSearch: boolean = false;
constructor(private _appGlobals: AppGlobals) {
}
ngOnInit() {
this._appGlobals.showSearch.subscribe(value => this.showSearch = value);
}
}
SubComponent template
<div *ngIf="showSearch">content</div>

How to write console.log wrapper for Angular2 in Typescript?

Is there a way to write a global selfmade mylogger function that I could use in Angular2 typescript project for my services or components instead of console.log function ?
My desired result would be something like this:
mylogger.ts
function mylogger(msg){
console.log(msg);
};
user.service.ts
import 'commons/mylogger';
export class UserService{
loadUserData(){
mylogger('About to get something');
return 'something';
};
};
You could write this as a service and then use dependency injection to make the class available to your components.
import {Injectable, provide} from 'angular2/core';
// do whatever you want for logging here, add methods for log levels etc.
#Injectable()
export class MyLogger {
public log(logMsg:string) {
console.log(logMsg);
}
}
export var LOGGING_PROVIDERS:Provider[] = [
provide(MyLogger, {useClass: MyLogger}),
];
You'll want to place this in the top level injector of your application by adding it to the providers array of bootstrap.
import {LOGGING_PROVIDERS} from './mylogger';
bootstrap(App, [LOGGING_PROVIDERS])
.catch(err => console.error(err));
A super simple example here: http://plnkr.co/edit/7qnBU2HFAGgGxkULuZCz?p=preview
The example given by the accepted answer will print logs from the logger class, MyLogger, instead of from the class that is actually logging.
I have modified the provided example to get logs to be printed from the exact line that calls MyLogger.log(), for example:
get debug() {
return console.debug.bind(console);
}
get log() {
return console.log.bind(console);
}
I found how to do it here: https://github.com/angular/angular/issues/5458
Plunker: http://plnkr.co/edit/0ldN08?p=preview
As per the docs in developers.mozilla,
The bind() method creates a new function that, when called, has its
this keyword set to the provided value, with a given sequence of
arguments preceding any provided when the new function is called.
More information about bind here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
If you want to use 'console.log' function just in your component you can do this:
import { Component, OnInit } from '#angular/core';
var output = console.log;
#Component({
selector: 'app-component',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
constructor() { }
ngOnInit() { }
printFunction(term: string): void {
output('foo');
}
}
How about using console on your main service, So we can customize and apply console.log conditionally:
myComponent.ts
export class myComponent implements OnInit {
constructor(
private config: GlobalService
) {}
ngOnInit() {
this.config.log('func name',{a:'aval'},'three');
}
}
global.service.ts
#Injectable()
export class GlobalService {
constructor() { }
this.prod = true;
public log(one: any, two?: any, three?: any, four?: any) {
if (!this.prod) {
console.log('%c'+one, 'background:red;color:#fff', two, three, four);
}
}
}
(Note: first parameter should be string in this example);
For toggling console.log ON\OFF:
logger.service.ts:
import { Injectable } from '#angular/core';
#Injectable()
export class LoggerService {
private oldConsoleLog = null;
enableLogger(){
if (this.oldConsoleLog == null) { return; }
window['console']['log'] = this.oldConsoleLog;
}
disableLogger() {
this.oldConsoleLog = console.log;
window['console']['log'] = function () { };
};
}
app.component.ts:
#Component({
selector: 'my-app',
template: `your templ;ate`
})
export class AppComponent {
constructor(private loggerService: LoggerService) {
var IS_PRODUCTION = true;
if ( IS_PRODUCTION ) {
console.log("LOGGER IS DISABBLED!!!");
loggerService.disableLogger();
}
}
}
I created a logger based on the provided information here
Its very basic (hacky :-) ) at the moment, but it keeps the line number
#Injectable()
export class LoggerProvider {
constructor() {
//inject what ever you want here
}
public getLogger(name: string) {
return {
get log() {
//Transform the arguments
//Color output as an example
let msg = '%c[' + name + ']';
for (let i = 0; i < arguments.length; i++) {
msg += arguments[i]
}
return console.log.bind(console, msg, 'color:blue');
}
}
}
}
Hope this helps
type safer(ish) version with angular 4, typescript 2.3
logger.service.ts
import { InjectionToken } from '#angular/core';
export type LoggerService = Pick<typeof console,
'debug' | 'error' | 'info' | 'log' | 'trace' | 'warn'>;
export const LOGGER_SERVICE = new InjectionToken('LOGGER_SERVICE');
export const ConsoleLoggerServiceProvider = { provide: LOGGER_SERVICE, useValue: console };
my.module.ts
// ...
#NgModule({
providers: [
ConsoleLoggerServiceProvider,
//...
],
// ...
my.service.ts
// ...
#Injectable()
export class MyService {
constructor(#Inject(LOGGER_SERVICE) log: LoggerService) {
//...
There is now an angular2 logger component on NPM which supports log levels.
https://www.npmjs.com/package/angular2-logger

Categories