Passing objects between Components/Pages - javascript

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.

Related

TypeScript - Getting prototype in class's static method

I'm working on a multiplayer game and need to serialize and deserialize entities while sending client <-> server. To do it, I'm just sending the object as JSON, then parsing it and assigning to an object's instance got by registry name. In the registry I put to array string with callback to create object:
static create(registryName: string, args: {}) {
const object = Object.assign(this.prototype,args);
object.registryName = registryName;
return object;
}
while args is my deserialized object, and the whole method is in my abstract entity class that all entities extend.
So what I want to get here is certain (defined in the registry) entity instance. However, when running the code, it throws me an exception, that this.prototype is undefined:
const object = Object.assign(this.prototype,args);
^
TypeError: Cannot convert undefined or null to object
at Function.assign (<anonymous>)
at Array.IGameEntity.create [as x_wing]
And now, is there any other method that would allow me to do that? I guess current prototype does not exists because it's static method (however PhpStorm says it is valid). Of course I could just put that method in every entity class and put the class name instead of this, but I'm curious if the current way could work somehow.
Code to reproduce:
export namespace Test{
export abstract class IGameEntity{
registryName: string;
readonly id: number;
vec: {
x: number;
y: number;
};
rotation: number = 0;
scale: number = 1;
static ids = 0;
protected constructor(vec: {x: number, y: number}, rotation: number = 0, scale: number = 1) {
this.id = IGameEntity.ids++;
this.vec = vec;
this.rotation = rotation;
this.scale = scale;
}
abstract tick(time: number);
static create(registryName: string, args: {}) {
const object = Object.assign(this.prototype,args);
object.registryName = registryName;
return object;
}
}
export class XWingEntity extends IGameEntity{
tick(time: number) {
//something here...
}
}
export class EntityRegistry {
static instance: EntityRegistry;
entities: Array<(registryName: string, data: {}) => IGameEntity> = [];
static readonly ENTITY_X_WING: string = "x_wing";
constructor() {
EntityRegistry.instance = this;
this.register();
}
register(){
this.reg(EntityRegistry.ENTITY_X_WING,XWingEntity.create);
}
private reg(registryName: string, caller: (registryName: string, args: {}) => Object) {
this.entities[registryName] = caller;
}
create(registryName: string, args: {}): IGameEntity | null {
if(this.entities[registryName]){
return this.entities[registryName](registryName, args);
}
return null;
}
}
}
And now sending:
//server code
const registry = new EntityRegistry();
let entityServer = registry.create(EntityRegistry.ENTITY_X_WING, {x: 5, y: 3})
//add to world, etc
//then send
let json = JSON.stringify(entityServer);
//send json
//client code
//get json
let object = JSON.parse(json);
let entityClient = registry.create(object['registryName'],object);
Thanks for any help.

"undefined" property, when trying to read object's property outside of subscribe in angular

I'm new in angular and working on spring+angular project, I have 2 classes called questions and answers.
questions.ts
export class Questions {
private questionDescription: string;
private id: string;
private answers: Answers[] = [];
// getter setters
answers.ts
export class Answers {
private answerDescription: string;
private isVisible: boolean;
private id: string;
private voteCounter: number;
// getter setters
In my component.ts,i fetch the data from webservice. Here's my service;
base_url: string = "http://localhost:8080";
constructor(private http: HttpClient) { }
getQuestion(id: string): Observable<HttpResponse<Questions>> {
return this.http.get<Questions>(this.base_url + "/question" + "/" + id, { observe: 'response' });
}
This is my component ;
constructor(private activatedRoute: ActivatedRoute, private restService: RestcallService) { }
public id: string;
public question: Questions ;
public answers: Answers[] = [];
ngOnInit() {
this.id = this.activatedRoute.snapshot.paramMap.get('id');
this.restService.getQuestion(this.id).subscribe(response => {
if (response.status == 200) {
this.question = new Questions("");
Object.assign(this.question, response.body);
this.answers = this.question._answers;
}
})
}
After the line "this.answers=this.question._answers" , i use "answers" array in my component.html with [ngModel)] and it works, but i can't read property values of answer array outside of subscribe method,i have a button and this button calls "results()" function on click. I need to perform some calculations after reading object properties but i can't read. Below code prints "undefined" in console.
results() {
console.log(this.answers[0]._answerDescription); // prints undefined
}
When i change the method like this;
results() {
console.log(this.answers); // prints the array in console
}
How can i achieve this?

How to create an Angular Domain Model correctly

I want to create a domain model correctly.
My attempt below creates the properties outside the constructor.
Should I be creating and setting the properties of the TestModel class inside the constructor only? this way their would be less lines of code
I have the below attempt that I think is correct:
export class TestModel1 {
public currentPage: number = 0;
public hasNext: boolean = false;
public hasPrev: boolean = false;
public pageSize: number = 0;
public totalItems: number = 0;
constructor(data: any) {
this.currentPage = data.currentPage;
this.hasNext = data.hasNext;
this.hasPrev = data.hasPrev;
this.pageSize = data.pageSize;
this.totalItems = data.totalItems;
}
}
It just seems a little big, too many lines of code.
Currently I need to pass in a data object and then map.
Is their a clever way for me to implement this better using the constructor function?
If we speak about the model class, the declaration of that should be like in example below:
export class Account {
constructor(
public activated: boolean,
public authorities: string[],
public email: string,
public firstName: string,
public langKey: string,
public lastName: string,
public login: string,
public imageUrl: string
) {}
}
Certainly you should not to define values outside of constructor. You may do declare the model class members as you have in your example, but without the definition of values:
export class TestModel1 {
public currentPage: number;
public hasNext: boolean;
public hasPrev: boolean;
public pageSize: number;
public totalItems: number;
constructor(data: any = null) {
if(data !== null) {
this.currentPage = data.currentPage;
this.hasNext = data.hasNext;
this.hasPrev = data.hasPrev;
this.pageSize = data.pageSize;
this.totalItems = data.totalItems;
}
}
}
And if you want to declare default values, my advice is to do this inside the constructor for clean and good code.
Update:
export class TestModel1 {
public currentPage: number;
public hasNext: boolean;
public hasPrev: boolean;
public pageSize: number;
public totalItems: number;
constructor(data: any = null) {
if(data !== null) {
this.currentPage = data.currentPage;
this.hasNext = data.hasNext;
this.hasPrev = data.hasPrev;
this.pageSize = data.pageSize;
this.totalItems = data.totalItems;
}
else {
this.currentPage = 0;
this.hasNext = false;
this.hasPrev = false;
this.pageSize = 0;
this.totalItems = 0;
}
}
}
That's would be better, if you want to have defaults
The main issue I see in the question is that of duplication. All of the properties are duplicated in the constructor, which violates the DRY principle of clean code.
If you want to populate a new instance of your domain object in a more concise way, you can do the following:
export class TestModel1 {
public currentPage: number;
public hasNext: boolean;
public hasPrev: boolean;
public pageSize: number;
public totalItems: number;
constructor(data: any) {
Object.keys(data).forEach(key => {
this[key] = data[key]
});
}
}
You have to ensure that the input data object will have the correct properties only, which may or may not be easy, based on the source of data.

TypeScript: Recursively creating complex objects

Using Angular & TypeScript, we can use generics and all the Compile-goodness to assure some sort of type-safety. But if we are using for example the HTTP-Service, we don't get a specific objec but just parsed JSON. For example, we have some generic methods doing that:
public get<T>(relativeUrl: string): Promise<T> {
const completeUrlPromise = this.createCompleteUrl(relativeUrl);
const requestOptions = this.createRequestOptions(ContentType.ApplicationJson, true);
return completeUrlPromise.then(completeUrl => {
return this.processResponse<T>(this.http.get(completeUrl, requestOptions));
});
}
private processResponse<T>(response: Observable<Response>): Promise<T> {
const mappedResult = response.map(this.extractData);
const result = mappedResult.toPromise();
return result;
}
private extractData(res: Response): any {
let body;
if (!Array.isArray(res)) {
if (res.text()) {
body = res.json();
}
} else {
body = res;
}
if (!JsObjUtilities.isNullOrUndefined(body)) {
return body;
}
return {};
}
Ultimately, the generic type is useless this way, since we just get the JSON. If the generic object has methods or properties not in the JSON, they are lost.
To avoid this, we added the possibility to pass a constructor-function to truly create the object:
private processResponse<T>(response: Observable<Response>, ctor: IParameterlessConstructor<T> | null = null): Promise<T> {
let mappedResult = response.map(this.extractData);
if (ctor) {
mappedResult = mappedResult.map(f => {
const newObj = JsObjFactory.create(f, ctor);
return newObj;
});
}
const result = mappedResult.toPromise();
return result;
}
And the JsObjFactory looking like this:
export class JsObjFactory {
public static create<T>(source: any, ctorFn: IParameterlessConstructor<T>): T {
const result = new ctorFn();
this.mapDefinedProperties(source, result);
return result;
}
private static mapDefinedProperties<T>(source: Object, target: T): void {
const properties = Object.getOwnPropertyNames(target);
properties.forEach(propKey => {
if (source.hasOwnProperty(propKey)) {
target[propKey] = source[propKey];
}
});
}
}
This works well for shallow objects, but doesn't work, if a property is also a complex type with a constructor. As there are no types at runtime, the best bet I have currently is to kindahow parse the properties, check if classes exist and then create them. But this seems to be very error-prone and cumbersome.
Since I'm always certain, I'm not the only person with this issues, are there solutions, or TypeScript/JavaScript features I'm not aware off, which would help here?
I don't personally do it like this, but it may be what you're looking for.
Example:
Customer.ts
export interface ICustomer {
Id: number;
Name: string;
Orders: IOrder[];
...
}
export class Customer implements ICustomer {
public Id: number;
public Name: string;
public Orders: IOrder[];
constructor(customer: Partial<ICustomer>) {
this.Id = customer.Id || 0;
this.Name = customer.Name || '';
this.Orders = [];
customer.Orders.forEach((order: IOrder) => this.Orders.push(new Order(order)));
}
//some functions
}
Order.ts
export interface IOrder {
Id: number;
Weight: number;
Shipmentdate: string;
}
export class Order implements IOrder {
public Id: number;
public Weight: number;
public Shipmentdate: string;
constructor(order: Partial<IOrder>) {
this.Id = order.Id || 0;
this.Weight = order.Weight || 0;
this.Shipmentdate = order.Shipmentdate || '';
}
//some functions
}
This would make the Object (in this case Customer) responsible for instantiating it's known complex types that you pass in. And Order in turn could have its complex types that it instantiates.

listen to object property changes inside array in a service

So basically what I want to achieve is watching/listening objects changing inside of array within an injectable service using setters and getters to manipulate it's data
eg
#Injectable()
export class StorageService {
protected items: Array<any> = [];
constructor(private storage: Storage) {
this.storage.ready().then(() => {
StorageService.getGetters().forEach((get) => {
this.storage.get(get).then(res => this.items[get] = res);
});
});
}
public static getGetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["get"] === "function"
});
}
get Storage() {
return this.storage;
};
ClearStorage() {
this.storage.clear();
}
protected Setter(key: string, value: any): void {
this.items[key] = value;
this.storage.set(key, value);
}
protected Getter(key: string): any {
return this.items[key];
}
set User(value: User) {
this.Setter('User', value);
}
get User(): User {
return this.Getter('User');
}
}
where User interface is :
export interface User {
id: number;
role_id: number;
name: string;
email?: string;
}
now in any component or service/provider I can DI my StorageService so I can access the User getter.
so:
storage.User.name = 'testing';
now the name is changed , but I have no way to track that , so I can update my storage!
to update my storage I would do:
storage.User.name = 'testing';
storage.User = storage.User;
which is working , but I need a way to listen to any changes happens to the object properties, so I can update my storage...
I searched alot , and all I can find is watching components #Input() , which is not working in my case.
Hopefully I made my point clear.

Categories

Resources