How to create an Angular Domain Model correctly - javascript

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.

Related

Cannot assign to read only property 'winner' of object '[object Object]'

I'm encountering the error
ContestDetailsModalComponent.html:21 ERROR TypeError: Cannot assign to read only property 'winner' of object '[object Object]'
at ContestDetailsModalComponent.onWinner (contest-details-modal.component.ts:88:24)
at Object.eval [as handleEvent] (ContestDetailsModalComponent.html:21:17)
at handleEvent (core.js:43993:77)
at callWithDebugContext (core.js:45632:1)
at Object.debugHandleEvent [as handleEvent] (core.js:45247:1)
at dispatchEvent (core.js:29804:1)
at core.js:42925:1
at HTMLButtonElement.<anonymous> (platform-browser.js:2668:1)
at ZoneDelegate.invokeTask (zone-evergreen.js:399:1)
at Object.onInvokeTask (core.js:39680:1)
when I try to make the assignment
this.contest.winner = winnerId;
you see in the following code snippet:
public onWinner(winnerId: number): void {
const post: Post = new Post();
post.title = `The winner of the contest has been elected: ${this.contest.title}`;
post.description = `Tizio has been elected winner of the contest and ${this.user.name} gives him the most welcome compliments`;
post.user = this.user;
post.contest = this.contest;
post.type = Type.WINNER;
post.level = Level.SUCCESS;
this.contest.winner = winnerId;
this.store.dispatch(PostActions.savePost({post}));
this.store.dispatch(ContestActions.saveContest({contest: this.contest}));
}
I've done this kind of assignment with other classes around the project and they never bothered me.
I enclose the contest class and post if it is useful:
export class Contest {
public id: number;
public title: string;
public description: string;
public rules: string;
public startDate: Date;
public endDate: Date;
public status: Status;
public winner: number;
public bannedUser: number[];
}
export class Post {
public id: number;
public title: string;
public description: string;
public level: Level;
public type: Type;
public user: User;
public publishDate: Date;
public contest: Contest;
}
I also tried some solutions always found on this forum such as:
Object.assign(target, source);
But he rightly tells me that this.contest.winner is null or undefined.
I hope for your help. Thank you
Edit:
This is the entire component.ts where onWinner() is present.
#Component({
selector: 'app-contest-details-modal',
templateUrl: './contest-details-modal.component.html',
styleUrls: ['./contest-details-modal.component.scss'],
})
export class ContestDetailsModalComponent implements OnInit, OnDestroy, AfterViewChecked {
private readonly subscriptions: Subscription = new Subscription();
public id: number;
public user: User;
public contest: Contest;
public images: Image[];
public hasVoted: boolean;
constructor(
private readonly bsModalRef: BsModalRef,
private readonly modalService: BsModalService,
private readonly store: Store<AppState>,
private cdRef: ChangeDetectorRef
) { }
public ngOnInit(): void {
this.subscriptions.add(this.store.pipe(select(AuthSelectors.getUser)).subscribe((user: User) => {
if (user) {
this.user = user;
}
}));
this.subscriptions.add(this.store.pipe(select(ContestSelectors.getById)).subscribe((contest: Contest) => {
if (contest) {
this.contest = contest;
}
}));
this.subscriptions.add(this.store.pipe(select(ImageSelectors.getImages)).subscribe((images: Image[]) => {
if (images.length) {
this.images = images;
}
}));
this.subscriptions.add(this.store.pipe(select(UserSelectors.check)).subscribe((ack: Ack) => {
if (ack) {
this.store.dispatch(AuthActions.updatedUser({userId: this.user.id}));
this.modalService.show(UploadImageModalComponent);
this.bsModalRef.hide();
}
}));
this.subscriptions.add(this.store.pipe(select(ImageSelectors.check)).subscribe((ack: Ack) => {
if (ack) {
this.bsModalRef.hide();
}
}));
}
public ngOnDestroy(): void {
this.store.dispatch(UserActions.clean());
this.store.dispatch(ContestActions.clean());
this.subscriptions.unsubscribe();
}
public onWinner(winnerId: number): void {
const post: Post = new Post();
post.title = `The winner of the contest has been elected: ${this.contest.title}`;
post.description = `Tizio has been elected winner of the contest and ${this.user.name} gives him the most welcome compliments`;
post.user = this.user;
post.contest = this.contest;
post.type = Type.WINNER;
post.level = Level.SUCCESS;
this.contest.winner = winnerId;
this.store.dispatch(PostActions.savePost({post}));
this.store.dispatch(ContestActions.saveContest({contest: this.contest}));
}
public onJoin(): void {
this.store.dispatch(UserActions.saveUser({idUser: this.user.id, id: this.contest.id}));
}
public onVote(image: Image): void {
let vote: number = image.vote;
vote = vote + 1;
this.store.dispatch(ImageActions.updateImage({photoId: image.id, votes: vote, userId: this.user.id}));
this.store.dispatch(AuthActions.updatedUser({userId: this.user.id}));
}
public isContain(contestId: number): boolean {
if (this.user.myContest) {
for (const i of this.user.myContest) {
if (contestId === i) {
return true;
}
}
}
return false;
}
public isImageVoted(id: number): boolean {
if (this.user.favouritePhoto) {
for (const imageVoted of this.user.favouritePhoto) {
if (id === imageVoted) {
this.hasVoted = true;
return true;
}
}
return false;
}
}
public onBan(image: Image): void {
this.modalService.show(BanModalComponent, {initialState : image});
this.bsModalRef.hide();
}
public isBan(contestId: number): boolean {
if (this.user.whereBanned) {
for (const contestBanned of this.user.whereBanned) {
if (contestId === contestBanned) {
return true;
}
}
return false;
}
}
public ngAfterViewChecked(): void {
this.cdRef.detectChanges();
}
}
contest contains a reference to an Observable emission, and all references emitted by Observables are readonly. Either clone contest:
this.contest = { ...contest };
Or, better, leave it as an Observable and consume it as such, generally via the async pipe. Also, if you're using NgRx, you want to be using store.select():
this.contest$ = this.store.select(ContestSelectors.getById);

"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?

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.

Angular4/TypeScript Class Usability

Is that possible to access two different Typescript classes like controller to store a value...here is my use case....
i'm getting values from the front end lets say i have ID="100",Name="Angular4". Now how can i pass those values to these class using the methods get getFieldValue(fieldName: string) and setFieldValue(fieldValue: string, value: string). Also i have tried to instantiate the classes to get or set the value but i got failed,can anybody please tell how can i acheive the desired output completely using typescript?
my classes are:
main class:
import {Record} from './record';
import {Field} from './field';
export class BusinessObject {
public boName: string = null;
public focusedRecordsList = new Set();
// public activeRecord: Record = new Record();
public fetchSize: number = 100;
// public activeParentRecord: Record = new Record();
public hasMoreRows: boolean;
public isPageQuery: boolean;
public startIndex: number;
public createNewRecord(boName: string, parentId: string, fromRecord: string) {
return JSON.stringify({'boName': boName, 'parentId': parentId, 'fromRecord': fromRecord});
}
getFieldValue(fieldName: string): any {
}
setFieldValue(fieldValue: string, value: string): void {
}
}
and another class, that contains my field types:
import {BusinessObject} from './business-object';
export class Field extends BusinessObject{
// Specifies different information on the field
public name: string = null;
public id: string = null;
public type: string = null;
public boId: string = null;
public colName: string = null;
constructor(name: string) {
}
}

WinRTError: Class not registered

I'm new in TypeScript.
I'm getting error when trying to instantiating the class.
Below is my sample code, actual code is different can't share.
module ABC {
export class A {
public execute<T>(action: string, data?: any, callerContext?: any): IAsyncResult<T> {
// CODE::
var requestMessage = new Common.ClientClasses.ClientRequestMessage(); **// **ERROR- "WinRTError: Class not registered"****
requestMessage.requestUri = actionRequest;
requestMessage.method = "POST";
requestMessage.body = data ? JSON.stringify(data, null, 2) : null;
Common.ClientClasses.ClientRequest.executeAsync(requestMessage)
.done((result: Common.ClientClasses.ClientResponeMessage) => {
// CODE:
}
// Code::
}
}
}
declare module Common.ClientClasses {
class ClientRequestMessage {
public requestUri: string;
public method: string;
public body: string;
}
class ClientResponeMessage {
public status: number;
public statusText: string;
public responseText: string;
}
class ClientRequest {
static executeAsync(clientRequestMessage: ClientRequestMessage): any;
}
}
I did some improvements, should work:
module ABC {
export class A {
public execute<T>(action: string, data?: any, callerContext?: any) {
var requestMessage = new Common.ClientClasses.ClientRequestMessage();
requestMessage.method = "POST";
requestMessage.body = data ? JSON.stringify(data, null, 2) : null;
Common.ClientClasses.ClientRequest.executeAsync(requestMessage)
}
}
}
module Common.ClientClasses {
export class ClientRequestMessage {
public requestUri: string;
public method: string;
public body: string;
}
class ClientResponeMessage {
public status: number;
public statusText: string;
public responseText: string;
}
export class ClientRequest {
static executeAsync(clientRequestMessage: ClientRequestMessage): any {
console.log("test");
}
}
}
Then it can be run as following:
var a = new ABC.A();
a.execute("some string");
declare module creates a definition file used for Intellisense but it doesn't provide any implementation that's why I changed your code so this fragment can work.
Also if you want to use any classes from the module, you must export them so they can be visible from outside of that module.

Categories

Resources