Ionic 4: 'Typescript error' in helperService Cannot read property 'length' of undefined at HelperService - arrays

Getting error 'Cannot read property 'length' of undefined at HelperService.addCommasToArray' when trying to loop through an array that has been passed as a paramter in a helperService class [Typescript]
I'm really not sure why this is not working - I believe it should be straightforward - all I'm trying to do is pass in an array as a parameter and add a ',' to every value in the array (except the last value)
Here is my HelperService Class method:
export class HelperService {
constructor() { }
/*
* Add commas to every value in the array except for the last value
*/
addCommasToArray(array: Array<any>) : Array<any> {
for (let i = 0; array.length; i++){
array[i] += ", ";
}
return array;
}
}
I then call this method within the ngInit of another ts class
this.helperService.addCommasToArray(this.previousClubs);
Here is the ngInit method
public previousClubs: Array<any>;
constructor(private playersService: PlayersService,private
helperService: HelperService, private route: ActivatedRoute) { }
ngOnInit() {
const playerId: string = this.route.snapshot.paramMap.get('id');
this.playersService.getPlayerDetails(playerId).get()
.then(playerDetailsSnapshot=> {
this.currentPlayerDetails = playerDetailsSnapshot.data();
this.currentPlayerDetails.id = playerDetailsSnapshot.id;
});
/*
* Return Previous Clubs
*/
this.playersService.getPreviousClubs(playerId).get().then(
previousClubsSnapshot =>{
this.previousClubs = [];
previousClubsSnapshot.forEach(snap => {
this.previousClubs.push({
id: snap.id,
name: snap.data().name,
});
return false;
});
});
this.helperService.addCommasToArray(this.previousClubs);
}

so here:
this.playersService.getPreviousClubs(playerId).get().then(
previousClubsSnapshot =>{
this.previousClubs = [];
previousClubsSnapshot.forEach(snap => {
this.previousClubs.push({
id: snap.id,
name: snap.data().name,
});
return false;
});
});
// this line executes without awaiting for .then enclosed scope
this.helperService.addCommasToArray(this.previousClubs);
Basically you call addCommasToArray even before your previousClubs var gets array assigned to it and then gets all its items pushed in. To fix since your method is (.then) async you need to call for this method inside the .then execution scope:
ngOnInit() {
const playerId: string = this.route.snapshot.paramMap.get('id');
this.playersService.getPlayerDetails(playerId).get()
.then(playerDetailsSnapshot=> {
this.currentPlayerDetails = playerDetailsSnapshot.data();
this.currentPlayerDetails.id = playerDetailsSnapshot.id;
});
/*
* Return Previous Clubs
*/
this.playersService.getPreviousClubs(playerId).get().then(
previousClubsSnapshot =>{
this.previousClubs = [];
previousClubsSnapshot.forEach(snap => {
this.previousClubs.push({
id: snap.id,
name: snap.data().name,
});
return false;
});
});
this.helperService.addCommasToArray(this.previousClubs);
}

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.

Typescript: How to access and update class variable from public function

I am trying to access value of a class variable in function setRating() but the console print is "undefined".
export class UserFeedbackComponent implements OnInit {
rating: number;
constructor() {
this.rating = 3;
}
ngOnInit() {
//initial setup
console.log("Rating " + this.rating);
document.addEventListener('DOMContentLoaded', function() {
let stars = document.querySelectorAll('.star');
stars.forEach(function(star) {
star.addEventListener('click', setRating);
});
let temps = parseInt(document.querySelector('.stars').getAttribute('data-rating'));
console.log("Rating 2: " + this.rating);
let target = stars[temps - 1];
target.dispatchEvent(new MouseEvent('click'));
});
}
function setRating(ev) {
//Printing 'undefined' in console.log
console.log('At top: ' + this.rating);
let span = ev.currentTarget;
let stars = document.querySelectorAll('.star');
let match = false;
let num = 0;
stars.forEach(function(star, index) {
if (match) {
star.classList.remove('rated');
} else {
star.classList.add('rated');
}
//are we currently looking at the span that was clicked
if (star === span) {
match = true;
num = index + 1;
}
});
this.rating = num;
console.log("value after update: " + this.rating)
document.querySelector('.stars').setAttribute('data-rating', num.toString());
}
}
the "value after update: " console log prints "undefined" unless this.rating is assigned to num. Can someone please help me with how to access the value of rating variable in setRating() function and how to update its value?
It's a context binding issue, you have to bind the setRating function to the class this otherwise it is going to use its own this which is different than the classes this no having access to this.rating. You can achieve this by using setRating.bind(this).
You can start by changing the DOMContentLoaded to an arrow function so that you inherit the context's this like so:
document.addEventListener('DOMContentLoaded', () => {
// this.rating is visible here now
...
})
Then you can do the same to the forEach handler:
stars.forEach((star) => {
// this.rating is visible here now too
...
});
Finally, you can bind the this of your external function to the classes this:
star.addEventListener('click', setRating.bind(this));
Your final code would be something like bellow:
export class UserFeedbackComponent implements OnInit {
rating: number;
constructor() {
this.rating = 3;
}
ngOnInit() {
//initial setup
console.log("Rating " + this.rating);
document.addEventListener('DOMContentLoaded', () => {
let stars = document.querySelectorAll('.star');
stars.forEach((star) => {
star.addEventListener('click', setRating.bind(this));
});
let temps = parseInt(document.querySelector('.stars').getAttribute('data-rating'));
console.log("Rating 2: " + this.rating);
let target = stars[temps - 1];
target.dispatchEvent(new MouseEvent('click'));
});
}
function setRating(ev) {
//Printing 'undefined' in console.log
console.log('At top: ' + this.rating);
let span = ev.currentTarget;
let stars = document.querySelectorAll('.star');
let match = false;
let num = 0;
stars.forEach(function(star, index) {
if (match) {
star.classList.remove('rated');
} else {
star.classList.add('rated');
}
//are we currently looking at the span that was clicked
if (star === span) {
match = true;
num = index + 1;
}
});
this.rating = num;
console.log("value after update: " + this.rating)
document.querySelector('.stars').setAttribute('data-rating', num.toString());
}
}
Further observation: You are declaring a function inside a class, that is totally unnecessary, you can declare it as a member
export class UserFeedbackComponent implements OnInit {
...
setRating(ev) {
...
}
}
Then you don't even ave to bind it to call it like so:
star.addEventListener('click', ev => this.setRating(ev));
You do not have to define the function using the function keyword in the class. Declare it as a class member and you should be able to access it normally. This is the best practice in Typescript class declaration.
EX:
export class UserFeedbackComponent implements OnInit {
rating: number;
constructor() {
this.rating = 3;
}
ngOnInit() {
//initial setup
console.log("Rating " + this.rating);
......................more code............
}
setRating(ev) {
//Printing 'undefined' in console.log
console.log(this.rating); //should work fine
// do other stuff with class members
}
}

Access object values in array nested in JSON object

I have a GET request that sends back a JSON object structured like this:
{"ID":5176,
"DateFrom":"8/29/2018",
"DateTo":"8/29/2018",
"Units":[{"Key":"Value","Key2": "Value2"}]
}
I cannot access Units[0] value. I have tried the following:
testFunction(){
this.service.getJSON(params)
.subscribe((data) => {
this.model = data;
this.dateFrom = this.model.DateFrom;
this.dateTo = this.model.DateTo;
this.unit = this.model.Units.Key;
console.log(this.unit); //undefined
});
}
}
ngOnInit() {
this.testFunction();
}
What am I missing?
You should use
this.unit = this.model.Units[0].Key;
instead of
this.unit = this.model.Units.Key;
since Units.Key is undefined
Since Units is an array of JSONs and the array contains only one element.
It should be accessed by Units[0].
Units[0] is now a JSON, Key property is needed now.
There are 2 ways to access it
Units[0].Key
Units[0]['Key']
The final code should look like
testFunction(){
this.service.getJSON(params)
.subscribe((data) => {
this.model = data;
this.dateFrom = this.model.DateFrom;
this.dateTo = this.model.DateTo;
//first method
//this.unit = this.model.Units[0].Key;
// second method
// this.unit = this.model.Units[0]['Key'];
console.log(this.unit); //undefined
});
}
}
ngOnInit() {
this.testFunction();
}

ngShow expression is evaluated too early

I have a angular component and controller that look like this:
export class MyController{
static $inject = [MyService.serviceId];
public elements: Array<string>;
public errorReceived : boolean;
private elementsService: MyService;
constructor(private $elementsService: MyService) {
this.errorReceived = false;
this.elementsService= $elementsService;
}
public $onInit = () => {
this.elements = this.getElements();
console.log("tiles: " + this.elements);
}
private getElements(): Array<string> {
let result: Array<string> = [];
this.elementsService.getElements().then((response) => {
result = response.data;
console.log(result);
}).catch(() => {
this.errorReceived = true;
});
console.log(result);
return result;
}
}
export class MyComponent implements ng.IComponentOptions {
static componentId = 'myId';
controller = MyController;
controllerAs = 'vm';
templateUrl = $partial => $partial.getPath('site.html');
}
MyService implementation looks like this:
export class MyService {
static serviceId = 'myService';
private http: ng.IHttpService;
constructor(private $http: ng.IHttpService) {
this.http = $http;
}
public getElements(): ng.IPromise<{}> {
return this.http.get('./rest/elements');
}
}
The problem that I face is that the array elements contains an empty array after the call of onInit(). However, later, I see that data was received since the success function in getELements() is called and the elements are written to the console.
elements I used in my template to decide whether a specific element should be shown:
<div>
<elements ng-show="vm.elements.indexOf('A') != -1"></elements>
</div>
The problem now is that vm.elements first contains an empty array, and only later, the array is filled with the actual value. But then this expression in the template has already been evaluated. How can I change that?
Your current implementation doesn't make sense. You need to understand how promises and asynchronous constructs work in this language in order to achieve your goal. Fortunately this isn't too hard.
The problem with your current implementation is that your init method immediately returns an empty array. It doesn't return the result of the service call so the property in your controller is simply bound again to an empty array which is not what you want.
Consider the following instead:
export class MyController {
elements: string[] = [];
$onInit = () => {
this.getElements()
.then(elements => {
this.elements = elements;
});
};
getElements() {
return this.elementsService
.getElements()
.then(response => response.data)
.catch(() => {
this.errorReceived = true;
});
}
}
You can make this more readable by leveraging async/await
export class MyController {
elements: string[] = [];
$onInit = async () => {
this.elements = await this.getElements();
};
async getElements() {
try {
const {data} = await this.elementsService.getElements();
return data;
}
catch {
this.errorReceived = true;
}
}
}
Notice how the above enables the use of standard try/catch syntax. This is one of the many advantages of async/await.
One more thing worth noting is that your data services should unwrap the response, the data property, and return that so that your controller is not concerned with the semantics of the HTTP service.

Why my array is still empty even though I had assign value to this array in my controller? (I am using angularjs, not angular)

This question took me one day to debug it, but still no luck.
Problem: this.gridOptions.data = this.allTemplatesFromClassificationRepo ;
**this.allTemplatesFromClassificationRepo ** is still a empty array. Before this line of code executes, I have call activate() function to assign array to **this.allTemplatesFromClassificationRepo **. Please see this line this.allTemplatesFromClassificationRepo = templateReorderProperties;
P.S. Although I cannot get the value of allTemplatesFromClassificationRepo in controller, but I can get the value of it in html.
namespace app.admin {
'use strict';
class FooController {
static $inject: Array<string> = [];
constructor() {
this.activate();
}
activate() {
this.getData();
this.classificationFoo = this.data[0];
this.getTemplateFromGivenRepo(this.classificationFoo.id, this.classificationFoo.displayName);
this.populateData();
}
data: any;
classificationFoo: any;
allDataFromclassificationFoo: any = [];
// demo grid
gridOptions = {
enableFiltering: true,
},
data: []
};
populateData() {
this.gridOptions.data = this.allTemplatesFromClassificationRepo ;
}
getData() {
this.fooService.getUserData();
this.data = this.fooService.userdata;
}
getTemplateFromGivenRepo(fooId: string, fooName: string) {
switch (fooId) {
case 'FOO':
this.TemplateApi.templatesAvaiableForRepoIdGET(fooId).then(data => {
data.forEach(element => {
element.fooName = fooName;
});
let templateReorderProperties = data
this.allTemplatesFromClassificationRepo = templateReorderProperties;
}, error => {
});
break;
default:
break;
}
};
}
class Bar implements ng.IDirective {
static $inject: Array<string> = [];
constructor() {
}
bindToController: boolean = true;
controller = FooController;
controllerAs: string = 'vm';
templateUrl: string = 'app/foo.html';
static instance(): ng.IDirective {
return new Bar();
}
}
angular
.module('app.admin')
.directive('bar', Bar.instance);
}
getTemplateFromGivenRepo is async operation.
Move this.populateGridData(); call inside getTemplateFromGivenRepo after
this.allTemplatesFromClassificationRepo = templateReorderProperties;
getTemplateFromGivenRepo(repoId: string, repoName: string) {
switch (repoId) {
case 'CLASSIFICATION':
this.TemplateApi.templatesAvaiableForRepoIdGET(repoId).then(data => {
this.allTemplatesFromClassificationRepo = templateReorderProperties;
this.populateGridData(); // call here
}, error => {
});
}
};
OR
You can return promise from getTemplateFromGivenRepo and in then able success callback,call
this.populateGridData();
I think the problem is in this instance that different inside Promise resolve.
Try to write in beginning something like:
var self = this;
and after that change all this to self key.
a.e.:
self.gridOptions.data = self.allTemplatesFromClassificationRepo;
// and so on ...
By this way you will guarantee that you use same scope instance
Hope it will work

Resources