Angular - Best approach for sending partial data model in HTTP request - javascript

Server returns a User object in response
{
name: "some_name",
age: 30
}
I'll create a class for the User object, but the class also have other property which I need only in the client side
class User {
public color: string = "red"
constructor(public name: string, public age: number) {}
}
color property I'll only use in client-side.
Now User type for client and server are different.
If I want to send User obj back to the server without color property, in request method
Should I create a new class Object from User?
class UserRequest {
constructor(public name: string, public age: number) {}
}
let userRequest = new UserRequest(user.name, user.age);
Just create new object without type?
let userRequest = {
name: user.name,
age: user.age
}
Here I have only 2 property, If I have more then will this feasible?
Assume If I have to send a list of User then iterating all the user object creating a new object will increase complexity.
What is the best approach when types are different?

You can define a new function in your User class that will return only the database fields, or make it more generic by defining a dbFields array that contains only the fields that need to be sent to the server:
class User {
public color: string = "red";
private dbFields = ['name', 'age'];
constructor(public name: string, public age: number) {}
getUserData(){
let data = {};
for(const field of this.dbFields)
data[field]=this[field];
return data;
}
}
Then you can simply call user.getUserData() and it will return an object with only name and age
You can also make it more general by defining a Model class and use the function in other models as well:
class Model {
constructor(protected dbFields: string[]) {}
getDatabaseData(){
let data = {};
for(const field of this.dbFields)
data[field]=this[field];
return data;
}
}
class User extends Model {
public color: string = "red";
constructor(public name: string, public age: number) {
super(['name', 'age']);
}
}
And you call user.getDatabaseData() when you want to send a request.
If you have more fields, you just add more values to the dbFields array. If you have an array of users, you can map it to an array of database data:
const usersData = users.map((user: User)=>user.getDatabaseData());

this.http.post<ServerModel>({ serverModel.prop1: clientModel.prop1, serverModel.prop2: clientModel.prop2 });
If you need to create server model in more than one spot you can wrap it in a function. Seeing you are using classes you could have a method called toServerModel but the Angular standard is to use interfaces for data models, not classes.

Related

How to extract only certain fields from Http response object

I'm fairly new to Angular and I'm trying to get only certain values from the Http response object.
In my service, I'm doing a get request to fetch weather data for a given city like so:
export class CityService {
private baseUrl = 'https://api.openweathermap.org';
constructor(private http: HttpClient) { }
getCity(name: string){
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`);
}
}
Now, in the component, I'm logging out the response like so:
ngOnInit(): void {
this.cityService.getCity('lucija').subscribe(data => {
console.log(data)
});
}
The response itself it's just one object with many fields (also nested ones), which most of them I do not need.
I've also set up an interface where I would like to "save" in those certain response fields:
export interface City {
name: string;
description: string;
icon: string;
main: object;
search: string;
}
How can I do that? Cheers!
EDIT:
Based on the answer below I got 2 errors, which I resolved like so:
getCity(name: string): Observable<City>{
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`)
.pipe(map((res: any) => <City>{
name: res.name,
description: res.weather[0].description,
icon: `http://openweathermap.org/img/w/${res.weather[0].icon}.png`,
main: res.main,
search: name
}));
One solution could be to map the object in your service. Then your service will return the City Object.
export class CityService {
private baseUrl = 'https://api.openweathermap.org';
constructor(private http: HttpClient) { }
getCity(name: string): Observable<City>{
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`)
.pipe(map((res) => { return { name: res.cityName }; }); // only added name as example. In the final code map all the values to the correct City field.
}
}
If you do not want your service to always return the City object you can do this mapping in your component.

Reactive Forms correctly convert Form Value to Model Object as List

I am trying to convert the submitted form to an object.
I have been using the following post for guidance.
Reactive Forms correctly convert Form Value to Model Object
However, the example is only for a single object and not a list of objects.
I have a Person object.
person.ts
export class Person {
public constructor(init?: Partial<Person>) {
Object.assign(this, init);
}
x: number;
y: number;
personId: number;
firstName: string;
lastName: string;
email: string;
companyName: string;
staffCode: string;
}
Here I submit the form, and try convert it to a Person object.
approval-edit.component.ts
public onSubmit(): void {
const data: any = this.approvalEditFormGroup.value;
console.log(data);
this.person = new Person(this.approvalEditFormGroup.value);
console.log(this.person);
}
Result:
Question
How do I convert the this.approvalEditFormGroup.value into a list of Person (Person[])?
persons: Person[] = convert(this.approvalEditFormGroup.value);
You just need to map of the form value and create new instance of the Person class on every item in form value
something like this
const data: any = this.approvalEditFormGroup.value;
persons: Person[] = Object.keys(data).map(key => new Person(data[key]));

JSON to Javascript Class

I have a http request that gets this Json object from a nosql database:
let jsonBody = {
birthday : 1997,
firstname: 'foo',
lastname:'bar'
}
Then I want to load this information into the Student model:
class Student{
constructor(){
}
getFullname(){
return this.lastname+' '+this.firstname
}
getApproxAge(){
return 2018- this.birthday
}
}
Normally, I would add this method to this class:
fromJson(json){
this.studentId = json.studentId;
this.birthday = json.birthday;
this.firstname = json.firstname;
this.lastname = json.lastname;
}
I would use it as follow:
let student = new Student()
student.fromJson(jsonBody)
console.log(student.getFullname())
console.log(student.getApproxAge())
This works fine but my problem is I have: 100 proprieties in reality. Will I have to write all proprities one by one in the fromJson method?
And also, if a propriety name has change, let's say: lastname became LastName, I will have to fix it?
Is there a simpler way to just assign these values to the object student dynamically but keep all of its methods??
Something like this:
fromJson(json){
this = Object.assign(this, json) //THIS IS NOT WORKING
}
Just assign to an instance:
static from(json){
return Object.assign(new Student(), json);
}
So you can do:
const student = Student.from({ name: "whatever" });
Or make it an instance method and leave away the assignemnt:
applyData(json) {
Object.assign(this, json);
}
So you can:
const student = new Student;
student.applyData({ name: "whatever" });
It could also be part of the constructor:
constructor(options = {}) {
Object.assign(this, options);
}
Then you could do:
const student = new Student({ name: "whatever" });
And also, if a property name has changed, let's say: lastname became LastName, I will have to fix it?
Yes you will have to fix that.
There is no way in javascript to deserialize json into classes. So I wrote a library ts-serializable that solves this problem.
import { jsonProperty, Serializable } from "ts-serializable";
export class User extends Serializable {
#jsonProperty(String)
public firstName: string = ''; // default value necessarily
#jsonProperty(String, void 0)
public lastName?: string = void 0; // default value necessarily
#jsonProperty(Date)
public birthdate: Date = new Date(); // default value necessarily
public getFullName(): string {
return [
this.firstName,
this.lastName
].join(' ');
}
public getAge(): number {
return new Date().getFullYear() - this.birthdate.getFullYear();
}
}
const user: User = new User().fromJSON(json);
user.getFullName(); // work fine and return string
user.getAge(); // work fine and return number
// or
const user: User = User.fromJSON(json);
user.getFullName(); // work fine and return string
user.getAge(); // work fine and return number
The library also checks types during deserialization.

Javascript / Typescript: new class instance inside Array.prototype.map function do not persist

I have the following class:
class Category {
static ObjectMapToCategory(id: number, display: string, name: string): Category {
return new Category(id, display, name);
}
constructor(private id: number, private display: string, private name: string) {
}
get Id(): number {
return this.id;
}
get Display(): string {
return this.display;
}
get Name(): string {
return this.name;
}
};
From an API endpoint I am getting an array of this other class:
class CategoryModel {
constructor(public id: number, public display: string, public name: string) {
}
}
Let's suppose I store the API response in:
let apiResponseArray:CategoryModel[];
The idea is to transform this array in another of type Category[]. For that I use the Array.prototype.map function:
let categoriesArray:Category[] = apiResponseArray.map(categoryModelElement => new Category(categoryModelElement.id, categoryModelElement.display, categoryModelElement.name));
Nevertheless, I end up with an array of objects containing only the properties: id, display and name. But properties (getters) Id, Display and Name from class Category, are lost. Any ideas why? Does it has anything to do with garbage collector deleting the new Category instance after returning from map? And thus, it is not taken as a class instance but as a plain object? That are my only thoughts...
Instead, if I do:
let categoriesArray:Category[] = [];
apiResponseArray.forEach(categoryModelElement => categoriesArray.push(new Category(element.id, element.display, element.name));
Then, it works. I get my array of Category elements.
Thanks in advance!

Overriding javascript Object toString() in a custom typescript class

I've been sitting with a problem for the past days & I can't seem to get a solution anywhere.
Background:
I have a typescript class defined as follows:
export class Client extends Person {
claimNumber: string;
policyNumber: string;
address: string;
insuranceCompany: Organisation = new Organisation();
toString(): string {
return this.policyNumber
.concat(this.claimNumber);
}
}
The above is used as a model that drives an angular 5 template. In my component, I fetch (using angular 5 HttpClient) a list of clients from a remote api & generate an html table rows. The LOC to generate the table rows is:
<tr *ngFor="let client of clients | filter:searchString"> ... </tr>
searchString above is property bound to a search input tag & filter is a custom filter Pipe defined as follows:
export class FilterPipe implements PipeTransform {
transform(items: Client[], term: string) {
if (term == undefined || term === '') return items;
return items.filter(item =>item.toString().toLocaleLowerCase().includes(term.toLocaleLowerCase()));
}
}
Problem:
When I inspect item.toString() in the filter pipe above, it returns [object Object] as opposed to a string made up of policyNumber, & claimNumber.
Investigation:
I investigated this issue as follows: I instantiated the Client class as follows:
let c = new Client();
c.policyNumber = 'ababababa';
c.claimNumber = 'aaaaaaa';
console.log('client toString() is => ' + c.toString());
Interesting enough, the console.log above outputs : 'ababababaaaaaaaa'.
Question:
What am I doing wrong that results in the item.toString() in the filter pipe return [object Object] whereas toString() on a class I instantiated returns the correct string?
If you get the clients from a WebService (or something similar), you are just getting plain json objects. If you say that the received objects are of type Client, typescript will show them as objects of such type, but only the properties will be the same, the methods will not be from the Client class, but from the Object class.
You might want to instantiate them as real client objects after you retrieve them from the server:
public myServiceMethod() {
return this.http.get(...).map(plainClients => {
const realClients: Array<Client> = (plainClients || []).map(plainClient => {
let realClient = new Client();
realClient.claimNumber = plainClient.claimNumber;
realClient.policyNumber = plainClient.policyNumber;
realClient.address = plainClient.address;
return realClient;
});
return realClients;
})
}
You might prefer to use anemic objects with their types being interfaces, and use an utilitarian function to retrieve the client as a string, to avoid cases like the one you're having:
export interface Person {
...
}
export interface Client extends Person {
claimNumber: string;
policyNumber: string;
address: string;
insuranceCompany: Organisation;
}
// In some utilitarian class
public static getClientInfo(client: Client) {
return client.policyNumber.concat(client.claimNumber);
}
// In your pipe
return items.filter(item => getClientInfo(item).toLocaleLowerCase().includes(term.toLocaleLowerCase()));
I'm not saying to just use anemic classes in your app, but if some type of object is passed around and will probably be serialized, then using anemic objects can avoid problems like the one you are having.
A way to figure out what the problem might be would be to rename your method to something that isn't a built in method name, like maybe toSearchString. It would also be worth adding console logs to your filter function to make sure you're actually getting clients there. You may actually be getting a different object.

Categories

Resources