How to pass an array from one component to its sub component - arrays

After googling lots of related answers and tries, I seemly have to seek help here.
I have an angular application, one of its components named stock-subscribe, which is used to display the feeTypes that a customer subscribed. Within this component, I created another component, stock-addsubs, used to display the feeTypes that are not yet subscribed by this customer.
enter image description here
Obviously, the two feeType lists can compose one whole list. From stock-subscribe, I can get an array, subscribedFeeIds, which holds all the ids of those subscribed feeTypes.
My requirement is to pass this array, subscribedFeeIds, to the stock-addsubs component so that I can filter out those yet unsubscribed feeTypes based on those ids of the array.
To my best understanding, the data passing from one component to its sub component should be a simple process and neither of two component html templates should be involved for my case. Of the many googled solutions, using #Output and #Input seems the simpler than event emitting. However, none can successfully pass the elements of the array in the sub component.
I can get the expected id list (subscribedFeeIds[]) from the stock-subscribe component, and all the rest code work fine so long as the array passed to the sub component is not EMPTY.
1) stock-subscribe.component.ts
#Component({
selector: 'app-stock-subscribe',
templateUrl: './stock-subscribe.component.html',
styleUrls: ['./stock-subscribe.component.scss']
})
export class StockSubscribeComponent implements OnInit {
userSubscribe: ISubscribe;
#Output() subscribedFeeIds: any = [];
listData: MatTableDataSource<any>;
constructor(private accountService: AccountService,
private stockService: StockService) { }
ngOnInit(): void {
this.createSubsList();
}
createSubsList() {
this.accountService.getUserAccount()
.subscribe(account => {
let userId = account.id.toString();
this.stockService.getUserSubscribes(userId).subscribe((data: ISubscribe[]) => {
// get the id of subscribed fee type and thenpublish to other component
for (var i = 0; i < data.length; i++)
{
if (data[i].status)
this.subscribedFeeIds.push(data[i].fees[0].id);
}
console.log(this.subscribedFeeIds);
// prepare the list source for display
this.listData = new MatTableDataSource(data);
this.listData.sort = this.sort;
}, error => {
console.log(error);
});
}, error => {
console.log(error);
});
}
}
2) stock-addsubs.component.ts
#Component({
selector: 'app-stock-addsubs',
templateUrl: './stock-addsubs.component.html',
styleUrls: ['./stock-addsubs.component.scss']
})
export class StockAddsubsComponent implements OnInit {
listData: MatTableDataSource<any>;
#Input() subscribedFeeIds: any [];
constructor(private feesService: FeesService) { }
ngOnInit(): void {
this.createFeeList();
}
createFeeList() {
this.feesService.getFeeList().subscribe((data: IFee[]) => {
// filter those already subscribed
for (var i = 0; i < this.subscribedFeeIds.length; i++)
{
for (var j = 0; j < data.length; j++)
{
data = data.filter(f => f.id != this.subscribedFeeIds[i]);
}
}
// prepare data to display
this.listData = new MatTableDataSource(data);
}, error => {
console.log(error);
});
}
}

You can implement either one of these methods:
1.) Enhance your #Input and #Output based on your code above:
stock-subscribe.component.ts
#Component({...})
export class StockSubscribeComponent implements OnInit {
#Output() subscribedFeeIds: EventEmitter<any> = new EventEmitter<any>();
list: any[] = []; // To store your ids, do not use the #Output variable above to push things inside a loop, you may not want to continuously emit data for an nth number of the sequence.
...
createSubsList() {
...
for (var i = 0; i < data.length; i++) {
...
if (data[i].status)
this.list.push(data[i].fees[0].id); // Push IDs on this.list not on this.subscribedFeeIds
}
this.subscribedFeeIds.emit(this.list); // After the loop finishes, emit the final list value
// Do not use .push() on #Output variable, this is an emission variable so use .emit()
}
}
or you could also do any of these methods when handling your array list:
// METHOD #1
this.filteredIds = [];
for(const { status, fees } of FEES_DATA) {
if (status) filteredIds.push(fees[0].id);
}
OR
// METHOD #2
this.filteredIds = FEES_DATA
.filter(({ status }) => status)
.map(({ fees }) => fees[0].id);
// Then emit the filtered ids
this.list.emit(this.filteredIds);
stock-addsubs.component.ts
#Component({...})
export class StockAddsubsComponent implements OnInit {
// Since you are waiting for emission from StockSubscribeComponent which the
// emission also waits till the loop is finished based on the code above, better to
// implement the #Input like this so as to avoid processing a an empty list
#Input() set subscribedFeeIds(list: any[]) {
if (list && list.length) { // If the list now has items, call createFeeList() function
this.createFeeList();
}
}
...
}
____
or
#Component({...})
export class StockAddsubsComponent implements OnInit, OnChanges { // Add OnChanges
#Input() subscribedFeeIds: any [];
ngOnChanges({ subscribedFeeIds }: SimpleChanges) { // SimpleChanges from #angular/core
if (subscribedFeeIds && subscribedFeeIds.currentValue && subscribedFeeIds.currentValue.length) {
this.createFeeList();
}
// You can also console the subscribedFeeIds and check it's value or changes
console.log(subscribedFeeIds);
}
}
Have created a Stackblitz Demo for your reference
2.) Use RxJS Subject or BehaviorSubject to emit data
Use this Stackblitz Demo for your reference.

Related

Angular decrement value in array

I try to decrement a value in my array, but I can't get it to work.
My array data contains attributes and everytime a method gets clicked, I call that value from a service and increment it in the array object. The getter is equal to amountCounter.
My main problem is that whenever I try to remove an array object, my amountCounter won't also decrement the value which it had before, but the array object gets removed.
I also put two pictures to better clarify my problem, thank you so much for every help.
app.component.html
<h2>Add values of my service into array:</h2>
<p>Array:</p>
<p>Total: {{amountCounter}}</p>
<div *ngFor="let item of data, let i = index;">
<span>ID: {{item.id}}</span>
<span>Title: {{item.title}}</span>
<span (click)="removeElement(i, item.amountCounter)" class="material-icons">
close
</span>
</div>
app.component.ts
export class AppComponent {
clickEventsubscription: Subscription
ngOnInit() {
}
id: number;
title: String;
amountCounter: number;
data: any = [];
constructor(private share: ShareDataService) {
this.clickEventsubscription = this.share.getClickEvent().subscribe(() => {
this.initialize();
})
}
removeElement(id: number, counter: number) {
this.data.splice(id, 1);
this.amountCounter -= counter //In that line I can't get it to work that my attribute decrements
console.log("before" + this.amountCounter);
console.log("after:" + counter);
}
initialize() {
this.id = this.share.getId();
this.title = this.share.getTitle();
this.amountCounter = this.share.getAmountCounter();
const newData = {
id: this.id,
title: this.title,
amountCounter: this.amountCounter
};
this.data.push(newData);
console.log(this.data);
}
}
share-data.service.ts
export class ShareDataService {
private subject = new Subject<any>();
title: String;
id: number;
amountCounter: number;
getId() {
return this.id;
}
getTitle() {
return this.title;
}
getAmountCounter(){
return this.amountCounter;
}
sendClickEvent() {
this.subject.next();
}
getClickEvent(): Observable<any> {
return this.subject.asObservable();
}
}
That is how my array looks before ID 1 is clicked
That is how my array looks after I clicked at "X", but it decrements wrong
Thank you so much!
Not sure if this is the behavior you are after but generally this method will calculate the sum of the array values
getTotalAmount(): number {
return this.data.reduce((acc, item) => acc + item.amount, 0);
}
The main issue I found very difficult to figure out is that you have amountCounter in [share-data.service, dialog.component, app.component]
I suppose you want to add new items using dialog.component with different amount values.
Here you add new item to your 'data' array, the values for single item comes from share service which was updated in your dialog.component
initialize() {
console.log("initialize");
const id = this.share.getId();
const title = this.share.getTitle();
const amount = this.share.getAmount();
const newData = {
id,
title,
amount
};
this.data.push(newData);
}
To summarize the flow:
in dialog.component you update field values in share-data.service clickMe() method
that method will trigger a method in app.component called initialize which will add the new item to the this.data array.
if you click on item (to remove it) splice will do it, and Angular will refresh the Total calling the getTotalAmount method
Working Stackblitz.

Why Can't Iterate over an array in my model using the map() function

i have angular 7 component which is tied to a model and there is an array inside that model, the array was populated from a service. and it's populated.
the problem is i can't map over the array although it has elements there.
when i console it it shows the array has element. then i tried to console typeOf(array) it always gives object although it is an array !!.
i tried using this soluation but it didn't help either.
any help please?
export class FooModel {
foo : Foo
bars: Bar[];
}
export class SomeComponent implements OnInit {
model: FooModel;
constructor(private service: ProjectService) {
this.model = new FooModel();
this.model.bars = [];
}
ngOnInit() {
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
});
Console.log(this.model); // the model has data at this point
const arr = this.model.bars.map(a=> {
// never comes here
return a;
});
console.log(arr); // nothing is displayed here
// this works why ??
const arr2 = [1,2,3].map(s=> {
return s;
}
console.log(arr2); // it displays [1,2,3]
}
}
As the request is asynchronous, you might need to place the logic within the subscribe,
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
const arr = this.model.bars.map(a=> {
// never comes here
return a;
});
console.log(arr);
});
subscription is asynchronous so while it is still working the next line operation in the execution stack will be performed in this case the map you have after the subscription meanwhile it is still being populated in the background. You can try mapping in another life cycle hook say viewChecked hopefully it works. #cheers
Please look at the comments
export class FooModel {
foo : Foo
bars: Bar[];
}
export class SomeComponent implements OnInit {
model: FooModel;
constructor(private service: ProjectService) {
this.model = new FooModel();
this.model.bars = [];
}
ngOnInit() {
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
});
// the following starts to execute even before the model is populated above.
const arr = this.model.bars.map(a=> {
// never comes here because this.model.bars is empty at here and the length is 0 and nothing inside map executes
return a;
});
console.log(arr); // nothing is displayed here because it has nothing inside
// this works why ?? because you are using on an array which has some items.
const arr2 = [1,2,3].map(s=> {
return s;
}
console.log(arr2); // it displays [1,2,3]
}
}
So as Sajeetharan suggested, you have keep it inside subscribe()

Angular 4 Observable on Array in Service

I have an interface that implements an array in a service:
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
interface Artwork {
artTitle: string;
slideUrl?: string;
}
let Artwks: Artwork [] = [];
Artwks.push({
artTitle: "'Surprising Breakfast Ideas'",
slideUrl: './assets/SBI_Slide_1.jpg',
});
Artwks.push({
artTitle: "'Eagle'",
slideUrl: './assets/Eagle_Slide_2.jpg',
});
Artwks.push({
artTitle: "'Knot'",
slideUrl: './assets/Knot_Slide_3.jpg',
});
#Injectable()
export class ImageServiceService {
Artwks = Artwks;
i: number = 0;
getIndex() {
return this.i ;
}
getSlide() {
return this.Artwks[this.i].slideUrl;
}
getPrev() {
this.i = this.i===0 ? 0 : this.i - 1;
this.getTitle();
console.log(this.getTitle());
this.getSlide();
}
getNext() {
this.i = this.i===this.Artwks.length ? this.i : this.i + 1;
this.getTitle();
console.log(this.getTitle());
this.getSlide();
}
getTitle(): Observable<Artwork.artTitle> {
return this.Artwks[this.i].artTitle;
}
}
I want a component that displays the title of the array when getNext() or getPrev() is called on the service, but I don't know how to set up my observable so it updates the value of artTitle every time it changes?
I get error "'Artwork' only refers to a type, but is being used as a namespace here"
You are returning a string from getTitle() so your type should be string.
getTitle(): string { //.etc
EDIT:
If you want to use an observable, you need to make an observable and return it somewhere. I'm not sure how to do this without knowing how your class is used, but the basic idea looks like this:
Create and subject observable:
import {Subject} from 'rxjs/Subject';
export class ImageServiceService {
public artworks:Subject<Artwork> = new Subject();
private index: number = 0
getArt(){
this.artworks.next(this.Artwks[this.index])
this.index++
}
// etc.
}
Then in your component you can subscribe to the observable:
constructor(private imageService: ImageServiceService){
this.artworks = imageService.artworks
this.artworks.subscribe(v => // do something with it)
// artworks should update with the subject calls next()
}

Angular 2 + Ionic 2: objects push to array, but it is empty when I try to use it

I'm trying to do an application with Ionic2 with imports questions from a JSON file and display them in a test. I was able to successfully import all the questions in the array made of 'uniqueChoiceQuestion' objects. However, when I try to construct the 'questionList' array, the values I pushed into it disappear when I try to use it in another function.
My test.ts
import {Component, OnInit, Input} from '#angular/core';
import {NavController, NavParams} from 'ionic-angular';
/*----- Question Classes -----*/
import {UniqueChoiceQuestion} from './../../classes/questions/uniqueChoiceQuestion/uniqueChoiceQuestion';
//Has id: number, content: string, options: Array<any>
import {QuestionList} from './../../classes/questions/questionList/questionList'
//Has id: number; type: string;
import {GetList} from './../../services/getList/getList';
#Component({
templateUrl: 'build/pages/test/test.html',
providers: [GetList]
})
export class TestPage implements OnInit {
/*----- Questions array -----*/
public questionsUnique: Array<UniqueChoiceQuestion>;
public questionList: Array<QuestionList>;
public firstQuestion: any;
constructor(private navController: NavController, private getList: GetList) {
}
/* On page init,
the questions array will be initialised,
the questions will be loaded on the arrays
*/
ngOnInit() {
this.setupArrays();
this.loadQuestions();
this.loadFirstQuestion();
}
/* Initializes the questions arrays as empty
*/
setupArrays() {
this.questionsUnique = [];
this.questionList = [];
}
/* Load the JSON data into the correct type of array
*/
processQuestionsData(data) {
for (let i = 0; i < data.length; i++) {
let question = data[i];
this["questions" + question.type].push(question);
this["setUpQuestion" + question.type](this["questions" + question.type].length - 1);
this.questionList.push(new QuestionList(question.id, question.type));
}
//Here, the array is printed correctly with all the questions ids and types
console.log(this.questionList);
}
/* Load data into a UniqueChoiceQuestion array
*/
setUpQuestionUnique(arrayId) {
let container = this.questionsUnique[arrayId];
container.viewConstruct = new UniqueChoiceQuestion(
container.id,
container.content,
container.options
);
}
/* Loads questions from a JSON files
*/
loadQuestions() {
this.getList.load()
.subscribe(
this.processQuestionsData.bind(this),
error => console.log("Test - loadQuestions() - Error: ", error)
);
}
loadFirstQuestion() {
//However, when I try to print the array here, it is empty
console.log(this.questionList);
//That will generate a 'TypeError: Cannot read property 'type' of undefined' error
let firstQuestion = this["questions" + this.questionList[0].type][this.questionList[0].id];
this["setUpQuestion" + this.questionList[0].type](this.questionList[0].id);
console.log(this.firstQuestion);
}
UPDATE
I tried debbuging the program more and now I believe the problem is that this.loadFirstQuestion() is being executed before this.loadQuestions() (on ngOnInit)
Load questions is asynchronous. So it starts by executing the async call, doesnt wait for it to complete, then goes to execute loadFirstQuestion. Here you don't get the question list because it isn't loaded yet. You can try calling loadfirstquestion in subscribe.
.subscribe(
this.processQuestionsData.bind(this),
error => console.log("Test - loadQuestions() - Error: ", error),
()=>this.loadFirstQuestion();
);

angular2 observables filter each array element than return it to an array

I have an array of Threads Objects with ID, title and a isBookmarked:boolean.
I've created a Subject and I want to subscribe to it in order to get an array of Thread Objects with isBookmarked=true.
https://plnkr.co/edit/IFGiM8KoSYW6G0kjGDdY?p=preview
Inside the Service I have
export class Service {
threadlist:Thread[] = [
new Thread(1,'Thread 1',false),
new Thread(2,'Thread 2',true),
new Thread(3,'Thread 3',false),
new Thread(4,'Thread 4',true),
new Thread(5,'Thread 5',true),
new Thread(6,'Thread 6',false),
new Thread(7,'Thread 7',false),
]
threadlist$:Subject<Thread[]> = new Subject<Thread[]>()
update() {
this.threadlist$.next(this.threadlist)
}
}
in the component
export class AppComponent implements OnInit {
localThreadlist:Thread[];
localThreadlistFiltered:Thread[];
constructor(private _service:Service){}
ngOnInit():any{
//This updates the view with the full list
this._service.threadlist$.subscribe( threadlist => {
this.localThreadlist = threadlist;
})
//here only if isBookmarked = true
this._service.threadlist$
.from(threadlist)//????
.filter(thread => thread.isBookmarked == true)
.toArray()
.subscribe( threadlist => {
this.localThreadlistFiltered = threadlist;
})
}
update() {
this._service.update();
}
}
which Instance Method do I use in general to split an array?
Also is there a better way to do it?
Thanks
You would leverage the filter method of JavaScript array within the map operator of observables:
this._service.threadlist$
.map((threads) => {
return threads.filter((thead) => thread.isBookmarked);
})
.subscribe( threadlist => {
this.localThreadlistFiltered = threadlist;
});
See this plunkr: https://plnkr.co/edit/COaal3rLHnLJX4QmvkqC?p=preview.

Resources