Star Ratings in Ionic 2


#1

Hi All,

Can anyone please recommend how to do a Star Ratings implementation in Ionic 2. Are there any plugins or scripts available? I have seen a few, but all have examples for Ionic 1. (For example, can Angular Ratings be used with Ionic 2?. How do you import it?)

  • needs to be able to display half star

Thanks


#2

I would like to see an example component too, because I need it to rate and to display the ratings hopefully exactly as they are (3.8, 4.7, 4.9). Maybe we can port some code from Angular Ratings, nice stuff.


#3

I have found this which shows how to do it in Angular 1 & Angular 2. So close to what we need I think. However, I am pretty new to Ionic, and am finding Angular 2 has some differences with ionic 2 which are confusing me.

For example, doesn’t Ionic 2 not have directives?

directives: [NgFor]

Also, does Ionic 2 use @View?


#4

I’m new to stand alone @Components on Ionic 2 but you can generate them with:

ionic g component rating

the template is configured there, and then port the code there :smiley:
then we can use the new component on any Page of the Ionic 2 app adding the

import { Rating } from '../components/rating'

@Component {
  ...
  directives: [ Rating ]

something like that! gotta try :slight_smile:


#5

Yes, you can declare directives in Ionic components. No, you don’t need to explicitly do it with ngFor: Ionic does it for you. Nobody uses @View any more: it was removed upstream.


#6

Thanks guys, I’ll give it a try


#7

Hi guys,

After I add <rating></rating>:

  <ion-item>
    <p><rating></rating></p>                
  </ion-item>

I get the following error:

EXCEPTION: Error: Uncaught (in promise): Template parse errors:
There is no directive with "exportAs" set to "index" ("
    <span tabindex="0">
      <template ng-for [ng-for-of]="range" [ERROR ->]#index="index">
        <span class="sr-only">(*)</span>
        <i class="glyphicon glyphicon-star">"): Rating@2:43
Can't bind to 'ng-for-of' since it isn't a known native property ("
    <span tabindex="0">
      <template ng-for [ERROR ->][ng-for-of]="range" #index="index">
        <span class="sr-only">(*)</span>
        <i class="glyphi"): Rating@2:23
Property binding ng-for-of not used by any directive on an embedded template ("
    <span tabindex="0">
      [ERROR ->]<template ng-for [ng-for-of]="range" #index="index">
        <span class="sr-only">(*)</span>
       "): Rating@2:6

Does that mean I to declare ngFor as a directive? How do I do this?

I’m using Ionic 2.0.0-beta.32

Thanks


#8

I’ve had an error like this using some old code on Angular 2, what’s your template code? can you post it?


#9

Thanks matheo

search.html

  <ion-list>
    <ion-item-sliding *ngFor="let item of employeeModels">
      <ion-item>
        {{ formatEmployee(item) }}
        <ion-avatar item-left><img [src]="item.avatar64 ? item.avatar64 : 'images/blank-profile-picture.png'"></ion-avatar>
        <h2>{{item.firstName}}</h2>
        <p>{{item.categories[0].name}}&nbsp;{{item.subCategories[0].name}}&nbsp;{{item.time}}&nbsp;<rating></rating></p>                
      </ion-item>
      <ion-item-options>
        <button primary (click)="alert('todo')"><ion-icon name="text"></ion-icon>Message</button>
        <button light (click)="alert('todo')"><ion-icon class="ion-ios-heart"></ion-icon>Favourite</button>
      </ion-item-options>
    </ion-item-sliding>
  </ion-list>

<ion-infinite-scroll (ionInfinite)=“doInfinite($event)”>

search.ts

import { Component, ChangeDetectionStrategy, enableProdMode, ChangeDetectorRef } from '@angular/core';
import { NavController, Loading } from 'ionic-angular';
import { EmployeeService } from '../service/employeeService';
import { UtilityService } from '../utils/utilityService';
import { Employee } from '../model/employee';
import { Rating } from '../utils/rating/rating';
//enableProdMode();
@Component({
  templateUrl: 'build/pages/search/search.html',
  providers: [EmployeeService, UtilityService],
  changeDetection: ChangeDetectionStrategy.OnPush,
  directives: [Rating]
})
export class SearchPage {
  private MAX_RESULTS: number = 20;
  private firstResult: number = 0;
  private ref: ChangeDetectorRef;
  private nav: NavController = null;
  private loading: Loading = Loading.create();;
  private employeeService: EmployeeService = null;
  private utilityService: UtilityService = null;
  private employeeModel: Employee = null;
  private employeeModels: Employee[] = [];
  constructor(ref: ChangeDetectorRef, nav: NavController, employeeService: EmployeeService, utilityService: UtilityService) {
    this.ref = ref;
    this.nav = nav;
    this.employeeService = employeeService;
    this.utilityService = utilityService;
  }
  ngOnInit() {
    this.getEmployeeRange();
  }
  doInfinite(infiniteScroll: any) {
    this.firstResult += this.MAX_RESULTS;
    this.employeeService.getEmployeeRange(this.firstResult, this.MAX_RESULTS).then((employeeModels: Employee[]) => {
      for (var index = 0; index < employeeModels.length; index++) {
        this.employeeModels.push(employeeModels[index]);
      }
      infiniteScroll.complete();
    });
  }
  getEmployeeRange() {
    this.nav.present(this.loading);
    this.employeeService.getEmployeeRange(this.firstResult, this.MAX_RESULTS).then((employeeModels: Employee[]) => {
      for (var index = 0; index < employeeModels.length; index++) {
        this.employeeModels.push(employeeModels[index]);
      }
      this.loading.dismiss();
      this.ref.markForCheck();
    });
  }

rating.ts

import {Component} from '@angular/core';
@Component({
  selector: 'rating',
  template: `
    <span tabindex="0">
      <template ng-for [ng-for-of]="range" #index="index">
        <span class="sr-only">(*)</span>
        <i class="glyphicon glyphicon-star"></i>
      </template>
    </span>
  `
})
export class Rating {
  private range: Array<number> = [1, 2, 3, 4, 5];
}

#10

Example directive for star rating in angular 2


#11

Thanks amityadav, that Plunker example looks the same as the one I am using here. However, I am having problems converting the Angular 2 conventions to Ionic 2.


#12

To me, the faulty code is:

#index="index"

somehow the range Array<number> is not exporting its index to be handled by the ngFor, maybe changing the ngFor syntax, or search an updated reference of it, because it can be a little old code, that API changed recently.


#13

Thanks matheo, You are right. I changed the code to the following and it works. It displays stars as (*) (*) (*) (*) (*). Which is what is expected according to this.

@Component({
   selector: 'rating',
  template: `
      <span tabindex="0">
          <template ngFor let-val [ngForOf]="range" let-index="index">
              <span class="sr-only">(*)</span>
             <i class="glyphicon glyphicon-star"></i>
          </template>
     </span>
 `,
  directives: []
})
export class Rating {
    private range:Array<number> = [1,2,3,4,5];
}

#14

Awesome news!
Happy coding then, and thanks for this!
It will help me the next week when I have time to get on my rating component too :slight_smile:


#15

Great, thank you for the help. Good luck with your project. I will do some more work on this tomorrow, I will post my code here if I have success so you can make use of it if you like.


#16

Just wanted to share my RatingComponent:

import {Component, ChangeDetectionStrategy, Input} from '@angular/core';
import {Icon} from 'ionic-angular/components/icon/icon';

@Component({
	selector: 'rating',
	directives: [Icon],
	template: `
		<ul>
			<li *ngFor="let icon of icons()">
				<ion-icon [name]="icon"></ion-icon>
			</li>
		</ul>
	`,
	styles: [`
		ul {
			display: inline-block;
			list-style: none;
			padding: 0;
			margin: 0;
		}
		li {
			display: inline-block;
			color: #ffa500;
		}
		li + li {
			margin-left: .1em;
		}
	`],
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class RatingComponent {

	@Input() public score: number = 1;
	@Input() public max: number = 5;

	@Input() public iconEmpty: string = 'star-outline';
	@Input() public iconHalf: string = 'star-half';
	@Input() public iconFull: string = 'star';

	public icons(): string[] {
		let step = 0.5;
		let score = Math.ceil(this.score / step) * step;

		let icons = [];
		for (let i = 1; i <= this.max; i++) {
			if (i <= score) {
				icons.push(this.iconFull);
			} else if (i - step <= score) {
				icons.push(this.iconHalf);
			} else {
				icons.push(this.iconEmpty);
			}
		}
		return icons;
	}

}

Use it like this in templates:

<rating score="4" max="5"></rating>


#17

Hi infoproducts,

Looks great. Nice that it shows half stars too.

Do you have any code to also update the stars? i.e. to select a star rating?

Thanks


#18

No, sorry. Haven’t had the need for it yet, but probably shouldn’t be too hard to add


#19

Thanks infoproducts, I’ll look to do it tomorrow and add the code here.


#20

This works, you need to add an index and an update function.

Thanks for your help matheo & infoproducts. matheohopefully this helps you too.

import {Component, ChangeDetectionStrategy, Input} from '@angular/core';
import {Icon} from 'ionic-angular/components/icon/icon';
@Component({
	selector: 'rating',
	directives: [Icon],
	template: `
		<ul>
			<li *ngFor="let icon of icons(); let i = index" (click)="update(i+1)">
				<ion-icon [name]="icon"></ion-icon>
			</li>
		</ul>
	`,
	styles: [`
		ul {
			display: inline-block;
			list-style: none;
			padding: 0;
			margin: 0;
		}
		li {
			display: inline-block;
			color: #ffa500;
		}
		li + li {
			margin-left: .1em;
		}
	`],
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class RatingComponent {
	@Input() public score: number = 1;
	@Input() public max: number = 5;
	@Input() public iconEmpty: string = 'star-outline';
	@Input() public iconHalf: string = 'star-half';
	@Input() public iconFull: string = 'star';
	public update(index: number) {
		this.score = index;
	}
	public icons(): string[] {
		let step = 0.5;
		let score = Math.ceil(this.score / step) * step;
		let icons = [];
		for (let i = 1; i <= this.max; i++) {
			if (i <= score) {
				icons.push(this.iconFull);
			} else if (i - step <= score) {
				icons.push(this.iconHalf);
			} else {
				icons.push(this.iconEmpty);
			}
		}
		return icons;
	}
}