Problem when saving and consulting a variable without reloading a page

Hello there, I’m creating a news reader app, right now the new is coming from an API, the way I’m getting and showing is:

private url: string = "https://api.myjson.com/bins/1awtvn";
private itensSalvos: string = '';
this.remove = '';
constructor(public navCtrl: NavController, private alertCtrl: AlertController, public loadingCtrl: LoadingController, public http: Http, public actionSheetCtrl: ActionSheetController, private sharingVar: SocialSharing, public storage: Storage) {
    this.remove = '';
    this.fetchContent();

    this.storage.get('saved_posts').then(itens => this.itensSalvos = itens);
    if (this.itensSalvos == ""){
      this.itensSalvos = "";
    } else {
      alert (this.itensSalvos);
    }
}

  fetchContent ():void {
    let loading = this.loadingCtrl.create({
      content: 'Buscando conteúdo...'
    });

    loading.present();

    this.http.get(this.url).map(res => res.json())
      .subscribe(data => {
        this.feeds = data.data;
        this.noFilter = this.feeds;
        loading.dismiss();
      });
  }

And showing on the screen with:

<ion-card *ngFor="let feed of feeds">
  <img (click)="itemSelected(feed)" [src]="feed.img" />
  <ion-card-content (click)="itemSelected(feed)">
    <ion-card-title>
      {{feed.post_title}}
    </ion-card-title>
    <p [innerHTML]="feed.post_excerpt" id="justify"></p><br/>
    <span [innerHTML]="feed.post_date" class="data_e_hora"></span> - <span>{{feed.autor}}</span>
  </ion-card-content>
</ion-card>

What I’m trying to do is save a string with the post id to search later, for example 1, 12, 25…

To do that I created a button:

<ion-row no-padding>
  <ion-col>
    <button ion-button clear small color="primary" (click)="saveItem(feed.ID)" icon-left>
      <ion-icon name='bookmark'></ion-icon>
    </button>
  </ion-col>
</ion-row>

inside the card and to handle this saveItem:

  saveItem (post) {
    this.storage.get('saved_posts').then(itens => this.itensSalvos = itens);
    // alert ("Recebeu: " + post + "Está incluso em?" + this.itensSalvos);
    if (this.itensSalvos.includes(post)){
      let alert = this.alertCtrl.create({
        title: 'Removed',
        buttons: ['OK']
      });
      this.remove = post + ",";
      this.itensSalvos = this.itensSalvos.replace (this.remove, "");
      this.storage.set ('saved_posts', this.itensSalvos);
      alert.present();
    } else {
      let alert = this.alertCtrl.create({
        title: 'Saved!',
        buttons: ['OK']
      });
      this.itensSalvos = this.itensSalvos + post;
      this.itensSalvos = this.itensSalvos + (",");
      this.storage.set ('saved_posts', this.itensSalvos);
      alert.present();
    }
  }

The problem is that, every time I click on save I get two times removed and then two times in a row saved. I discovered that if I click save and reload the page every time I save or remove this ID it works perfectly but that’s a problem because for each saved post I’m going to have to reload the app.

How can I save the ID in a string without having to reload the page?

Thank you!

You load saved_posts in the constructor. Either it will never change before you call saveItem() (which should probably be called savePost() instead), in which case you do not want to refetch it at the start of that method, or it might change, in which case you probably want to rethink fetching it in the constructor.

In either case, it is extremely important to follow this rule when dealing with async code. Any function you write that calls an external async source (like storage.get()) must fall into one of two categories:

  • it returns void and all logic that relies on the value is self-contained and hangs off a then or subscribe

  • it returns a future and the very first word of the method is return

There is no need for you to micromanage converting arrays into strings. Just store and retrieve saved_posts as an array.

Hello there, thank you for answering, I’m going to try what you said. And I need to convert an array to string because the API uses that string as a slug to search specific posts so I’d have to convert sometime.

Thank you for your help.

Hello there, I made some changes but I’m still getting the error, here’s what I tried:

private itensSalvos: string = '';

This need to be done because otherwise, I get: undefined is not an object.

constructor(public navCtrl: NavController, private alertCtrl: AlertController, public loadingCtrl: LoadingController, public http: Http, public actionSheetCtrl: ActionSheetController, private sharingVar: SocialSharing, public storage: Storage) {

    this.remove = '';
    this.toggled = false;
}

Removed saved_posts and itensSalvos from the constructor like you said.

  getSavedPosts (){
    this.storage.get('saved_posts').then(itens => this.itensSalvos = itens);
  }

Created a void function using then like you suggested.

  saveItem (post) {
    this.getSavedPosts ();
    alert (this.itensSalvos);
    if (this.itensSalvos.includes(post)){
      this.remove = post + ",";
      this.itensSalvos = this.itensSalvos.replace (this.remove, "");
      this.storage.set ('saved_posts', this.itensSalvos);
    } else {
      this.itensSalvos = this.itensSalvos + post;
      this.itensSalvos = this.itensSalvos + (",");
      this.storage.set ('saved_posts', this.itensSalvos);
    }
  }

Called this function whenever the saveItens is called. But, I’m still getting the same error, the alert is returning the same string twice then the updated string including the post id twice and then the string without the id twice again, did I do something wrong? Thank you for your help.

You didn’t really follow the rules. Other functions (notably saveItem) rely on this.itensSalvos being set. Directly or indirectly, the only place you can do that is inside the then() block where you are assigning it. You cannot assume that itensSalvos is set simply because you have called getSavedPosts(), which is what you are doing now.

In general, I don’t like having pages be so intimately involved with Storage. I would abstract much of this into a provider:

@Injectable() export class PostService {
  savedPosts = new Subject<string>();

  constructor(private _storage: Storage) {
    _storage().ready()
    .then(() => _storage.get('saved_posts'))
    .then((sp) => this.savedPosts.next(sp));
  }

  setSavedPosts(sp: string): Promise<void> {
    return this._storage.set('saved_posts', sp)
    .then(() => this.savedPosts.next(sp));
  }

  addSavedPost(pid: string): Promise<void> {
    let sp = this.savedPosts.value;
    // tack pid onto sp
    return this.setSavedPosts(sp);
  }

  removeSavedPost(pid: string): Promise<void> {
    let sp = this.savedPosts.value;
    // splice pid out of sp
    return this.setSavedPosts(sp);
  }
}

export class SavedPostsPage {
  itensSalvos: string;

  constructor(private _postsvc: PostService) {
    _postsvc.savedPosts.subscribe(sp => this.itensSalvos = sp);
  }
}

You can freely call this._postsvc.addSavedPost() and removeSavedPost() from your page, and the subscription made in the constructor will automatically ensure that itensSalvos stays up-to-date.

This could be improved by storing a Subscription and unsubscribing in one of the lifecycle events when your page is going away, but hopefully this will at least be sufficient to get you going.

When I tried to create this provider, I’m getting two errors:

  1. Cannot find name: Subject on savedPosts = new Subject<string>();
  2. Cannot invoke an expression whose type lacks a call signature. Type ‘Storage’ has no compatible call signatures. on _storage().ready() - If I erase this storage.ready, this works but the Subject is still not working,

I imported Storage to the script but that didn’t solve the problem.

Subject is an RxJS thing; you have to import its declaration. The only reason I can think of that your Storage doesn’t have a ready() method is that it’s really old. ready() was added in 1.1.7 and you should definitely not blindly eliminate it, or your app will have race conditions.

How can I check/update my storage version?

Here’s my ionic info:

global packages:

    @ionic/cli-utils : 1.3.0
    Cordova CLI      : 7.0.1 
    Ionic CLI        : 3.3.0

local packages:

    @ionic/app-scripts              : 1.3.7
    @ionic/cli-plugin-cordova       : 1.4.0
    @ionic/cli-plugin-ionic-angular : 1.3.0
    Cordova Platforms               : android 6.2.3 ios 4.4.0
    Ionic Framework                 : ionic-angular 3.3.0

I also added import { Subject } from 'rxjs/Subject'; to my file and now I’m getting: Property 'value' does not exist on type 'Subject<string>'.

I made a small change to constructor and ready is working now:

    _storage.ready()
    .then(() => _storage.get('saved_posts'))
    .then((sp) => this.savedPosts.next(sp));

But the .value is still not working.

Perhaps making it a BehaviorSubject instead will help.