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.
Related
I want to get my image from my firebase storage when I enter the user-profile.
I've tried something for myself but that doesn't work.
upload.service.ts:
import { Injectable, Inject } from '#angular/core';
import { AngularFireStorage } from '#angular/fire/storage';
import firebase from 'firebase/app';
import { User, NgAuthService } from '../auth/auth.service';
#Injectable({
providedIn: 'root',
})
export class UploadService {
file: File;
url = '';
constructor(private afStorage: AngularFireStorage, #Inject(NgAuthService) private user: User) { }
iUser = this.user.uid;
basePath = `/uploads/images/${this.iUser}`;
//method to upload file at firebase storage
async uploadFile(event: any) {
this.file = event.files[0]
const filePath = `${this.basePath}/${this.file.name}`; //path at which image will be stored in the firebase storage
const snap = await this.afStorage.upload(filePath, this.file);
if (this.file) {
this.getUrl(snap);
} else {
console.log("Select a image please.")
}
this.getUrl(snap);
}
//method to retrieve download url
async getUrl(snap: firebase.storage.UploadTaskSnapshot) {
await snap.ref.getDownloadURL().then(url => {
this.url = url; //store the URL
console.log(this.url);
});
}
}
user-profile.component.ts:
import { Component, OnInit } from '#angular/core';
import { UploadService } from '../../storage/upload.service';
import { AngularFireStorage } from '#angular/fire/storage';
import firebase from 'firebase/app';
#Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.scss'],
})
export class UserProfileComponent implements OnInit {
constructor(
public uploadService: UploadService,
public afStorage: AngularFireStorage,
) { }
image = this.uploadService.url;
ngOnInit() {
this.uploadService.getUrl();
}
}
No idea how I'm supposed to do it. I wanted to do it with the private method from my service file but it wants me to provide a parameter that I don't have in my component file. I then tried to just call I can try and provide a StackBlitz for this.
Link to StakBlitz: https://stackblitz.com/edit/angular-ivy-34jf1q?
One great way to get asynchronous data on page load is to use Resolvers, which allow you to call api calls and retrieve data before the route navigation completes so that when you arrive all your data is there.
Unfortunately, I'm not familiar enough with firebase to know about getting the 'snap' argument. It may be worth looking into AngularFire to make things simpler (maybe?)
Best of luck!
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 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.
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 have a PermissionService, which provide user roles. At the server-side data will not be uploaded if the user is not corresponds on role. The back-end is written with asp.net web api, which will use attributes to secure data. On upload page will be static upload user roles, the idea is just to show or hide elements on page which depending from user role.
The PermissionsService check avaiable role in its array. There are methods like isSeller(), isManager(). And all what i want is to provide accessibility from each view. For now i have this implementation.
permission.service
import { Injectable } from "#angular/core";
export enum Roles {
Admin,
Manager,
Moderator,
Seller
}
interface IPermissionDictionary {
[key: string]: boolean;
}
#Injectable()
export class PermissionService {
private permissions: IPermissionDictionary = {};
public constructor() {
this.emitPermissions();
}
private emitPermissions(): void {
let selector = document.querySelectorAll("#roles > span");
let availableRoles = Array.from(selector).map(element => element.textContent);
for (let role in Roles) {
if (!/^\d+$/.test(role)) { // for strings types in Roles
this.permissions[role] = availableRoles.indexOf(role) > -1;
}
}
}
public isInRole(role: string): boolean {
return this.permissions[role];
}
public isAdmin() {
return this.isInRole(Roles[Roles.Admin]);
}
public isSeller() {
return this.isInRole(Roles[Roles.Seller]);
}
public isManager() {
return this.isInRole(Roles[Roles.Manager]);
}
public isModerator() {
return this.isInRole(Roles[Roles.Moderator]);
}
}
app.component
import { Component } from "#angular/core";
import { ROUTER_DIRECTIVES } from "#angular/router";
import { PermissionService } from "./share/permission.service";
import { HomeComponent } from "./home/home.component";
import { OrderComponent } from "./order/order.component";
#Component({
selector: "admin-panel",
templateUrl: "../app/app.template.html",
directives: [ROUTER_DIRECTIVES],
precompile: [HomeComponent, OrderComponent]
})
export class AppComponent {
constructor(private permissionService: PermissionService) {
}
}
main.ts
import { bootstrap } from "#angular/platform-browser-dynamic";
import { AppComponent } from "./app.component";
import { APP_ROUTES_PROVIDER } from "./app.routes";
import { HTTP_PROVIDERS } from '#angular/http';
import { PermissionService } from "./share/permission.service";
bootstrap(AppComponent, [APP_ROUTES_PROVIDER, HTTP_PROVIDERS, PermissionService]);
For now to access the method of PermissionService need to inject it in component constructor. And in template is is use like
<div *ngIf="permissionService.isAdmin()">will show if you are admin</div>
But every time to inject my service in each component where i want to use it seems for me strange. And i just want to get access it from every part of my app like:
<div *ngIf="isAdmin()">will show if you are admin</div>
I think the person who asked this question has another version of Angular2 (perhaps a pre-release?), but in the latest version if you need to export a service for all the app you do it in the following way.
First, in your main.ts you must have a class that you bootstrap, like this:
platformBrowserDynamic().bootstrapModule(AppModule);
In this class "AppModule" (or whatever it is in your case), you should be able to add a global service provider in this way:
...
import {GlobalService} from './global-service.service'
#NgModule({
...
providers: [MyGlobalService],
...
})
export class AppModule{ ...}
In this way MyGlobalService is available for all other components.
Hopefully this will be useful to someone :).
Some option could be to create top level super class with the permission methods and then just subclass in view .ts. Not sure if this suits you as you still need to import super class into your components and extend it. It can also violate the "is-a".