Not a Function Issue - javascript

I'm facing off a classic "Not a Function" issue on Ionic Framework, I hope that you will help me to better understand this case.
I am trying to get out an object from an array. The object has private properties that I retrieve with simple getter methods.
The problem comes out when this line of the whole class above is executed:
this.selectedWorkoutPlan = this.workoutPlanList.find(object => object.getId() === this.id);
The console returns the error mentioned before.
This is my .ts file:
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import {WorkoutPlanService} from "../../services/workout-plan.service";
import {WorkoutPlan} from "../../models/workout-plan";
#IonicPage()
#Component({
selector: 'page-workout-plan-detail',
templateUrl: 'workout-plan-detail.html',
})
export class WorkoutPlanDetailPage {
id: number = 0;
title: string = "";
startDate: Date = new Date();
endDate: Date = new Date();
workoutPlanList: WorkoutPlan[] = [];
selectedWorkoutPlan: WorkoutPlan = new WorkoutPlan();
constructor(public navCtrl: NavController, public navParams: NavParams,
private workoutPlanService: WorkoutPlanService) {
}
ionViewDidLoad() {
this.id = this.navParams.get("id");
this.workoutPlanList = this.workoutPlanService.getWorkoutPlanList();
this.selectedWorkoutPlan = this.workoutPlanList.find(object => object.getId() === this.id);
}
}
and this is the .ts model's class.
import {WorkoutExercise} from "./workout-exercise";
export class WorkoutPlan {
private title: string;
private exercises: WorkoutExercise[];
private startDate: Date;
private endDate: Date;
private id: number;
constructor() {
this.id = new Date().getTime();
}
setTitle(newTitle: string) {
this.title = newTitle;
}
setExercises(newExercises: WorkoutExercise[]) {
this.exercises = newExercises;
}
setStartDate(newStartDate: Date) {
this.startDate = new Date(newStartDate);
}
setEndDate(newEndDate: Date) {
this.endDate = new Date(newEndDate);
}
setId(newId: number) {
this.id = newId;
}
getTitle() {
return this.title;
}
getExercises() {
return this.exercises;
}
getStartDate() {
return this.startDate;
}
getEndDate() {
return this.endDate;
}
getId() {
return this.id;
}
}
Am I missing something in the syntax?
In my mind I thought than an array's object would have properties and methods both, isn't it?
Thanks in advance for your time.

My guess is that you might be serializing your WorkOutPlan objects somewhere within WorkoutPlanService and then parsing the strings back into WorkOutPlan objects, which causes you to lose the associated methods.
Are you stringifying those objects at any time?

Related

Angular Component: Impossible to loop through an array of object with TypeScypt

can any one please tell me why I can not loop through this array?
In ngOnInit, everything works fine. I got an array that I successfully display in the template.
But in ngAfterViewInit, console.log show the array but when looping through with "for of" or "forEach", nothing works.
import { JobsService } from '../jobs.service';
import {Job} from '../models/Job';
#Component({
selector: 'app-job',
templateUrl: 'job.component.html'
})
export class JobComponent implements OnInit, AfterViewInit {
title = 'Job';
jobs: Job[] = [];
InProcess = '';
CurrentPartner = '';
ShowProcess = false;
sended = '';
constructor(private jobsService: JobsService) {
}
ngOnInit() {
this.jobs = this.jobsService.getJobs();
}
ngAfterViewInit() {
console.log(this.jobs); // Show the array
// Nothing happened when looping through the array
this.jobs.forEach((oneJob) => {
console.log(oneJob);
});
}
}
Screenshot of the console in Google Chrome
The content of the service:
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import {Job} from './models/Job';
interface IJob {
message: string;
jobs: any[];
}
#Injectable({
providedIn: 'root'
})
export class JobsService {
constructor(private httpClient: HttpClient) { }
private REST_API_SERVER = 'http://localhost:8080/myband/api/getjobs.php';
private REST_API_SERVER_SEND = 'http://localhost:8080/myband/api/sendjob.php';
jobList: Job[] = [];
errorMessage: any;
message: string;
static handleError(err: HttpErrorResponse) {
let errorMessage = '';
if (err.error instanceof ErrorEvent) {
errorMessage = `An error occurred: ${err.error.message}`;
} else {
errorMessage = `Server returned code: ${err.status}, error message is: ${err.message}`;
}
console.error(errorMessage);
return throwError(errorMessage);
}
public getJobs() {
this.requestJobs().subscribe(
iJob => {
this.message = iJob.message;
for (const job of iJob.jobs) {
const oneJob: Job = new Job(job);
this.jobList.push(oneJob);
}
},
error => this.errorMessage = error as any
);
return this.jobList;
}
public requestJobs(): Observable<IJob> {
return this.httpClient.get<IJob>(this.REST_API_SERVER).pipe(
catchError(JobsService.handleError)
);
}
}
The first thing I want to say to you is about isolation of responsibilities.
Your service must have just one job: provider one way to access your data; It means your logic inside getJobs() method could be done in your component.
export class JobsService {
constructor(
private httpClient: HttpClient,
) {}
private REST_API_SERVER = 'http://localhost:8080/myband/api/getjobs.php';
public requestJobs(): Observable<IJob> {
return this.httpClient.get<IJob>(this.REST_API_SERVER);
}
}
Now, you can handler your data in your component.
import { JobsService } from '../jobs.service';
#Component({
selector: 'app-job',
templateUrl: 'job.component.html'
})
export class JobComponent implements OnInit, AfterViewInit {
title = 'Job';
jobs$;
InProcess = '';
CurrentPartner = '';
ShowProcess = false;
sended = '';
constructor(private jobsService: JobsService) {
}
ngOnInit() {
this.jobs$ = this.jobsService.requestJobs();
}
ngAfterViewInit() {
this.jobs$
.pipe(
map(() => {}), // change your data here
catchError(() => {}) // handler your error here;
)
.subscribe(
() => {} // have access to your final data here.
);
}
}
Things to know:
You can remove the subscribe() execution and use the async pipe in your template;
The use of the operator map in pipe() is optional, you can handler your final data directly from your first callback subscribe().
You can convert your Observable to Promise using toPromise() method in one observable. Don't forgot async / await in your ngAfterViewInit.
Let me know if there is something I can help.
Try:
Object.keys(this.jobs).forEach(job => {
console.log(this.jobs[job]);
});
Try to assign an iterator function with below part replacement by this code:
// Nothing happened when looping through the array
this.jobs.forEach(oneJob, function(value, key) {
console.log(key + ': ' + value);
});
Usage of forEach in AngularJS:
For documentation try to check AngularJS forEach Docs
Syntax:
someIterable.forEach(object, iterator, [context])
Please check below example
class Job {
id: any;
status: any;
constructor(obj: any) {
this.id = obj.id;
this.status = obj.status;
}
}
let arr = [
{
id: 1,
status: "job"
}, {
id: 2,
status: "job2"
}
];
let newArr: any = [];
arr.forEach(a => {
let obj: Job = new Job(a);
newArr.push(obj);
})
console.log(newArr);
newArr.forEach((a: any) => {
console.log(a);
})

Why change in Observable variable triggers change in View Angular

I'm learning Angular, so I'm building todo app. Todos are fetched from API, and every one of them has a project as parent. In a view I'm adding new project which triggers addNewProject method in service which in turn triggers POST request to the API in another service. Projects are listed in the sidebar, bind to input from parent component. ProjectsService holds array of projects in a private field, and has observable which is used by main component.
I'm struggling to understand why on earth appending private property _projects after API call in the service triggers change in MainComponent property even though _projects is private and change in Observable from array should not trigger functions passed from Observers.
Parent:
#Component({
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.css']
})
export class MainComponent implements OnInit {
projects: Project[] = new Array<Project>();
constructor(private projectsService: ProjectsService) {
}
ngOnInit() {
this.projectsService.projects$.subscribe((projects) => {
this.projects = projects;
});
}
addNewProject(newProjectName: string) {
this.projectsService.addNewProject(newProjectName);
}
}
Sidebar:
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.css']
})
export class SidebarComponent implements OnInit {
#Input() projects: Project[];
#Output() projectEntered = new EventEmitter<string>();
constructor() {
}
ngOnInit() {
}
projectAdded(projectName: string) {
this.projectEntered.emit(projectName);
}
}
Service:
export class ProjectsService {
private _projects$: Observable<Array<Project>> = new Observable<Array<Project>>();
private _projects: Project[] = [];
private _loadedProject$: Observable<Project>;
private _projectsLoaded: boolean;
private _taskForProject: object;
constructor(private userService: UserService, private api: ApiService) {
}
loadAllProjects() {
this._projects$ = this.api.getAllProjectsByUserId(this.userService.userId).pipe(
map((projects) => {
this._projects = projects;
return this._projects;
})
);
}
get projects$() {
if (!this._projectsLoaded) {
this.loadAllProjects();
}
return this._projects$;
}
getAllTasks() {
return this.api.getAllTasksByUserId(this.userService.userId);
}
getProject(projectId: string) {
this._loadedProject$ = this.api.getProjectById(projectId);
return this._loadedProject$;
}
getTodayTasksForProject(tasks: Task[]) {
const todayTasks: Task[] = [];
const todayDate = new Date();
tasks.forEach((task) => {
if (new Date(task.completionPlannedDate).getDate() === todayDate.getDate()) {
tasks.splice(tasks.indexOf(task), 1);
todayTasks.push(task);
}
});
return todayTasks;
}
getTomorrowTasksForProject(tasks: Task[]) {
const tomorrowTasks: Task[] = [];
const tomorrowDate = new Date(new Date().getDate() + 1);
tasks.forEach((task) => {
if (new Date(task.completionPlannedDate).getDate() === tomorrowDate.getDate()) {
tasks.splice(tasks.indexOf(task), 1);
tomorrowTasks.push(task);
}
});
return tomorrowTasks;
}
getUpcomingTasks(tasks: Task[]) {
const upcomingTasks: Task[] = [];
const upcomingDate = new Date(new Date().getDate() + 2);
tasks.forEach((task) => {
if (new Date(task.completionPlannedDate).getDate() > upcomingDate.getDate()) {
tasks.splice(tasks.indexOf(task), 1);
upcomingTasks.push(task);
}
});
return upcomingTasks;
}
addNewProject(projectName: string) {
this.api.postNewProject({
id: null,
userId: this.userService.userId,
title: projectName,
tasks: []
}).subscribe((project: Project) => {
this._projects.push(project);
});
}
}
Please see here:
Basically application state change can be caused by three things:
Events - click, submit
XHR - Fetching data from a remote server
Timers - setTimeout(), setInterval()
If you don't want change detection to fire try changing to ChangeDetectionStrategy.OnPush

Passing objects between Components/Pages

Hello Im passing objects between two pages.
I have two pages in Ionic App. The first page has Defect object and sends it to the second page. The second Page receives the object and calls it's methods. Passing objects is done with usage of NavParams, an Ionic core class. Below you can see the receiving of the object. The Defect object.
export class Defect {
public priority: DefectPriority;
public state: DefectState;
public note: string;
public _id: string;
public posX: number;
public posY: number;
public createdAt: number;
public drawingId: string;
public images: DefectImage[];
constructor();
constructor(posY?: number, posX?: number, note?: string, defectId?: string, drawingId?: string) {
if (defectId === undefined || defectId === "") {
throw new Error("incorrect defect id");
}
if (posX === undefined || posY === undefined) {
throw new Error("incorrect coordinates");
}
if (drawingId === undefined || drawingId === "") {
throw new Error("incorrect drawingId");
}
if (drawingId === undefined || drawingId === "") {
throw new Error("incorrect drawingId");
}
this.priority = DefectPriority.NORMAL;
this.createdAt = new Date().getTime();
this.state = DefectState.REPORTED;
this._id = defectId;
this.note = note;
this.posX = posX;
this.posY = posY;
this.drawingId = drawingId;
this.images = [];
}
public getPriority() {
return this.priority;
}
setPriority(value: DefectPriority) {
if (!Object.values(DefectPriority).includes(value.toString())) {
throw new Error("incorrect priority")
}
this.priority = value;
}
public changeState(value: DefectState) {
this.state = value;
}
public setNote(note: string) {
this.note = note;
}
generateImageUrl(creatorName: string): DefectImage {
const newUrl = ObjectId.generate() + '-' + this._id + '.jpg';
const defectImage = new DefectImage(newUrl, creatorName, new Date().getMilliseconds());
this.addImage(defectImage);
return defectImage;
}
addImage(defectImage: DefectImage) {
if (!this.images) {
this.images = [];
}
this.images.push(defectImage);
}
Here is the receiving class:
defect: Defect;
constructor(private viewCtrl: ViewController,
private nav: NavParams,
private navCtrl: NavController,
private photo: PhotoProvider) {
this.defect = this.nav.get('defect');
}
Defect class apart from some properties has also methods like: generateImageUrl
Now when I change view to the component where the defect is beeing fetched from params internally it is just JS object without information about Defect class methods: Which means I cannot call methods defined in defect class after I send it to the another Page.
Notice no custom methods like generateImageUrl. Is there a way that I could not lose informations about this object? Or should I just recreate this object from data in the new component ?? my goal on screen below:
the way Im passing data:
const defectModal = this.modalCtrl.create(DefectDetailModal, {
defect: this.defect
});
I'm assuming that Defect is an entity in your App. Angular's Style Guide recommends using interfaces for data models instead of classes.
Consider using an interface for data models.
That being said, you should have created a DefectService, where you would have set some property for the current defect that you're dealing with.
You could have then injected the service in the components that you wanted to share data between. Then from one component, you could have set the defect and then you could get the defect in the other component using setters and getters.

Angular2 Error: Return type of public method from exported class has or is using private name

I am building out an Angular 2 app using the Angular-CLI, after initially building a working version of the app using the non-CLI version of Angular 2. To my surprise, some code that wasn't a problem in my non-CLI app version HAS been a problem with my Angular-CLI version. All that said, I've resolved everything except for one final error I am stuck on.
This is the error message I'm getting:
Uncaught Error: Module build failed: Error:
/Users/fdr/Documents/rds/rds/cli-rds/src/app/ui/generate-field.component.ts
(340,48): Return type of public method from exported class has or is
using private name 'Hint'.)
And here is the problem file causing the error:
import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit, ElementRef, ViewChild } from '#angular/core';
import { EventHandler } from '../app.event-handler';
import '../app.utils';
#Component({
selector: 'app-generate-field',
templateUrl: 'app/ui/generate-field.component.html',
styleUrls: ['app/ui/generate-field.component.css']
})
export class GenerateField extends EventHandler
{
public get hasFocus(): boolean
{
return this._hasFocus;
}
#Input() delay: number = 300;
#ViewChild('inputField') private inputField: ElementRef;
#ViewChild('suggestionField') private suggestionField: ElementRef;
#Input() public value: string;
#Output() private valueChange: EventEmitter<string> = new EventEmitter<string>();
#Output() public keyup: EventEmitter<KeyboardEvent> = new EventEmitter<KeyboardEvent>();
#Output() public focus = new EventEmitter<KeyboardEvent>();
#Output() public blur = new EventEmitter<KeyboardEvent>();
private inlineSuggestion: string;
private suggestions: ISuggestion[];
#Input() public options: string[];
#Output() private optionsChange: EventEmitter<string[]> = new EventEmitter<string[]>();
private isDirty: boolean = false;
private _hasFocus: boolean = false;
constructor(myElement: ElementRef)
{
super();
this.defineObservableProperty('value');
this.defineObservableProperty('isDirty');
this.defineObservableProperty('suggestions');
this.defineObservableProperty('options');
this.addPropertyListener('isDirty', function ()
{
if (this.isDirty == false)return;
var delay = this.delay ? this.delay : 500;
var self = this;
setTimeout(function ()
{
self.updateSuggestions();
this.isDirty = false;
}.bind(this), delay);
}.bind(this));
this.addPropertyListener('value', (): void=>
{
this.valueChange.emit(this.value);
this.isDirty = true;
});
this.addPropertyListener('suggestions', (): void=>
{
this.updateInlineSuggestion();
});
this.addPropertyListener('options', ()=>
{
this.optionsChange.emit(this.options);
});
}
//--------------------------------------------------------
// Functions
//--------------------------------------------------------
/**
* Evaluates value and updates the list of suggestions
*/
public updateSuggestions(): void
{
// Update suggestions
this.suggestions = this.generateSuggestions(this.value);
}
/***
* Updates the inline suggestion that appears on the text field
*/
private updateInlineSuggestion(): void
{
// Clear inline if there are no suggestions
if (this.suggestions.length == 0)
{
this.inlineSuggestion = '';
return;
}
// Show first option inline
this.inlineSuggestion = this.suggestions[0].value;
var x = this.inputField.nativeElement.selectionStart;
var y = this.inputField.nativeElement.selectionEnd;
this.suggestionField.nativeElement.selectionStart = x;
this.suggestionField.nativeElement.selectionEnd = y;
this.inputField.nativeElement.selectionStart = x;
this.inputField.nativeElement.selectionEnd = y;
this.suggestionField.nativeElement.scrollLeft = x;
}
private onFocus(): void
{
this._hasFocus = true;
// Forward event
this.focus.emit();
}
private onBlur(): void
{
this._hasFocus = false;
// Forward event
this.blur.emit();
}
interface ISuggestion
{
word: string;
match: string;
value: string;
}
try to add "export interface ISuggestion" at the last part of your code so that ISuggestion is exported, too.
Try adding : any after your method. I encountered same problem, It solved after I added any.

Including Typescript code in Relay (System.js import)

How to include system.js to fix the error below? Or is there any other solution?
I downloaded the relay-starter-kit (https://github.com/relayjs/relay-starter-kit), changed the database.js to database.ts with the below content (Snippet 1).
I ran "npm run update-schema" and got the error
System.register([], function (exports_1) {
^
ReferenceError: System is not defined
at Object.<anonymous> (database.js:9:1)
at Module._compile (module.js:410:26)
..
I know it occurs because update-schema uses scripts/updateSchema.js -> data/schema.js -> which imports objects from data/database.js (compiled version of database.ts) has -
System.register([], function(exports_1) {
Snippet 1:
/// <reference path="./interface.d.ts" />
export class User implements IUser{
constructor (public id: String, public name: String){
this.id = id;
this.name = name;
}
}
// Model types
class UserModel extends User implements IUserModel {
constructor(public id: String, public name: String){
super (id,name);
}
getUser ():IUser{
return this;
}
setUser (_User:IUser) : void {
this.id = _User.id;
this.name = _User.name;
}
getUserbyId (_id:String):IUser{
if (_id === this.id){
return this;
} else {
return null;
}
}
}
export class Widget implements IWidget{
constructor (public id: String, public name: String){
this.id = id;
this.name = name;
}
}
// Model types
class WidgetModel extends Widget implements IWidgetModel {
constructor(public id: String, public name: String){
super (id,name);
}
getWidget ():IWidget{
return this;
}
setWidget (_Widget:IWidget) : void {
this.id = _Widget.id;
this.name = _Widget.name;
}
getWidgetbyId (_id:String):IWidget{
if (_id === this.id){
return this;
} else {
return null;
}
}
}
// Mock data
var viewer:IUserModel = new UserModel('1','Anonymous');
var widgets:IWidget[] = ['What\'s-it', 'Who\'s-it', 'How\'s-it'].map((name:String, i:any) => {
let widget:IWidgetModel = new WidgetModel(name,`${i}`);
return widget;
});
export function getUser (_id:String):IUser {
return viewer.getUserbyId(_id);
}
export function getViewer ():IUser {
return viewer.getUser();
}
export function getWidget (_id:String):any {
widgets.forEach(
w => {
if (w.id === _id)
return w;
else
return null;
}
);
}
export function getWidgets (): IWidget[]{
return widgets;
}
tsconfig.json had
"module": "system",
Changed it to
"module": "umd",
and it worked.

Categories

Resources