Passing item property between components


#1

Hi, I know there’s a lot of ressources to answer this question but I need a little boost to understand with an example from my project. I’m new to Ionic and Angular 2 so I’m sure I’ll be able to understand better with your help. I need to understand where to put things.

I’d like to pass item.points to an other component.

home.ts : Here’s my home.ts :

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Action1 } from '../action1/action1';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})

export class Home {

  items = [];
  btnName: any = 'Edit';
  flag: any = false;

  constructor(public navCtrl: NavController) {

    this.items = [
      {
        id: 'action1',
        points: 0,
        label: 'never : details',
        link: 'Action1'
      },
      {
        id: 'action2',
        points: 0,
        label: 'never : details',
        link: 'Action2'
      },
    ];
  }

  reorderItems(indexes) {
    let element = this.items[indexes.from];
    this.items.splice(indexes.from, 1);
    this.items.splice(indexes.to, 0, element);
  }

  actionBtn() {
    if (this.btnName == 'Edit') {
      this.btnName = 'Done';
      this.flag = true;
    }
    else {
      this.btnName = 'Edit';
      this.flag = false;
    }
  };

  goToPage(test) {
    console.log('test');
    this.navCtrl.push(Action1);
  }

  rarely(SlidingItem, item) {
    item.points = 25;
    SlidingItem.close();
  }

  sometimes(SlidingItem, item) {
    item.points = 50;
    SlidingItem.close();
  }

  often(SlidingItem, item) {
    item.points = 75;
    SlidingItem.close();;
  }

  always(SlidingItem, item) {
    item.points = 100;
    SlidingItem.close();
  }

}

home.html : Here’s my home.html

<ion-header>
  <ion-navbar>
    <ion-title>Home</ion-title>
    <ion-buttons end>
      <button ion-button small clear (click)="actionBtn();">
        {{btnName}}
      </button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>

  <ion-card>
    <ion-item>
      <ion-avatar item-left>
        <ion-icon class="custom-icon" name="home"></ion-icon>
      </ion-avatar>
      <h2></h2>
      <p [innerHTML]="testing"></p>
    </ion-item>
  </ion-card>
  <ion-list sliding="{{flag2}}">
    <ion-item-group reorder="{{flag}}" (ionItemReorder)="reorderItems($event)">
      <ion-item-sliding *ngFor="let item of items" #SlidingItem >
        <ion-item (click)="goToPage(Action1)">
          {{item.id}}
          <p>{{item.label}}</p>
          <p>{{item.points}}</p>
        </ion-item>
        <ion-item-options side="right">
          <button ion-button (click)="always(SlidingItem, item)">
         Always
       </button>
          <button ion-button (click)="often(SlidingItem, item)">
         Often
       </button>
          <button ion-button (click)="sometimes(SlidingItem, item)">
         Sometimes
       </button>
          <button ion-button (click)="rarely(SlidingItem, item)">
         Rarely
       </button>
        </ion-item-options>
      </ion-item-sliding>
    </ion-item-group>

  </ion-list>

</ion-content>

statistics. ts : I’d like to pass item.points to an other component. This one :

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Home } from '../pages/home/home';

@Component({
  selector: 'page-statistics',
  templateUrl: 'statistics.html'
})
export class Statistics {

  constructor(public navCtrl: NavController) {

  }

}

statistics.html : Where I wrote 10 actions and 180 points here :

<!--
  Generated template for the Energy page.

  See http://ionicframework.com/docs/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>Statistics</ion-title>
  </ion-navbar>

</ion-header>


<ion-content padding>

  <ion-card>
  <ion-item>
    <ion-avatar item-left>
      <img src="../assets/img/stats-bars.png">
    </ion-avatar>
    <h2>10 actions</h2>
    <p>180 points</p>
  </ion-item>
</ion-card>

</ion-content>

Thanks !


#2

First up, stop using any as a crutch. Type checking is your friend, and helps both avoiding bugs and making your code much easier to read and maintain.

export interface Activity {
  id: string;
  points: number;
  label: string;
  link: string;
}

Now, instead of thinking of passing things between components (which is bad - it encourages overly tight coupling), we think in terms of centralizing storage in a shared service:

@Injectable()
export class ActivityService {
   // put initial setup here if you want, or read it from storage, or load from backend, or whatever
  activities: Activity[] = [{...}];

  // you can make this more fancy with `addActivity` operations
  // or hide the array behind accessor functions, this is just the
  // bare bones
}

Add ActivityService to the providers of your app module.

Now in any page that cares, simply add an ActivityService parameter to the constructor and DI will ensure they get the same one. You can expose it to the template as a property, assign from it in the constructor, assign from it on ionViewWillEnter(). It all depends on how you need to manage data updates. Changes made to the activities in the service from Page A will be reflected in the service seen by Page B.


#3

Thanks a lot for the answer !

I still have a problem, I’m not sure if I managed to implement it correctly.

Here’s the service :

import { Injectable } from '@angular/core';

@Injectable()
export class MyService {

  items: Array<any>

  constructor() {

    this.items = [
      {
        id: 'A1',
        action: 0,
        points: 0,
        label: 'L1',
        link: 'Li1'
      },
      {
        id: 'A2',
        action: 0,
        points: 0,
        label: 'L2',
        link: 'Li2'
      },
    ];
  }
  getItems() {
    return this.items;
  }
}

I can access it in one component :

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Action1 } from '../action1/action1';

import { MyService } from '../../app/my.service';

@Component({
  selector: 'page-home',
  providers: [MyService],
  templateUrl: 'home.html'
})

export class Home {

  items = [];
  btnName: any = 'Edit';
  flag: any = false;

  constructor(public navCtrl: NavController, private myservice: MyService) {

    this.items = myservice.getItems();
  }

  reorderItems(indexes) {
    let element = this.items[indexes.from];
    this.items.splice(indexes.from, 1);
    this.items.splice(indexes.to, 0, element);
  }

  actionBtn() {
    if (this.btnName == 'Edit') {
      this.btnName = 'Done';
      this.flag = true;
    }
    else {
      this.btnName = 'Edit';
      this.flag = false;
    }
  };

  goToPage(test) {
    console.log('test');
    this.navCtrl.push(Action1);
  }

  rarely(SlidingItem, item) {
    item.points = 25;
    item.action = 1;
    SlidingItem.close();
  }

  sometimes(SlidingItem, item) {
    item.points = 50;
    item.action = 1;
    SlidingItem.close();
  }

  often(SlidingItem, item) {
    item.points = 75;
    item.action = 1;
    SlidingItem.close();;
  }

  always(SlidingItem, item) {
    item.points = 100;
    item.action = 1;
    SlidingItem.close();
  }

  totalPoints() {
    var total = 0;
    for (var i = 0; i < this.items.length; i++) {
      var item = this.items[i];
      total += item.points;
    }
    if (total > 1) {
      return total + " points";
    }
    else {
      return total + " point";
    }
  }

  totalActions() {
    var total = 0;
    for (var i = 0; i < this.items.length; i++) {
      var item = this.items[i];
      total += item.action;
    }
    if (total > 1) {
      return total + " actions";
    }
    else {
      return total + " action";
    }

  };

}

But I can’t access in an other :

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Home } from '../pages/home/home';

import { MyService } from '../../app/my.service';

@Component({
  selector: 'page-statistics',
  providers: [MyService],
  templateUrl: 'statistics.html'
})
export class Statistics {

  items = [];

  constructor(public navCtrl: NavController, private myservice: MyService) {

    this.items = myservice.getItems();
  }

}

Did I forget something ?


#4

When people are trying to help you, please take their comments seriously. When you declare a provider at the component level, each component gets its own instance of the provider.


#5

I really take seriously what you said, you help me a lot to take good directions and I’m grateful for that but I just don’t understand what I have to change, I miss something.

I’ve already added MyService to my app module :

import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';

import { Statistics } from '../pages/statistics/statistics';
import { Home } from '../pages/home/home';
import { Food } from '../pages/food/food';
import { Transports } from '../pages/transports/transports';
import { Consommation } from '../pages/consommation/consommation';
import { Action1 } from '../pages/action1/action1';
import { TabsPage } from '../pages/tabs/tabs';

import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

import { MyService } from './my.service';

@NgModule({
  declarations: [
    MyApp,
    Statistics,
    Home,
    Food,
    Transports,
    Consommation,
    Action1,
    TabsPage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    Statistics,
    Home,
    Food,
    Transports,
    Consommation,
    Action1,
    TabsPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    MyService,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}


#6

No. Bad. Make that go away.


#7

I feel stupid. I thought it was the connection to the service ? So how the component will get the items I put in the constructor of the service ?


#8

Details are here, but the basic idea is that if you declare it in the app module, you get an app-wide singleton, whereas if you declare it at the component level it’s per-component.


#9

Ok, so I’ve just put [myService] in the app module and moved them away from the components but I still can’t get access to the array I created in one of the module. Is there something wrong with the code ? I check everything. I must miss something.


#10

You need to act on the items object that is in MyService, not on local copies made in pages.


#11

Ok, so, I’ve just added providers in my app.component.ts:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

import { TabsPage } from '../pages/tabs/tabs';

import { Home } from '../pages/home/home';
import { Statistics } from '../pages/statistics/statistics';

import { MyService } from './my.service';

@Component({
  templateUrl: 'app.html',
  providers: [MyService]
})
export class MyApp {
  rootPage:any = TabsPage;

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, private myservice: MyService) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      statusBar.styleDefault();
      splashScreen.hide();
    });
  }
}

My service looks like this :

import { Injectable } from '@angular/core';

@Injectable()
export class MyService {

  items: [{
    id: string;
    action: number;
    points: number;
    label: string;
    link: string;
  }];

  constructor() {

    this.items = [
      {
        id: 'A1',
        action: 0,
        points: 0,
        label: 'L1',
        link: 'Li1'
      },
      {
        id: 'A2',
        action: 0,
        points: 0,
        label: 'L2',
        link: 'Li2'
      },
      {
        id: 'A3',
        action: 0,
        points: 0,
        label: 'L3',
        link: 'Li3'
      },
    ];
  }
  getItems() {
    return this.items;
  }
}

No problem for this page to show what’s in the service :

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Action1 } from '../action1/action1';

import { MyService } from '../../app/my.service';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})

export class Home {

  items = [];
  btnName: any = 'Edit';
  flag: any = false;

  constructor(public navCtrl: NavController, private myservice: MyService) {

    this.items = myservice.getItems();
  }

  reorderItems(indexes) {
    let element = this.items[indexes.from];
    this.items.splice(indexes.from, 1);
    this.items.splice(indexes.to, 0, element);
  }

  actionBtn() {
    if (this.btnName == 'Edit') {
      this.btnName = 'Done';
      this.flag = true;
    }
    else {
      this.btnName = 'Edit';
      this.flag = false;
    }
  };

  goToPage(test) {
    console.log('test');
    this.navCtrl.push(Action1);
  }

  rarely(SlidingItem, item) {
    item.points = 25;
    item.action = 1;
    SlidingItem.close();
  }

  sometimes(SlidingItem, item) {
    item.points = 50;
    item.action = 1;
    SlidingItem.close();
  }

  often(SlidingItem, item) {
    item.points = 75;
    item.action = 1;
    SlidingItem.close();;
  }

  always(SlidingItem, item) {
    item.points = 100;
    item.action = 1;
    SlidingItem.close();
  }

  totalPoints() {
    var total = 0;
    for (var i = 0; i < this.items.length; i++) {
      var item = this.items[i];
      total += item.points;
    }
    if (total > 1) {
      return total + " points";
    }
    else {
      return total + " point";
    }
  }

  totalActions() {
    var total = 0;
    for (var i = 0; i < this.items.length; i++) {
      var item = this.items[i];
      total += item.action;
    }
    if (total > 1) {
      return total + " actions";
    }
    else {
      return total + " action";
    }

  };

}

But I still have problem with the other component :

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

import { MyService } from '../../app/my.service';

@Component({
  selector: 'page-statistics',
  templateUrl: 'statistics.html'
})
export class Statistics {

  items = [];

  constructor(public navCtrl: NavController, private myservice: MyService) {

    this.items = myservice.getItems();
  }

}

And I can’t figure it out why. Any clue ?


#12

I am sorry, but I do not know how to express this any more clearly:

You need to act on the items object that is in MyService, not on local copies made in pages.


#13

What do you mean by “acting” on the items object in MyService ? What should I change ?
I follow this tutorial and I can’t see where’s the difference.


#14

Ok, I find where the problem comes from. I think that’s what you meant. The problem was in the html. You help me a lot ! Thanks.


#15

I am glad you solved your problem. It is difficult to give exact code, because there are many options that really depend on how your data updating is managed.