Star Ratings in Ionic 2

I do have a question please.

How do I get the RatingComponent to update my model object with its newly selected score value?

Details:

I am using the RatingComponent, as shown in the previous reply comment. When a user clicks on the stars of the RatingComponent, it calls the update function on the RatingComponent. This sets the template on the RatingComponent as expected, and the stars displayed are updated.

I also have an EmployeePage that has a RatingComponent.

EmployeePage.ts:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { EmployeeModel } from '../model/employeeModel';
import { RatingComponent } from '../utils/rating/RatingComponent';
@Component({
  templateUrl: 'build/pages/employee/employee.html',
  providers: [RatingComponent],
  directives: [RatingComponent]
})
export class EmployeePage {
  private employeeModel: EmployeeModel = null;
  private ratingComponent: RatingComponent;
  constructor(private navController: NavController, private navParams: NavParams, ratingComponent: RatingComponent) {
    this.employeeModel = navParams.get('employeeModel');
    this.ratingComponent = ratingComponent;
  }
  updateRating(event: Event) {
    alert(event.timeStamp);
    this.employeeModel.averageRating = this.ratingComponent.score;
    this.ratingComponent.update(this.ratingComponent.score);
  }
}

I have an html that places the <rating> tag on the page.

employee.html:

<rating score="{{employeeModel.averageRating}}" max="5" (click)="updateRating($event)"></rating>
<br>Rating: <b>{{employeeModel.averageRating}}</b>

When the user clicks on the stars to update he raitng, the RatingComponentā€™s update function is called, but now I also need to update my model object with the new star rating (employeeModel.averageRating).

I donā€™t want the RatingComponent to have any reference to the EmployeePage, because it needs to be loosely coupled. The EmployeePage does have a RatingComponent though.

I am not sure if my EmployeePage implementation of its RatingComponent is correct.

When the star is clicked I also call the updateRating function on the EmployeePage (via (click)="updateRating($event)"), in order to update the model. I try update the model with the RatingComponentā€™s new score, but it does not get the new updated value, but always gets the default value of 1 (@Input() public score: number = 1;).

It is as if there are two separate RatingComponent objects. I can set one as desired, but when I want to reference the value set (score), I can only get a returned default value.

My EmployeePage has the RatingComponent defined as a directive in order to display the stars, and also as a provider in order to get a handle on it, to get the new score value.

@Component({
  templateUrl: 'build/pages/employee/employee.html',
  providers: [RatingComponent],
  directives: [RatingComponent]
})

I think this is where my problem lies. I think it is creating two separate RatingComponent objects when I want to share the same object.

Can anyone please suggest a better way for me to handle this?

Thanks

Iā€™m on my phone right now, so just a couple of quick pointers to hopefully get you going.

RatingComponent goes in the directives array only. Remove it from your componentā€™s providers array and constructor.

You should communicate with RatingComponent via attributes in the template only. RatingComponent should have:

Output() public update: Eventemitter = new EventEmitter();

And rename current update to onUpdate(score: number): void {
this.score = score;
this.update.emit(score);
}

Now in your component template you can have rating (update)=ā€œonRatingChange($event)ā€

And add the onRatingChange(score: number) function to your component.

1 Like

Hi infoproducats,

Thank you! That works perfectly. I appreciate your help, you have saved me a lot of time.

EmployeePage.ts

  onRatingChange(score: number) {
    this.employeeModel.averageRating = score;
  }

employee.html

<rating score="{{employeeModel.averageRating}}" max="5" (update)="onRatingChange($event)"></rating>

RatingComponent.ts

template: `
		<ul>
			<li *ngFor="let icon of icons(); let i = index" (click)="onUpdate(i+1)">
				<ion-icon [name]="icon"></ion-icon>
			</li>
		</ul>
	`,
	@Output() public update: EventEmitter<number> = new EventEmitter();
	onUpdate(score: number): void {
		this.score = score;
		this.update.emit(score);
	}

Thatā€™s it! Glad to help :slight_smile:

1 Like

Hello,
can you please share the updated star rating directive? Thanks

Hi nylex,

What I am using is:

RatingComponentUpdateable.ts

import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } 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)="onUpdate(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;
			font-size: 3.2em;
		}
		li + li {
			margin-left: .1em;
		}
	`],
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class RatingComponentUpdateable {
	@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';
	@Output() public update: EventEmitter<number> = new EventEmitter();
	onUpdate(score: number): void {
		this.score = score;
		this.update.emit(score);
	}
	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;
	}
}

html

<ion-content padding class="search" id="idsearch">
  <div class="card">
    <div class="item item-text-wrap">
    </div>
    <rating score="0" max="5" (update)="onRatingChange($event)"></rating>
  </div>
</ion-content>

ok this works fine in separate page. But how can I implement in another page?

Iā€™ve created a rating component for Ionic 2: https://github.com/andrucz/ionic2-rating
PRs are welcome!

Hi All,

I am using the following Rating Component, and I need to default the star rating to a value when I load it. Currently it just defaults to zero stars.

Does anyone know how I can set the number of stars from a ts file?

Thanks

RatingComponentUpdateable.ts

import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } 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)="onUpdate(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;
			font-size: 3.2em;
		}
		li + li {
			margin-left: .1em;
		}
	`],
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class RatingComponentUpdateable {

	@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';

	@Output() public update: EventEmitter<number> = new EventEmitter();

	onUpdate(score: number): void {
		this.score = score;
		this.update.emit(score);
	}

	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;
	}
}

html

<ion-content padding class="search" id="idsearch">

  <div class="card">
    <div class="item item-text-wrap">
    </div>
    <rating score="0" max="5" (update)="onRatingChange($event)"></rating>
  </div>

</ion-content>

Great! but this is not well documented. How can I integrate this in my application? I followed all the steps in the page but I am getting ā€˜template parse error rating is not an elementā€™.

I donā€™t know about the original package discussed in the thread, but I wrote a Star Rating component a while ago, and just posted it to Github. Link: https://github.com/Aaron-Sterling/star-rating

As an avid user of the Behavior Subject, I really appreciate your recent posts and utilization of the BS (behavior subject. Not to be confused with the other BS). I didnā€™t start using them until relatively recently, but they have proven to be an excellent tool.

Admittedly, I have borrowed some techniques from your examples.

Feel free! Probably 5% of my code techniques are original to me, and 95% are things Iā€™ve found other places. In case anyone cares, the content Iā€™ve found most useful is: official Angular docs; books by @joshmorony, @javebratt, and fullstack.io; and video courses on Observables at egghead.io. Altogether, Iā€™ve spent maybe $150 on educational materials, and it got me to a level I doubt I would have arrived at otherwise.

1 Like

https://egghead.io looks like a great tool for me at the moment. Iā€™m trying to build my skills in the observable realm. Thanks for the links

You can do like below.

page.html

  <div style="font-size: 30px" text-center>
    <ion-icon *ngFor="let i of [0,1,2,3,4]" [name]="i < rate ? 'star' : 'star-outline'"
       (click)="selectStar(i)"></ion-icon>
  </div>

page.ts

rate = 0;
selectStar(i) {
    this.rate = i + 1;
}

See: https://github.com/andrucz/ionic2-rating