I'm trying to do the following:
I have an object like this:
let demo_obj = {
park_name: "Park #01",
assets:
[
{
id: 1,
name: "Test #01"
},
{
id: 2,
name: "Test #02"
},
{
id: 3,
name: "Test #03"
}
],
}
A page loops through the assets objects with an *ngFor listing each asset as a list item.
<ion-list>
<ion-item *ngFor="let asset of demo_obj.assets" (click)="gotoAsset(asset)">
<ion-avatar item-left>
<img [src]="asset.cover_image_public_path">
</ion-avatar>
<h2>{{asset.nome}}</h2>
</ion-item>
</ion-list>
When the user clicks the button the following code gets executed:
gotoAsset(asset) {
this.navCtrl.push(AssetPage, { asset: asset });
}
This pushes a page displaying the asset details.
When the new page gets on the stack it fetches the asset from the nav params and it makes a backup copy of it stringifying it.
ngOnInit() {
this.asset = this.navParams.get("asset");
this.original_asset = JSON.stringify(this.asset);
}
The user here, can change several properties on the asset. But when he tries to go back without saving changes the app asks the user with a confirmation alert if he is sure to exit loosing changes:
ionViewCanLeave(): boolean {
if (JSON.stringify(this.asset) === this.original_asset) {
return true;
}
if (this.can_go_back) {
return true;
}
this.askForLeaveConfirmation();
return false;
}
private askForLeaveConfirmation() {
let confirm = this.alertCtrl.create({
title: 'Do you confirm?',
message: 'Leaving the page without saving, will reset the resource at its original state. Are you sure?',
buttons: [
{
text: 'Disagree',
handler: () => {
console.log('Disagree clicked');
}
},
{
text: 'Agree',
handler: () => {
this.asset = JSON.parse(this.original_asset);
this.can_go_back = true;
this.navCtrl.pop();
console.log('Agree clicked');
}
}
]
});
confirm.present();
}
At this point if he chooses Agree, the resource gets reseted. All works fine, in fact you can see in a small fraction of time the resource getting reseted before the animation start.
The weird thing happens when the this.navCtrl.pop() gets called. The previous page keeps the changes even if the resource were reseted.
I thought that it's in some way linked with the previous page since I'm continuously passing the same object between the pages. Where am I doing wrong?
I've managed to solve it with a workaround.
Creating a service called SyncService, which will take care to backup the given object and provides a copy of it.
Then that copy can be fetched back with the proper method.
Keeping a backed up copy of the object allows me to perform a check on the ionViewCanLeave() method and on the constructor of the parent page.
The core key was using Events. When the user confirms the reset of the object in the alert, an event is published, and the constructor of the parent page subscribes at that event.
Inside the subscription, we'll loop all the current assets searching for the same ID, then if found the entire object gets overwritten.
SyncService class:
import { Injectable } from "#angular/core";
#Injectable()
export class SyncService {
public resource;
public workOnCopy(resource: any, res_type: string): boolean {
sessionStorage.setItem("astrid_mobile_tmp_" + res_type + "_bak", JSON.stringify(resource));
this.resource = resource;
return this.resource;
}
public getBackedUpResource(res_type: string, parse: boolean = true): any {
let res = sessionStorage.getItem("astrid_mobile_tmp_" + res_type + "_bak");
if (!res) {
return false;
}
if (parse) {
JSON.parse(res);
}
return res;
}
}
The child page (AssetPage):
#Component({
selector: 'page-asset',
templateUrl: 'asset.html'
})
export class AssetPage implements OnInit {
public asset;
private can_go_back: boolean = false;
constructor(
private navCtrl: NavController,
private navParams: NavParams,
private alertCtrl: AlertController,
private Events: Events,
private SyncService: SyncService,
) {
this.asset = this.SyncService.workOnCopy(this.navParams.get("asset"), "asset");
}
ionViewCanLeave(): boolean {
if (JSON.stringify(this.asset) === this.SyncService.getBackedUpResource("asset")) {
return true;
}
if (this.can_go_back) {
return true;
}
this.askForLeaveConfirmation();
return false;
}
private askForLeaveConfirmation() {
let confirm = this.alertCtrl.create({
title: 'Do you confirm?',
message: 'Leaving the page without saving, will reset the resource at its original state. Are you sure?',
buttons: [
{
text: 'Disagree',
handler: () => {
console.log('Disagree clicked');
}
},
{
text: 'Agree',
handler: () => {
console.log("Event fired");
this.Events.publish("asset:reseted", this.SyncService.getBackedUpResource("asset"));
this.can_go_back = true;
this.navCtrl.pop();
console.log('Agree clicked');
}
}
]
});
confirm.present();
}
}
The parent page (ParkPage) constructor:
constructor(
private navCtrl: NavController,
private navParams: NavParams,
private Events: Events,
) {
this.park = this.navParams.get("park");
this.Events.subscribe("asset:reseted", (bak_asset) => {
let asset = JSON.parse(bak_asset);
for (let i = 0; i < this.park.assets.length; i++) {
if (this.park.assets[i].id == asset.id) {
this.park.assets[i] = asset;
}
}
});
}
Related
it might be a silly question but i am a beginner so please help me on this
i want to access a public property and change its value inside setTimeout function, Below is my code .
export class AppComponent implements OnInit {
public selectedIndex = 0;
public appPages = [
{
title: 'Trash',
url: '/folder/Trash',
icon: 'trash'
},
{
title: 'Spam',
url: '/folder/Spam',
icon: 'warning'
}
];
public showSplash = true;
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
setTimeout(function() {
//showSplash = false ;
console.log('set timeout worked');
}, 300);
});
}
console.log works inside setTimeout i need to change showSplash value in that block, Please help how can i do that
I think is angular and typescript code. You can use arrow function.
https://developer.mozilla.org/tr/docs/Web/JavaScript/Reference/Functions/Arrow_functions
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
setTimeout(() => {
this.showSplash = false;
console.log('set timeout worked');
}, 300);
});
}
I'm practicing some Angular/Ionic and am having a bit of a hard time figuring out how i get all offresList of all users
this is my database look like :
This is my providre offre.ts
export class OffreProvider {
public offreListRef: firebase.database.Reference;
constructor() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.offreListRef = firebase
.database()
.ref(`/userProfile/${user.uid}/offreList`);
}
});
}
createOffre(
offreTitre: string,
offreSecteur: string,
offreVille: string,
offreDispo: string,
offreDescrip: string,
offreDate : string,
): firebase.database.ThenableReference {
return this.offreListRef.push({
titre: offreTitre,
secteur: offreSecteur,
ville: offreVille,
dispo: offreDispo,
descrip: offreDescrip,
date : offreDate
});
}
getOffreList(): firebase.database.Reference {
return this.offreListRef;
}
}
and this is how i get all offres of currentUser
mesOffres.ts
export class MesOffresPage {
public offreList: Array<any>;
constructor(public navCtrl: NavController, public navParams: NavParams,
public offreProvider: OffreProvider) {
}
creatOffre(): void {
this.navCtrl.push('CreateOffrePage');
}
ionViewDidLoad() {
this.offreProvider.getOffreList().on("value", offreListSnapshot => {
this.offreList = [];
offreListSnapshot.forEach(snap => {
this.offreList.push({
id: snap.key,
titre: snap.val().titre,
secteur: snap.val().secteur,
ville: snap.val().ville
});
return false;
});
});
}
}
now how i can get all offreslist of all Users with alloffres.ts
and display it in alloffres.html
I m stacking for 2 days
To get the offers across all users, you will have to load all user data. Something like this:
ionViewDidLoad() {
let ref = firebase.database().ref("userProfile");
ref.on("value", userListSnapshot => {
this.offreList = [];
userListSnapshot.forEach(userSnapshot => {
let offerListSnapshot = userSnapshot.child("offreList");
offerListSnapshot.forEach(offerSnapshot => {
this.offreList.push({
id: snap.key,
titre: snap.val().titre,
secteur: snap.val().secteur,
ville: snap.val().ville
});
});
})
});
}
Note that this code also loads the profile data for each user, which the it doesn't use. This is one of the many reasons why experienced Firebase developers recommend to keep separate entity types in separate top-level lists. So in your case, I'd recommend having:
userProfiles: {
uid1: {
...
},
uid2: {
...
}
},
userOffers: {
uid1: {
"-LRZ...": {
...
}
},
uid2: {
"-LRQ...": {
...
}
}
}
With the above structure you can load just the offers for all users with code very similar to what I shared above. But with the updated structure, you won't be loading the user profile data unnecessarily. And just in case you need the profile data and offers for a user, you can easily load both in separate calls. The performance will be very similar, since Firebase pipelines the requests over a single connection.
How do I use a method starting a listener on an observable which it returns in an if statement?
I'm in an Angular 5 project, I have this sort of setup in one of my components with an timeline where double click opens up a modal and you can type in the name for the item you're creating into that modal.
for the modals I used a reworked version of this answer. (I needed more up to date syntax and imports).
I've got it all nearly working now, here's my setup,
(timeline component which opens the modals) :
#Component({
selector: 'app-planning',
templateUrl: './planning.component.html',
styleUrls: ['./planning.component.css']
})
export class PlanningComponent implements AfterViewInit {
options = {
onAdd: (item, callback) => {
if(this.timeline.getCurrentTime() > item.start){
this.errorTimelineItemModal();
callback(null);
} else {
if (this.createNewTimelineItemModal()) { // <-- currently I have no return but
// having one would be meaningless
// anyways because the if wouldn't wait
// for the observable response and as a
// result, it would always assess false.
callback(item);
} else callback(null);
}
}
}
constructor(
private _element: ElementRef,
private modalService: BsModalService
) {}
ngAfterViewInit(){
this.container = this._element.nativeElement.querySelector('#timeline');
if (!this.items) {
this.items = new vis.DataSet(this.mydataset);
this.timeline = new vis.Timeline(this.container, this.items, this.groups, this.options);
}
}
createNewTimelineItemModal() {
const initialState = {
title: 'Ajouter',
multipleChoice: 'Bid',
choices: ['Bid', 'D.C.', 'Kickoff'],
accceptBtnName: 'Ajouter',
closeBtnName: 'Annuler',
};
this.bsModalRef = this.modalService.show(Modal, {initialState});
this.bsModalRef.content.onClose.subscribe(result => {
this.createItemResult = result;
console.log(JSON.stringify(result));
})
}
updateTimelineItemModal(name) {
const initialState = {
title: 'Nouveau Nom ?',
itemCurrentName: name,
accceptBtnName: 'Rennomer',
closeBtnName: 'Annuler',
};
this.bsModalRef = this.modalService.show(Modal, {initialState});
this.bsModalRef.content.onClose.subscribe(result => {
this.createItemResult = result;
console.log(JSON.stringify(result));
})
}
deleteTimelineItemModal() {
const initialState = {
title: 'Êtes-vous sûr de vouloir supprimer cet element?',
accceptBtnName: 'Supprimer',
closeBtnName: 'Annuler',
};
this.bsModalRef = this.modalService.show(Modal, {initialState});
this.bsModalRef.content.onClose.subscribe(result => {
this.createItemResult = result;
console.log(JSON.stringify(result));
})
}
errorTimelineItemModal() {
const initialState = {
title: 'Erreur',
list: ['Désolé, créer des éléments avant la date d\'aujourd\'hui est désactivé']
};
this.bsModalRef = this.modalService.show(Modal, {initialState});
this.bsModalRef.content.onClose.subscribe(result => {
this.createItemResult = result;
console.log(JSON.stringify(result));
})
}
}
(modal component) :
export class Modal implements OnInit {
onClose: Subject<Object>;
constructor(
private formBuilder: FormBuilder,
public _bsModalRef: BsModalRef) {}
ngOnInit(): void {
this.onClose = new Subject();
}
public onConfirm(): void {
this.onClose.next(true);
this._bsModalRef.hide();
}
public onCancel(): void {
this.onClose.next(false);
this._bsModalRef.hide();
}
}
As you can see I am getting an answer from validating or not the modal. I can console log it.
Now is where I'm stuck. How can I get the code execution to just halt until an observable has been received by that method so as to assess correctly within the if?
this is actually very important for the correct execution of my code because the callback(null); and callback(item); that you might have noticed are the syntaxe one must have to either finalize the item creation or prevent it.
see : http://visjs.org/docs/timeline/#Methods
I had this working with alerts but I'm trying to switch to something with more functionalities and cleaner.
If I can understand you correctly, you need to synchronize two separate events. It is usually a bad practice to do so.
Try to re-organise your code. It is an async process, so you should divide the process into sub-"transactions", that can happen separately.
Separate the logic for opening up your modal.
Wait for the user to enter the data
Process the answer from the modal.
Something like this:
createNewTimelineItemModal() {
const initialState = {
...
this.bsModalRef.content.onClose.subscribe(result => {
this.createItemResult = result;
this.continueToCreateItem(result);
});
}
private continueToCreateItem(result: any){
<insert your code here>
}
Or other solution can be to return observable objects and hande it within the onAdd
options = {
onAdd: (item, callback) => {
...
this.createNewTimelineItemModal().subscribe(result => {
if(result is something){
callback(item);
} else callback(null);
}
}
}
}
To "halt" the process is a pretty bad practice, but can be achived with Promise object.
this.myPromiseReturningMethod(params).then(() => {
but this will block all your application for the time being (with the user being unable to do anything) so I recommend to alter the structure instead.
I'm working on a simple project using nodejs and angular 2.
In my client project I have a component which has a form and a submit event. When I throw this event all data from the form is properly sent to my node application.
But I have another component which has a table where I list all registers located on my database.
Everything works fine. But I should make my table load in the same time after I've sent my submit.
I have no idea how I can do this.
I tried implement something like this:
export class EmployeeListComponent implements OnInit {
public employees: string[];
constructor(private employeeService : EmployeeService) {
this.employeeService.employeeRequest().subscribe(employees => {
this.employees = employees;
})
}
ngOnInit() {
}
}
And in my register component I did this method:
onRegister(){
const Employee = {
name : this.name,
familyName: this.familyName,
participation: this.participation
}
if (!this.validateService.validateRegister(Employee)){
this.alertMsg = JSON.stringify({'msg': 'All fields must be filled'});
console.log(this.alertMsg);
return false;
} else {
this.registerService.employeeRegister(Employee).subscribe(data => {
this.alertMsg = JSON.stringify({'msg': 'Employee registered successfully'});
console.log(this.alertMsg);
return this.router.navigate(['/list']);
});
};
};
My tests show that my code works fine, but my table just load properly at first submit. After that I have to refresh manually the browser to load my table again.
Is there someone who know what I'm doing wrong or can tell me some way to code that?
Try to change your constructor to a function, and than you call it on ngOninit, so every time your list component initialize your list of employees will load again, something like this:
export class EmployeeListComponent implements OnInit {
public employees: string[];
constructor(private employeeService : EmployeeService) { }
ngOnInit() {
this.loadAllEmployees();
}
loadAllEmployees() {
this.employeeService.employeeRequest().subscribe(data => {
this.employees = data;
});
}
}
Also see Angular life cycle hooks documentation to understand more about ngOnInit and others:
https://angular.io/guide/lifecycle-hooks
Well. After I got the suggestions I tried another strategies. First I've simplify my two components as a single component. Then I moved my logic from the constructor to a method called dashCall() which I call inside my constructor and in the final of onRegister() method.
The final code looks like this:
export class EmployeeRegisterComponent {
public name: string;
public familyName: string;
public participation: number;
public alertMsg: any;
public employees: string[];
constructor(private validateService : ValidateService,
private registerService : EmployeeService,
private employeeService : EmployeeService) {
this.dashCall();
}
onRegister(){
const Employee = {
name : this.name,
familyName: this.familyName,
participation: this.participation
}
if (!this.validateService.validateRegister(Employee)){
this.alertMsg = JSON.stringify({'msg': 'All fields must be filled'});
return false;
} else {
this.registerService.employeeRegister(Employee).subscribe(data => {
this.alertMsg = JSON.stringify({'msg': 'Employee registered successfully'});
this.dashCall();
});
};
};
dashCall(){
this.employeeService.employeeRequest().subscribe(employees => {
this.employees = employees;
});
}
}
I know it's not the best way to implement this. But in the end I got what I needed.
So basically what I want to achieve is watching/listening objects changing inside of array within an injectable service using setters and getters to manipulate it's data
eg
#Injectable()
export class StorageService {
protected items: Array<any> = [];
constructor(private storage: Storage) {
this.storage.ready().then(() => {
StorageService.getGetters().forEach((get) => {
this.storage.get(get).then(res => this.items[get] = res);
});
});
}
public static getGetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["get"] === "function"
});
}
get Storage() {
return this.storage;
};
ClearStorage() {
this.storage.clear();
}
protected Setter(key: string, value: any): void {
this.items[key] = value;
this.storage.set(key, value);
}
protected Getter(key: string): any {
return this.items[key];
}
set User(value: User) {
this.Setter('User', value);
}
get User(): User {
return this.Getter('User');
}
}
where User interface is :
export interface User {
id: number;
role_id: number;
name: string;
email?: string;
}
now in any component or service/provider I can DI my StorageService so I can access the User getter.
so:
storage.User.name = 'testing';
now the name is changed , but I have no way to track that , so I can update my storage!
to update my storage I would do:
storage.User.name = 'testing';
storage.User = storage.User;
which is working , but I need a way to listen to any changes happens to the object properties, so I can update my storage...
I searched alot , and all I can find is watching components #Input() , which is not working in my case.
Hopefully I made my point clear.