I am working on a listing application that transfers an object from one component to an Identical sibling component. I'd also like to decrease the moves property by 1 with each move. I am having trouble with the actually pushing the selected item into the sibling array.
Here's a sample of my data...
import { player } from "./player";
export const PLAYERS: player[] = [
{
index: 1,
photo: "../../assets/images/lonzo.jpeg",
name: "Lonzo Ball",
position: "G",
moves: 5
},
{
index: 2,
photo: "../../assets/images/Brook.jpeg",
name: "Brook Lopez",
position: "C",
moves: 5
},
This is what I have in the services file...
import { Injectable } from "#angular/core";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { Subject } from 'rxjs/Subject';
import { player } from "./player";
import { PLAYERS } from "./players";
import 'rxjs/add/observable/of';
import { Observable } from "rxjs/Observable";
import { of } from "rxjs/observable/of";
import { bench } from './bench';
import { RESERVES } from './RESERVES';
#Injectable()
export class DataService {
public players = <any>PLAYERS;
public bench = <any>RESERVES;
public getPlayers(): Observable<player[]> {
return Observable.of(this.players);
}
public getReserves(): Observable<bench[]> {
return Observable.of(this.bench);
}
addToReserves(data) {
this.bench.next(data);
}
addToStarter(data) {
this.players.next(data)
}
constructor() {}
}
This is what I have in the starters.ts file...
import { Component, OnInit } from "#angular/core";
import { DataService } from '../data.service'
import { player } from "../player";
#Component({
selector: "app-roster-box",
templateUrl: "./roster-box.component.html",
styleUrls: ["./roster-box.component.css"],
})
export class RosterBoxComponent implements OnInit {
moves: number = 5;
btnText: string = "Move to Bench";
players: Array<any>
constructor(public dataService: DataService) {}
getPlayers() : void {
this.dataService.getPlayers()
.subscribe(players => this.players = players);
}
ngOnInit() {
this.getPlayers();
}
moveToBench( data, i): void {
this.dataService.addToReserves(data)
// this.players.splice(i, 1);
}
}
And finally the code for the bench.ts file...
import { Component, OnInit } from '#angular/core';
import { DataService } from '../../app/data.service'
import { bench } from "../bench";
#Component({
selector: 'app-roster-bench',
templateUrl: './roster-bench.component.html',
styleUrls: ['./roster-bench.component.css']
})
export class RosterBenchComponent implements OnInit {
moves: number = 5;
btnText: string = "Make Starter";
reserves: Array<any>;
constructor(private dataService: DataService) { }
getReserves() : void {
this.dataService.getReserves()
.subscribe(reserves => this.reserves = reserves);
}
ngOnInit() {
this.getReserves();
// this.moveData();
}
makeStarter( data, i): void {
this.dataService.addToStarter(data)
// this.players.splice(i, 1);
}
}
I am looking to simply click a button in html to move the selected player to the sibling component. I am having trouble with how to structure the services/observable etc as this is my first time working with Angular.
I had a little free time ... so I moved your sample code to a stackblitz here: https://stackblitz.com/edit/angular-j2l8pq
I was not sure exactly what you were trying to accomplish, but tried to basically answer your question.
A few key things:
1) I assumed that the bench and the players arrays both needed to be of type Player. That allows you to move items of the same type between the two arrays without any loss of properties.
2) I changed the property in the component to be a getter. That way it would always get the current value from the service:
get players(): Player[] {
return this.dataService.players;
}
get bench(): Player[] {
return this.dataService.bench;
}
3) You did not seem to need a Subject/BehaviorSubject so I removed the imports.
I was not sure what "sibling component" you were referring to. But if you create another component that talks to the same service, if it also has a getter as shown above it will share the same data as the player component.
Hopefully the provided code provides the basic direction for you to answer your question.
Component
import { Component, OnInit } from "#angular/core";
import { DataService } from './player.service'
import { Player } from "./player";
#Component({
selector: 'app-roster-box',
template:
`<div>Current Roster</div>
<li *ngFor="let player of players" (click)="moveToBench(player)">
{{ player.name }}
</li>
<br>
<div>Bench</div>
<li *ngFor="let player of bench">
{{ player.name }}
</li>
`
})
export class RosterBoxComponent implements OnInit {
moves: number = 5;
btnText: string = "Move to Bench";
get players(): Player[] {
return this.dataService.players;
}
get bench(): Player[] {
return this.dataService.bench;
}
constructor(public dataService: DataService) {}
getPlayers() : void {
this.dataService.getPlayers()
.subscribe();
}
ngOnInit() {
this.getPlayers();
}
moveToBench( player ): void {
console.log(player);
this.dataService.addToReserves(player)
}
}
Service
import { Injectable } from "#angular/core";
import { Player } from "./player";
import { Observable } from "rxjs/Observable";
import { of } from "rxjs/observable/of";
#Injectable()
export class DataService {
players: Player[] = [];
bench: Player[] = [];
constructor() {}
getPlayers(): Observable<Player[]> {
this.players = [
{
index: 1,
photo: "../../assets/images/lonzo.jpeg",
name: "Lonzo Ball",
position: "G",
moves: 5
},
{
index: 2,
photo: "../../assets/images/Brook.jpeg",
name: "Brook Lopez",
position: "C",
moves: 5
}
]
return of(this.players);
}
getReserves(): Observable<Player[]> {
return of(this.bench);
}
addToReserves(player: Player) {
this.bench.push(player);
const index = this.players.indexOf(player);
if (index > -1)
this.players.splice(index, 1);
}
addToStarter(data) {
this.players.push(data)
}
}
Related
I have a list of employees in a JSON object in my service.ts file. It will be moved to a DB eventually. I know this is not the place for it. It's just for dev purposes. I am able to retrieve the employees by clicking on it and displaying them in a dynamic view component.
However, I want to be able to see the next employee on a button click without having to always go back to the main list. I am able to retrieve the index and increment the index on click. I just can't get the route to display the data for that index.
The HTML is pretty basic a simple button with a click method. The Service file looks like this:
import {HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Employee } from '../models/employee.models';
#Injectable({
providedIn: 'root'
})
export class EmployeesService {
private listEmployees: Employee[] = [...regular json object...];
constructor() { }
getEmployees(): Employee[] {
return this.listEmployees;
}
getEmployee(id: number): Employee {
return this.listEmployees.find(e => e.id === id);
}
}
And my employee.component.ts file
import { ActivatedRoute } from '#angular/router';
import { EmployeesService } from './../../../services/employees.service';
import { Component, OnInit } from '#angular/core';
import { Employee } from 'src/app/models/employee.models';
import { Router } from '#angular/router';
#Component({
selector: 'employee',
templateUrl: './employee.component.html',
styleUrls: ['./employee.component.scss']
})
export class EmployeeComponent implements OnInit {
employee: Employee;
employees: Employee[];
index: number;
private _id: number;
constructor(
private _route: ActivatedRoute,
private _router: Router,
private _employeeService: EmployeesService
) { }
ngOnInit() {
this.employees = this._employeeService.getEmployees();
for ( this.index = 1; this.index < this.employees.length + 1; this.index++ ) {
this._route.paramMap.subscribe(params => {
this._id = +params.get('id')
console.log('this index:', this.index);
this.employee = this._employeeService.getEmployee(this._id);
});
}
}
viewNextEmployee() {
if( this.index < this.employees.length ){
this.index = this.index++;
console.log('this index:', this.index);
this._router.navigate(['/team', this._id])
} else {
this.index = 1;
}
}
}
I'm sorry, I can't really reproduce a working code. So many dependencies.
I Have created a shopping cart with 2 components(Productlist and cart list). when I click on the 'add to cart' button in the product list it is successfully moving into the service file and from the service file to 'cart list' but Total was showing undefined. Please help me to solve this cart Total issue.
Product-list.ts file
import { Component, OnInit } from '#angular/core';
import { ProductService } from '../../../service/product.service';
import { CartService } from '../../../service/cart.service';
#Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
Productlist = [];
constructor(private productservice: ProductService,
private cartservice: CartService) {
this.Productlist = this.productservice.send()
}
ngOnInit(): void {
}
saveproduct(item) {
this.cartservice.get(item)
}
}
cart service file
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class CartService {
cartproducts = [{Name: 'Mobile', Price: 13000, Qty: 1}]
constructor() { }
send() {
return this.cartproducts
}
get(items) {
this.cartproducts.push(items)
console.log(this.cartproducts)
}
}
cart list file
import { Component, OnInit, ɵConsole, ɵɵNgOnChangesFeature } from '#angular/core';
import { CartService } from '../../../service/cart.service';
#Component({
selector: 'app-cart',
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.css']
})
export class CartComponent implements OnInit {
cartlist = [];
Total: number;
constructor( private cartservice: CartService) {
this.cartlist = this.cartservice.send()
console.log(this.cartlist);
console.log(this.Total);
}
ngOnInit() {
this.cartlist.forEach((_e: any) => {
this.Total += (_e.qty * _e.price);
});
}
}
When the Total property has been created, it is undefined by default until you assign it a value. You do assign a value to it in the ngOnInit but you calculate on an undefined value, so it returns undefined as well. Give Total a default value like so:
...
Total: number = 0;
...
Also the code you put in ngOnInit to calculate the Total, would be better placed in a get property. This way, Total will always be calculated when it's requested instead of pre-rendered and only calculated once. In that case your code would look like:
get Total(): number {
let value = 0;
this.cartlist?.forEach((_e: any) => {
value += (_e.qty * _e.price);
});
return value;
}
import { NgForm } from '#angular/forms';
import { Exercise } from './../exercise.model';
import { TrainingService } from './../training.service';
import { Component, OnInit, ViewChild } from '#angular/core';
import { AngularFirestore } from 'angularfire2/firestore';
import { Observable, Subscriber } from 'rxjs';
import 'rxjs/add/operator/map';
#Component({
selector: 'app-new-training',
templateUrl: './new-training.component.html',
styleUrls: ['./new-training.component.css'],
})
export class NewTrainingComponent implements OnInit {
exercises: Observable<Exercise[]>;
constructor(
private trainingService: TrainingService,
private db: AngularFirestore
) {}
ngOnInit(): void {
this.exercises = this.db
.collection('availableExercises')
.snapshotChanges()
.map((docArray) => {
return docArray.map((doc) => {
return {
id: doc.payload.doc.data().id,
name: doc.payload.doc.data().name,
duration: doc.payload.doc.data().duration,
calories: doc.payload.doc.data().calories,
};
});
});
}
onStartTraining(form: NgForm) {
this.trainingService.startExercise(form.value.exercise);
}
}
Have issue with id, name, duration, and calories.. they are all underlined and error says: Property does not exist on type 'unknown' for all four. So not sure what the issue is. I have tried as well
id: doc.payload.doc['id'],
name: doc.payload.doc['name'],
duration: doc.payload.doc['duration'],
calories: doc.payload.doc['calories'],
doesn't work as well. Was repeating after Maximilian Schwarzmuller's tutorial. Would appriciate any help.
The answer was to change the next line on the function ngOnInit from:
return docArray.map((doc) => {
Into:
return docArray.map((doc: any) => {
This will allow you to allow you to avoid type checking during compilation. As mentioned here
I have a loop on a component which represents a list of graph cards on my real app.
I have copied this component ( and loop it ) as the original
Hello Component
export class HelloComponent {
message:string;
printedMessage:string
#Input() elm:string;
constructor(private data: DataService, private router : Router) { }
ngOnInit() {
this.message = this.data.messageSource.value;
this.data.messageSource.subscribe(message => this.message = message)
}
updateService(){
this.data.changeMessage(this.message);
this.printedMessage=this.data.messageSource.value
}
navigateToSibling(){
this.router.navigate(['/sibling']);
}
}
app component
<div *ngFor="let elm of [1,2,3,4]">
<hello [elm]= "elm"></hello>
</div>
<h1>Copy </h1>
<div *ngFor="let elm of [1,2,3,4]">
<hello [elm]= "elm"></hello>
</div>
DataService component
export class DataService {
messageSource = new BehaviorSubject<string>("default message");
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
Expected behaviour
What I would is when change the input value on the component 1 for example , only the value on the input of the copied component 1 changes.
Actual behaviour
Actually when I change a value inside an input all the other inputs are changings.
Here's a stackblitz example
Below is a solution that will solve you issue. This may not be a perfect solution but you need something similar.
hello.html
<h1>App component {{elm}}</h1>
<input type="text" [(ngModel)]="message">
<button (click)="updateService()" type="button">Save</button> {{printedMessage}}
Data Service
import {
Injectable
} from '#angular/core';
import {
BehaviorSubject
} from 'rxjs/BehaviorSubject';
#Injectable()
export class DataService {
messageSource = new BehaviorSubject < any > ("default message");
constructor() {}
changeMessage(message: string, elem: any) {
this.messageSource.next({
message: message,
elem: elem
});
}
}
HelloComponent
import {
Component,
Input
} from '#angular/core';
import {
DataService
} from "./dataService";
import {
Router
} from '#angular/router';
#Component({
selector: 'hello',
templateUrl: './hello.html',
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
message: string;
printedMessage: string
#Input() elm: string;
constructor(private data: DataService, private router: Router) {}
ngOnInit() {
this.message = this.data.messageSource.value;
this.data.messageSource.subscribe(message => this.message = message.elem === this.elm ? message.message : this.message);
}
updateService() {
debugger
this.data.changeMessage(this.message, this.elm);
this.printedMessage = this.data.messageSource.value.message;
}
navigateToSibling() {
this.router.navigate(['/sibling']);
}
}
Have also updated the Stackblitz Demo. Hope this helps :)
I am trying to implement a functionality which will display a list of nodes selected from the TreeNode structure, on the right.
Something similar to this plunkr I found : http://next.plnkr.co/edit/1Fr83XHkY0bWd9IzOwuT?p=preview&utm_source=legacy&utm_medium=worker&utm_campaign=next&preview
In my current method, I am getting the right-side list to be in the same hierarchy order as that on the left.
For eg : Even if I select node "B" before node "A", in the list, it still shows first A and then B (because A is above B in the displayed hierarchy).
Rightside.component.html :
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<ul class="selection-list">
<li *ngFor="let item of getSelections()">
<button class="btn" (click)="deselect(item)" *ngIf="item.selected">
<i class="fa fa-close"> {{ item.displayName }} </i>
</button>
</li>
</ul>
rightside.component.ts:
import { Component, Input, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '#angular/core';
import { DataService } from '../../shared/service/data.service';
import { TreeNode } from '../../shared/dto/TreeNode';
import html from './rightside.component.html';
import css from './rightside.component.css';
#Component({
selector: 'rightside-component',
template: html,
providers: [DataService],
styles: [css],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RightSideComponent implements OnInit {
selections: string[];
#Input() treeNode: TreeNode<string>[];
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
}
getSelections() : TreeNode<string>[] {
if (typeof(this.treeNode) == "undefined" || (this.treeNode) === null) {
return [];
}
return this.treeNode;
}
deselect(item: TreeNode<string>): void {
if((item.children) !== null) {
item.children.forEach(element => {
this.deselect(element);
});
}
item.selected = false;
}
}
productline.component.html:
<p>Productlines</p>
<mat-input-container>
<input #searchInput matInput placeholder="Search for Product Line">
</mat-input-container>
<div class="flex-container">
<div class="PLCheck" *ngIf="isDataLoaded">
<fortune-select
(dataChanged)="onDataChange($event)"
[searchTerm]="searchInput.value"
[currentNode]="tree"
[singleSelect]="singleSelect"
[collapsed]="true"></fortune-select>
</div>
<div class="sendToRight">
<rightside-component
[treeNode]="selectedProductLine">
</rightside-component>
</div>
</div>
Productline.component.ts:
import { Component, Input, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '#angular/core';
import * as API from '../../shared/api-routes';
import { DataService } from '../../shared/service/data.service';
import { ValidationService } from '../../shared/service/validation.service';
import { Subject } from 'rxjs/Subject';
import { BasestepComponent } from '../basestep-component/basestep.component';
import { TreeNode } from '../../shared/dto/TreeNode';
import html from './productline.component.html';
import css from './productline.component.css';
#Component({
selector: 'productline-component',
template: html,
providers: [DataService],
styles: [css],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductlineComponent extends BasestepComponent<string> implements OnInit {
selectedProductLine: TreeNode<string>[];
constructor(dataService:DataService, cd:ChangeDetectorRef) {
super(dataService, cd);
}
ngOnInit() {
this.subject.subscribe((productline) => this.productLineChange(productline));
}
public productLineChange(productLine: TreeNode<string>[]):void {
this.selectedProductLine = productLine;
}
}
Basestep.component.ts:
import { Input, ChangeDetectorRef } from '#angular/core';
import { castTree, ReportTemplate } from '../report-common';
import { DataService } from '../../shared/service/data.service';
import { Subject } from 'rxjs/Subject';
import { ValidationService } from '../../shared/service/validation.service';
import { TreeNode } from '../../shared/dto/TreeNode';
export class BasestepComponent<T> {
public tree: TreeNode<T>;
public singleSelect: boolean = false;
public isDataLoaded: boolean;
public template: ReportTemplate;
#Input() templateSubject: Subject<ReportTemplate>;
#Input() subject: Subject<TreeNode<T>[]>;
constructor(private dataService:DataService, private cd: ChangeDetectorRef) {}
public onDataChange(event:TreeNode<T>[]):void {
if (!ValidationService.isNullOrUndefined(this.subject)) {
this.subject.next(event);
}
}
public markForCheck() {
this.cd.markForCheck();
}
public reset() {
this.tree = null;
this.isDataLoaded = false;
this.markForCheck();
}
}
I want the order on the right to be in the order in which the items are selected and not based on the hierarchy. Hence, even if A is above B in the hierarchy, I would want B to be shown and then A based on the selections.
Basically, I would like the selections to be displayed dynamically in the order that they are chosen.
EDIT 1:
Adding code for fortune-select.component.ts:
import { Component, Input, Output, EventEmitter } from '#angular/core';
import { TreeNode } from './../dto/TreeNode';
import html from './fortune-select.component.html';
import css from './fortune-select.component.css';
#Component({
selector: 'fortune-select',
template: html,
styles: [css],
})
export class FortuneSelectComponent<T> {
#Input() currentNode: TreeNode<T>;
#Input() collapsed: boolean = false;
#Input() linked: boolean = true;
#Input() searchTerm: string = '';
#Input() singleSelect: boolean = false;
#Output() dataChanged = new EventEmitter<TreeNode<T>[]>();
selectedRadioValue:T;
public checkboxChangedEvent():void {
let list:TreeNode<T>[];
this.currentNode.indeterminate = true;
if (this.linked) {
list = TreeNode.getTopLevelCheckedNodes(this.currentNode);
} else {
list = TreeNode.getAllCheckedNodes(this.currentNode);
}
this.dataChanged.emit(list);
}
public radioChangedEvent(event:TreeNode<T>):void {
this.dataChanged.emit([event]);
}
}
Is there a way I could maybe store every selection in a list and then display the list? That way, it will show the selections in order. Right now, the entire structure is getting traversed, hence it displays in the tree's order.
Is there a way to do this?