I am programming an Angular2 application and I want to handle the back and forward button from the browser.
The idea is to write in the console a message on "back" click and different message on "forward" click.
I used this code:
import { Location } from '#angular/common';
export class AppComponent implements OnInit {
constructor(private location: Location) {}
ngOnInit() {
this.location.subscribe(x => { [custom message] });
}
}
The Problem is: I cannot recognize the click if it is back or forward to write the correct message in the console.
How can I check it in angular2? I don't know what should I exactly search on google. All the answers were about to handle the event but to differentiate it.
P.S. it works for me if it is in javascript.
Thank you.
I needed to differentiate between back and forward buttons in Angular (5) also, in my case for animating route transitions. I couldn't find a solution, so here's one I came up with.
Add the following to a service.
private popState: boolean = false;
private urlHistory: string[] = [];
private popSubject: Subject<boolean> = new Subject();
constructor(private location: Location, private router: Router) {
location.subscribe(event => {
let fwd = false;
if (this.urlHistory.lastIndexOf(event.url)) {
this.urlHistory.push(event.url);
}
else {
fwd = true;
this.urlHistory.pop();
}
this.popState = true;
this.popSubject.next(fwd);
})
router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
if (!this.popState)
this.urlHistory = [this.router.url];
this.popState = false;
}
})
}
get popEvent$() {
return this.popSubject.asObservable();
}
Imports:
import { Location } from '#angular/common';
import { NavigationEnd, Router } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
Remove popSubject and popEvent$ if you don't need to listen for the event externally.
Related
I'm having this isue only when i deploy to android (didn't tried on iOS), if i run ionic serve to view if all is fine, console doesn't throw me any error and working fine!, but when i try it in android, i have the issue:
Cannot read property 'next' of undefined
ok, let's make a description, first of all, i'm using the integrated ionic starter template side_menu, i have made some style modifications, (but only that), so let's view each component needed to reproduce this issue:
app.component.html:
here i only put an ionDidClose event in the ion-menu element, (other things in the document are not neccesary at all):
<ion-menu class = "shorted_menu" (ionDidClose) = "setDisplayMenuInfo('basicInfo')">
<!--More code here...-->
</ion-menu>
in app.component.ts i have:
import { Component } from '#angular/core';
import { Platform } from '#ionic/angular';
import { SplashScreen } from '#ionic-native/splash-screen/ngx';
import { StatusBar } from '#ionic-native/status-bar/ngx';
import { enableProdMode } from '#angular/core';
import { MenuController } from '#ionic/angular';
import { Storage } from '#ionic/storage';
import { Router} from '#angular/router';
import { OneSignal } from '#ionic-native/onesignal/ngx';
import { AppPages } from '../global_modules/app_pages';
import { showInfo, setComponentBehaviorSubject } from '../global_modules/menu_functionality';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
enableProdMode();
#Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
public appPages;
public showInfo;
public currentDisplayMenu = 'basicInfo';
public behaviorSubject: BehaviorSubject<string>;
public behaviorSubjectObservable: Observable<any>;
public behaviorSubjectSubscription: Subscription;
public setComponentBehaviorSubject;
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
private oneSignal: OneSignal,
private storage: Storage,
private router: Router,
public menu: MenuController
)
{
this.initializeApp();
}
async setup(){
this.appPages = AppPages.pages;
this.showInfo = showInfo;
this.setComponentBehaviorSubject = setComponentBehaviorSubject;
this.userData = await this.getUserData();
this.userAvatar = this.userData.data.avatar_src.src && this.userData.data.avatar_src.src != "" ?
`${this.imagesRoute}${this.userData.data.avatar_src.src}`:
this.userAvatar;
this.behaviorSubject = new BehaviorSubject<string>('basicInfo');
this.behaviorSubjectObservable = this.behaviorSubject.asObservable();
this.setComponentBehaviorSubject(this.behaviorSubject);
this.behaviorSubjectSubscription = this.behaviorSubjectObservable.subscribe(data =>{
this.currentDisplayMenu = data;
});
}
ionViewWillLeave(){
this.behaviorSubjectSubscription.unsubscribe();
}
setDisplayMenuInfo(str){
this.showInfo(str);
}
async getUserData(){
return await this.storage.get('session_storage');
}
clearUserSession(){
this.oneSignal.removeExternalUserId();
this.storage.set('session_storage', null);
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleLightContent();
this.splashScreen.hide();
this.setup();
if(this.platform.is('cordova')){
this.setupPush();
}
this.storage.get('session_storage').then((res)=>{
if(res == null){
this.router.navigateByUrl('log-in');
}
});
});
}
setupPush(){
this.oneSignal.startInit(oneSignalData.appId, oneSignalData.googleProjectNumber);
this.oneSignal.inFocusDisplaying(this.oneSignal.OSInFocusDisplayOption.Notification);
this.oneSignal.handleNotificationReceived().subscribe(data => {
//Do something when notification received...
/*let msg = data.payload.body;
let title = data.payload.title;
let additionalData = data.payload.additionalData;*/
});
this.oneSignal.handleNotificationOpened().subscribe(data => {
let additionalData = data.notification.payload.additionalData;
//Actions to take when notification opened...
});
this.getUserData().then(res =>{
if(res && res.data.username != "" && res.data.type_user != ""){
this.oneSignal.setExternalUserId(`${res.data.username};${res.data.type_user}`);
}else{
this.oneSignal.removeExternalUserId();
}
})
this.oneSignal.endInit();
}
}
where AppPages is simply another class imported by app.component.ts and inside i having the public static property pages, this is simply an array of objects, and inside each object i have some menu display properties, like the title and icon of each menu item, also, having another property, called detailTag, this tag allows me to easily show or hide some content of the menu depending to the property:
currentDisplayMenu
of app.component.ts, so some menu info is clasified by 'basicInfo' inside detailTag property, this means that when currentDisplayMenu have content 'basicInfo', i will only show the menu items that have the detailTag matching by 'basicInfo', the other clasification is 'accountInfo'.
Well, other file that is needed is menu_functionality.ts, which looks like:
import { BehaviorSubject } from 'rxjs';
var componentBehaviorSubject:BehaviorSubject<string>;
export function showInfo(tag){
switch(tag){
case 'basicInfo':
return componentBehaviorSubject.next('basicInfo');
break;
case 'accountInfo':
let CBS = componentBehaviorSubject.next('accountInfo');
this.menu.open();
return CBS;
break;
}
}
export function setComponentBehaviorSubject(behaviorSubject:BehaviorSubject<string>){
componentBehaviorSubject = behaviorSubject;
}
The reason of why i'm calling the this.menu.open(); function only when the tag is 'accountInfo', is because, the app.component.html and app.component.ts are working globaly in the app and if the user clicks only in the sidemenu button to open it there is no problem and no need to put it, but here is the thing and the real reason of why i'm using BehaviorSubject:
App.component.html and app.component.ts are shared, so there are no problems if the user hits in the menu opener button in one or other page of the app, but... the way i'm showing other info like 'accountInfo' is using an avatar which is not inside of app.component.html (is not shared) i wanted to put in another place instead of putting it in the menu, this is because i didn't know how to make it globally usable like app.component.html, so i have to write that html in each page.html that i want to use it.
and when user hits the avatar i need to only show the 'accountInfo' items in the menu and obviously open it, (this is why i only call this.menu.open when the tag is accountInfo), i don't know if there is another better way to make this.
I am new to Angular and Ionic. I am looping through an array of content that is store in my Firestore database. When the app recompiles and loads, then I go to the settings page (that's where the loop is happening), I see the array of content just fine. I can update it on Firestore and it will update in real time in the app. It's all good here. But if I click "Back" (because Settings is being visited using "navPush"), then click on the Settings page again, the whole loop content will be gone.
Stuff is still in the database just fine. I have to recompile the project to make the content appear again. But once again, as soon as I leave that settings page, and come back, the content will be gone.
Here's my code:
HTML Settings page (main code for the loop):
<ion-list>
<ion-item *ngFor="let setting of settings">
<ion-icon item-start color="light-grey" name="archive"></ion-icon>
<ion-label>{{ setting.name }}</ion-label>
<ion-toggle (ionChange)="onToggle($event, setting)" [checked]="setting.state"></ion-toggle>
</ion-item>
</ion-list>
That Settings page TS file:
import { Settings } from './../../../models/settings';
import { DashboardSettingsService } from './../../../services/settings';
import { Component, OnInit } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
#IonicPage()
#Component({
selector: 'page-dashboard-settings',
templateUrl: 'dashboard-settings.html',
})
export class DashboardSettingsPage implements OnInit {
settings: Settings[];
checkStateToggle: boolean;
checkedSetting: Settings;
constructor(public dashboardSettingsService: DashboardSettingsService) {
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
}
onToggle(event, setting: Settings) {
this.dashboardSettingsService.setBackground(setting);
}
}
And my Settings Service file (the DashboardSettingsService import):
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return this.settings;
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}
Any idea what is causing that?
My loop was in a custom component before, so I tried putting it directly in the Dashboard Settings Page, but it's still not working. I have no idea what to check here. I tried putting the :
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
})
...part in an ngOninit method instead, or even ionViewWillLoad, and others, but it's not working either.
I am using Ionic latest version (3+) and same for Angular (5)
Thank you!
From the Code you posted i have observed two findings that might be the potential cause for the issue ,
Calling of the Service method in the constructor :
When your setting component is created , then that constructor will be called but but if you were relying on properties or data from child components actions to take place like navigating to the Setting page so move your constructor to any of the life cycle hooks.
ngAfterContentInit() {
// Component content has been initialized
}
ngAfterContentChecked() {
// Component content has been Checked
}
ngAfterViewInit() {
// Component views are initialized
}
ngAfterViewChecked() {
// Component views have been checked
}
Even though you add your service calling method in the life cycle events but it will be called only once as you were subscribing your service method in the constructor of the Settings service file . so just try to change your service file as follows :
getSettings() {
this.settingsCollection = this.afs.collection('settings');
this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
Update :
Try to change the Getsettings as follows and please do update your question with the latest changes
getSettings() {
this.settingsCollection = this.afs.collection('settings');
return this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
I'm not certain, but I suspect the subscription to the settings observable settings: Observable<Settings[]> could be to blame. This may work on the first load because the DashboardSettingsService is being created and injected, therefore loading the settings, and then emitting an item (causing your subscription event in DashboardSettingsPage to fire).
On the second page load, DashboardSettingsService already exists (services are created as singletons by default) - this means that the constructor does not get called (which is where you set up your observable) and therefore it does not emit a new settings object for your component.
Because the Observable does not emit anything, the following event will not be fired, meaning your local settings object is never populated:
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
You could refactor your service with a method that provides the latest (cached) settings object, or a new Observable (dont forget to unsubscribe!!), rather than creating a single Observable which will only be triggered by creation or changes to the underlying storage object.
Here's a simple example that doesnt change your method signature.
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
cachedSettings: Settings[];
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
this.cachedSettings = data;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return Observable.of(this.cachedSettings);
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}
I'm using a third-party library that requires me to implement my own event listener. This is done by implementing window.onGoogleYoloLoad = function() { ... }. I tried to implement it like this in my user service file:
#Injectable()
export class UserService {
public userCredentials = new EventEmitter<Credentials>();
constructor(){
window.onGoogleYoloLoad = function(credentials){
this.userCredentials.emit(credentials);
}
}
}
Then I subscribed to the event. The subscribers do get notified, but the view does not get updated. It's like angular doesn't know the event happened.
The callback is running outside the Angular zone. Move the callback to a component and call ChangeDetectorRef.detectChanges
import { Component, ChangeDetectorRef } from '#angular/core';
#Component(...)
export class MyComponent {
public userCredentials = new EventEmitter<Credentials>();
constructor(
private cd: ChangeDetectorRef,
private userService: UserService
){
window.onGoogleYoloLoad = function(credentials){
this.userService.userCredentials.emit(credentials);
this.cd.detectChanges();
}
}
}
Re-entering the Angular zone is another option: What's the difference between markForCheck() and detectChanges()
import { Injectable, NgZone } from '#angular/core';
#Injectable()
export class UserService {
public userCredentials = new EventEmitter<Credentials>();
constructor(private zone: NgZone){
window.onGoogleYoloLoad = function(credentials){
this.zone.run(() => {
this.userCredentials.emit(credentials);
})
}
}
}
I'm currently getting started with Angular 2 and got stuck on something probably pretty simple:
I have a shared service chatMessageService.ts:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class ChatMessageService {
private messageList = new BehaviorSubject<string>("");
currentMessage = this.messageList.asObservable();
constructor() {
}
public addMessage(msg:string) {
this.messageList.next(msg) }
}
The service is imported by two components, one that calls it's addMessage function to add the message to the Observable and then my chatComponent.ts looks like this (shortened fpr convinience):
import { Component } from '#angular/core';
import { Message } from './message';
import { ChatMessageService } from './chatMessage.service';
#Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css']
})
export class ChatComponent {
conversation: Message[] = [];
//.....
constructor(private chatMessageService: ChatMessageService) { }
addUserMessage(message) {
this.conversation.push({
content: message
});
}
ngOnInit() {
this.chatMessageService.currentMessage.subscribe(message => {this.addUserMessage(message);} )
}
}
My crisis arises at that last subscripion part. When I replace
{this.addUserMessage(message);}
with
{console.log(message)}
the message is printed out perfectly fine. If I call the addUserMessage()-method manually it works just fine. But when I call the method right there, with the message as argument, nothing happens. The method isn't even executed?
Thankful for your insights!
It looks like you need some buffering in the service.
Instead of BehaviorSubject, try
private messageList = new ReplaySubject<string>(10);
See working example: Plunker
I know, this question may sound duplicate and I have tried everything found on stackover flow unable to resolve this problem, so please bear with me
To make you able to reproduce the error I am providing you the whole code thought this
Github Repo
Problem
I am getting the following error:
Provider parse errors:↵Cannot instantiate cyclic dependency!
InjectionToken_HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule
in ./AppModule#-1:-1
Information about the scenario (Notes)
Note 1
File: response-interceptor.service.ts
Path: ./src/app/shared/interceptors/response-interceptor/
I am intercepting the HTTPClient responses to check the 401 error and when the error comes I need to ask user to re-login.
To show the re-login prompt to user I have made a global-functions-services that has a function 'relogin'
Note 2
File: global-function.service.ts
Path: ./src/app/shared/services/helper-services/global-function/
Here is the place where this all started to happen...
As soon as I am injecting the PersonService
constructor(
public dialog: MatDialog,
private _personService: PersonService
) { }
I am getting this error and in PersonService I cannot find any import that can cause the issue.
PersonService:
./src/app/shared/services/api-services/person/person.service.ts
import { IRequest } from './../../../interfaces/I-request';
import { environment } from 'environments/environment';
import { Injectable } from '#angular/core';
// for service
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/toPromise';
// models
import { Person } from 'app/shared/models/person';
import { RequestFactoryService, REQUEST_TYPES } from 'app/shared/factories/request-factory/request-factory.service';
#Injectable()
export class PersonService {
private _requestService: IRequest;
constructor(
_requestFactoryService: RequestFactoryService
) {
this._requestService = _requestFactoryService.getStorage(REQUEST_TYPES.HTTP);
}
public signup(record): Promise<Person> {
let url = environment.api + 'person/public/signup';
return this._requestService.post(url, record) as Promise<Person>;;
}
public authenticate(code: string, password: string): Promise<Person> {
let url = environment.api + 'auth/signin';
let postData = {
code: code,
password: password
}
return this._requestService.post(url, postData) as Promise<Person>;
}
}
Request
Please suggest a solution for this, I have already wasted 2 days to figure out the issue but no luck.
Many thanks!! in advance
Cyclic dependency, means circling around endless, like planets orbiting sun..
Solution: Break the dependency chain, Re-factor code.
You have GlobalFunctionService -> PersonService -> so on... -> ResponseInterceptorService -> and back to -> GlobalFunctionService.
Cycle complete.
REMOVE the PersonService dependency from GlobalFunctionService. (its not used anyway, if you need it then find different way to get around.)
import { PersonService } from 'app/shared/services/api-services/person/person.service';
import { Injectable } from '#angular/core';
import { InputModalComponent } from 'app/shared/components/input-modal/input-modal.component';
import { MatDialog } from '#angular/material';
#Injectable()
export class GlobalFunctionService {
constructor(
public dialog: MatDialog
) { }
relogin(): void {
let dialogRef = this.dialog.open(InputModalComponent, {
width: '250px',
data: { title: "Please provide your password to re-login." }
});
dialogRef.afterClosed().subscribe(result => {
debugger
console.log('The dialog was closed');
let password = result;
});
}
}
Use setTimeout() function in constructor to assign service.
constructor(private injector: Injector) {
setTimeout(() => {
this.loginService = this.injector.get(LoginService);
})
}
Try this and revert back if you face any issue.
You have to modify your response-interceptor.service.ts
import { Injectable,Inject, Injector } from '#angular/core';
constructor( inj: Injector) {
this._globalFunctionService=inj.get(GlobalFunctionService)
}
You can get more info From this link