convert returned Observables to custom class array in angular - javascript

Hello folks I will keep my question very simpler by showing code
I am using Json placeholder site for the fake rest Api
I have a user class Object
I want to convert returned Observable to the
custom class object array.
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { Users } from './users.model';
#Injectable({
providedIn: 'root'
})
export class UsersService {
private url = "https://jsonplaceholder.typicode.com";
constructor(private http:HttpClient) {
console.log(this.getUsers());
}
getUsers():Observable<Users[]>{
return this.http.get<Users[]>(`${this.url}/posts`);
}
}
The above is my service
export class Users {
email: string;
id: number;
name: string;
phone: string;
username: string;
}
above is my class I haven't included all properties
In my typescript file I have code like.
constructor(private _usersService:UsersService) {
}
ngOnInit(): void {
this._usersService.getUsers().subscribe(data=>this.users=data);
console.log(this.users);
}
Now the things I want is
how to convert returned observable in my custom class object?
I don't have all the fields so how is it possible to map only those fields which I want?
Hope my question is clear..!!

so this answer takes advantage of map() which is imported from rxjs.
before subscribing we are going to pipe a map() function into the observable stream and then map() each element from that array into a new object that fits our User interface
then we subscribe and the data we get then will be an array that fits our User interface
ngOnInit(): void {
this._usersService.getUsers()
.pipe(map(data => {
return data.map(item => {
const user: User = {
name: item.name,
email: item.email,
}
return user
})
}))
.subscribe(data=>this.users=data);
console.log(this.users);
}

You can do like below, in the User class have a constructor and return User while mapping
import { Component, VERSION, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
export class User {
email: string;
id: number;
name: string;
phone: string;
username: string;
constructor( user: User ) {
Object.assign( this, user );
}
}
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
name = 'Angular ' + VERSION.major;
constructor(private http: HttpClient){}
ngOnInit() {
this.http.get<User[]>("https://jsonplaceholder.typicode.com/users")
.pipe(
map( data => {
return data.map( ( user ) => {
return new User( {
email: user['email'],
id: user['id'],
name: user['name'],
phone: user['phone'],
username: user['username'],
} );
} );
} ),
)
.subscribe( (users : User[]) => console.log(users) );
}
}
Working stackblitz

Related

Why is my Angular Service not assigning a filtered result to my property?

I setup a service called 'bankService' which is being used by my 'user' component. The 'user' component is receiving the data from the service correctly but I am unable to assign a filtered result to my 'currentAccount' property. I am filtering by 'id' from the list of 'accounts' that is being returned from my service. Any help with an explanation would be appreciated!
Model
export interface Account {
id: number;
accountHolder: string;
checking: number;
savings: number;
}
Service
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { elementAt, Observable } from 'rxjs';
import { Account } from '../models/Account';
import { Transaction } from '../models/Transaction';
#Injectable({
providedIn: 'root',
})
export class BankServiceService {
private apiUrl = 'http://localhost:5000/accounts';
constructor(private http: HttpClient) {}
getAccounts(): Observable<Account[]> {
return this.http.get<Account[]>(this.apiUrl);
}
}
Component
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Account } from 'src/app/models/Account';
import { BankServiceService } from 'src/app/services/bank-service.service';
#Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css'],
})
export class UserComponent implements OnInit {
currentAccount: Account[] = [];
accountId: number;
accountHolder: string;
checkingAmount: number;
savingsAmount: number;
constructor(
private route: ActivatedRoute,
private bankService: BankServiceService
) {}
ngOnInit(): void {
// gets the parameter for the url (the param is the account id)
this.accountId = this.route.snapshot.params['id'];
console.log('id: ', this.accountId);
// pulls in all accounts
this.bankService
.getAccounts()
.subscribe(
(accounts) =>
(this.currentAccount = accounts.filter(
(account) => account.id === this.accountId
))
);
console.log('current account: ', this.currentAccount);
}
}
If I'm not mistaking, your issue is this one:
the account id received from the backend is a number
the account id pulled from the url is a string
In the filter function you are using strict equality, and that's why no account id passes the condition in the filter callback
You can switch from strict equality to loose equality (==) or do something like this for the filter callback:
(account) => account.id.toString() === this.accountId

Property 'id/name/duration/calories' does not exist on type 'unknown'

import { NgForm } from '#angular/forms';
import { Exercise } from './../exercise.model';
import { TrainingService } from './../training.service';
import { Component, OnInit, ViewChild } from '#angular/core';
import { AngularFirestore } from 'angularfire2/firestore';
import { Observable, Subscriber } from 'rxjs';
import 'rxjs/add/operator/map';
#Component({
selector: 'app-new-training',
templateUrl: './new-training.component.html',
styleUrls: ['./new-training.component.css'],
})
export class NewTrainingComponent implements OnInit {
exercises: Observable<Exercise[]>;
constructor(
private trainingService: TrainingService,
private db: AngularFirestore
) {}
ngOnInit(): void {
this.exercises = this.db
.collection('availableExercises')
.snapshotChanges()
.map((docArray) => {
return docArray.map((doc) => {
return {
id: doc.payload.doc.data().id,
name: doc.payload.doc.data().name,
duration: doc.payload.doc.data().duration,
calories: doc.payload.doc.data().calories,
};
});
});
}
onStartTraining(form: NgForm) {
this.trainingService.startExercise(form.value.exercise);
}
}
Have issue with id, name, duration, and calories.. they are all underlined and error says: Property does not exist on type 'unknown' for all four. So not sure what the issue is. I have tried as well
id: doc.payload.doc['id'],
name: doc.payload.doc['name'],
duration: doc.payload.doc['duration'],
calories: doc.payload.doc['calories'],
doesn't work as well. Was repeating after Maximilian Schwarzmuller's tutorial. Would appriciate any help.
The answer was to change the next line on the function ngOnInit from:
return docArray.map((doc) => {
Into:
return docArray.map((doc: any) => {
This will allow you to allow you to avoid type checking during compilation. As mentioned here

How to join multiple documents in a Firestore?

I have a Firestore DB with the following structure:
users
[uid]
name: 'User one'
artists
[uid]
style: 'Pop teste'
user_uid: [uid]
in my service I have
constructor(private afu: AngularFireAuth, private afs: AngularFirestore, private storage: AngularFireStorage) {
this.usersCollection = afs.collection<User>('users');
this.users = this.usersCollection.valueChanges();
}
getUsers() {
return this.users = this.usersCollection.snapshotChanges()
.pipe(map(changes => {
return changes.map(action => {
const data = action.payload.doc.data() as User;
return data
});
}));
}
How can join between users and artists ?
Using combineLatest is a great way. Since the user_uid doesn't exist on the user, I added the idField to the user as user_uid. View the code first then read below for an explanation.
import { Component, OnInit } from '#angular/core';
import { AngularFirestore } from '#angular/fire/firestore';
import { Observable, combineLatest } from 'rxjs';
interface User {
name: string;
user_uid: string;
}
interface Artist {
style: string;
user_uid: string;
}
interface Joined {
user_uid: string;
name: string;
style: string;
}
#Component({
selector: 'test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
users$: Observable<User[]>;
artists$: Observable<Artist[]>;
joined$: Observable<Joined[]>;
constructor(private afs: AngularFirestore){}
ngOnInit(){
this.users$ = this.afs.collection<User>('users').valueChanges({idField: 'user_uid'});
this.artists$ = this.afs.collection<Artist>('artists').valueChanges();
this.joined$ = combineLatest(this.users$, this.artists$, (users, artists) => {
const joinedAsMap: Map<string, Joined> = new Map(artists.map(oneArtist => [oneArtist.user_uid, { ...{name: null} , ...oneArtist}]));
users.forEach(one => joinedAsMap.set(one.user_uid , {...{name: one.name}, ...joinedAsMap.get(one.user_uid) } ));
const joined: Joined[] = Array.from(joinedAsMap.values());
return joined;
});
}
}
Make a joined interface
Get both observables
use combine latest
Build a map with uid as key and and artist as value. Set the name to null just so the types will work. Use the spread operator to merge some objects.
Loop through user and add in the user info to the value of each key
Build joined array from values of map
return the value
You can do this different ways but using es6 maps is a nice way to simplify some things. Also, didn't get a chance to test with a real database so you might need to verify. Also, this is all within the component for demonstration. You could do this in the service for sure.

Delete function error Angular 6 return undefined

I am trying to write a delete function to delete a movie from my object.
This is my code but when I click on a delete button I get DELETE: Error.
What do you think is the error in my code?
You can check out my code here ...
movie-model.ts
export class Movie {
Id: number;
Title: string;
Year: number;
Runtime: string;
Genre: string;
Director: string;
}
data.service.ts
import { Movie } from './model/movie.model';
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) { }
baseUrl: string = 'http://localhost:4200/';
getMovies() {
return fetch('https://www.omdbapi.com/?i=tt3896198&apikey=9fa6058b').then(function (resp) {
return resp.json()
});
}
createMovie(movie:Movie) {
return this.http.post(this.baseUrl, movie);
}
deleteMovie(movie:Movie){
return this.http.delete(this.baseUrl + movie.Id);
}
}
movie-list.component.ts
import { DataService } from './../data.service';
import { Movie } from './../model/movie.model';
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-movie-list',
templateUrl: './movie-list.component.html',
styleUrls: ['./movie-list.component.css']
})
export class MovieListComponent implements OnInit {
movies = [
{Id:1, Title: 'Avatar', Year: '2009'},
{Id:2, Title: 'Harry Potter', Year: '2001'},
{Id:3, Title: 'Spiderman 3', Year: '2007'}
];
constructor(private dataService:DataService){}
ngOnInit() {
this.getMovie().then(dt => {
this.movies.push(dt);
})
}
getMovie() {
return fetch('https://www.omdbapi.com/?i=tt3896198&apikey=9fa6058b').then(function (resp) {
return resp.json()
});
}
deleteMovie(movie: Movie): void {
this.dataService.deleteMovie(movie.Id)
.subscribe( data => {
this.movies = this.movies.filter(u => u !== movie);
})
};
}
This is the error I get ...
What can I do for the delete button to work and give me an alert and then delete itself from the object?
You try to acces an endpoint where actually your angular app is running: baseUrl: string = 'http://localhost:4200/'; This is the default port of your angular application on your local computer, and you try to call an delete endpoint of an external rest api I guess.
But the rest service does not run on your localhost on port 4200, thats why you get a 404 not found. I think you have to call delete on this endpoint https://www.omdbapi.com.
EDIT:
If you want delete a movie from your list, you have to delete the entry in your array. The easiest way would be if you change the id attribute to imdbID because the response type from omdbapi doesn't have an id attribute which means your id will always be undefined. Then when you want to delete an entry you could do it like this:
deleteMovie(imdbID: string): void {
this.movies = this.movies.filter(m => m.imdbID !== imdbID)
};
It's almost the same code that you have but without the delete call on the rest api. Because you don't want to delete the entry from the database but just on your angular app.
In the service file you have created method deleteMovie which accept a Movieobject
deleteMovie(movie:Movie){
return this.http.delete(this.baseUrl + movie.Id);
}
But in your Component movie-list.component.ts you are passing id in the delete method
import { DataService } from './../data.service';
import { Movie } from './../model/movie.model';
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-movie-list',
templateUrl: './movie-list.component.html',
styleUrls: ['./movie-list.component.css']
})
export class MovieListComponent implements OnInit {
movies = [
{Id:1, Title: 'Avatar', Year: '2009'},
{Id:2, Title: 'Harry Potter', Year: '2001'},
{Id:3, Title: 'Spiderman 3', Year: '2007'}
];
constructor(private dataService:DataService){}
ngOnInit() {
this.getMovie().then(dt => {
this.movies.push(dt);
})
}
getMovie() {
return fetch('https://www.omdbapi.com/?i=tt3896198&apikey=9fa6058b').then(function (resp) {
return resp.json()
});
}
deleteMovie(movie: Movie): void {
// this.dataService.deleteMovie(movie.Id) <- Your code error
// Pass movie object
this.dataService.deleteMovie(movie)
.subscribe( data => {
this.movies = this.movies.filter(u => u !== movie);
})
};
}

Cannot find namespace error for model in Angular2/TypeScript

The FeaturedCategories model
export class FeaturedCategories {
categories: Array<{ id: number, title: string, graphic: string, categorycards: Array<{}> }>;
}
Also tried this:
export class FeaturedCategories {
id: number;
title: string;
graphic: string;
categorycards: Object[];
}
The Component
import { Component, ChangeDetectionStrategy, ViewEncapsulation } from '#angular/core';
import { ApiService } from '../shared/services/api.service';
import { FeaturedCategories } from '../shared/models/home/featuredcategories';
#Component({
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.Emulated,
selector: 'home',
styleUrls: [ './home.component.css' ],
templateUrl: './home.component.html'
})
export class HomeComponent {
testFeaturedCategories: Array<FeaturedCategories>;
constructor(private api: ApiService) {
// we need the data synchronously for the client to set the server response
// we create another method so we have more control for testing
this.universalInit();
}
universalInit() {
console.log('universalInit...')
this.api.getFeatured()
.subscribe(categories => {
console.log('categories', categories);
this.testFeaturedCategories = categories
});
}
}
This will work: testFeaturedCategories: Array<{}>;
However I'm trying to use TypeScript to let my App know what type of model to expect.
This causes the error above:
testFeaturedCategories: FeaturedCategories.categories;
And if I just try this: testFeaturedCategories: FeaturedCategories;
I get a type [{}] is not assignable error.
UPDATE
So I noticed that when I commented out all the keys in my FeaturedCategories model finally the error goes away and
featuredCategories: FeaturedCategories[]; will work.
However now this is just an empty object without keys to expect :(
export class FeaturedCategories {
// id: number;
// title: string;
// graphic: string;
// categorycards: Object[];
}
this is working fine for me.
export class MyComponent {
categories: FeaturedCategories[] = [{
id: 1,
title: "",
graphic: "",
categorycards: [{}]
}];
}
export class FeaturedCategories{
id: number;
title: string;
graphic: string;
categorycards: Object[];
}
My problem was trying to type my Array, instead of just using the Typed objects that exist in the larger Array.
Also had a problem in my service, originally I had this:
/**
* Get featured categories data for homepage
* /wiki
*/
getFeatured(): Observable<[{}]> {
return this.http.get(`${this.getFeaturedUrl}/home`)
// .do(res => console.log('getFeatured res', res.json()))
.map(res => res.json())
.catch(this.handleError);
}
I did not need or could even use a type for my larger Categories array, what I needed was a smaller type for the exact Objects that exist in that larger Array:
export class FeaturedCategory {
id?: number;
type: string;
title: string;
graphic?: string;
video?: string;
categorycards: Array<{}>;
}
So now with the correct Type of Objects inside my Array I added it to the service:
getFeatured(): Observable<[FeaturedCategory]> {
return this.http.get(`${this.getFeaturedUrl}/home`)
.map(res => res.json())
.catch(this.handleError);
}
Now back in my Component I imported the single Typed Object
import { FeaturedCategory } from '../shared/models/home/featuredcategory';
Then typed the variable:
featuredCategories: Array<FeaturedCategory>;
And finally in ngOnInit
ngOnInit() {
this.api.getFeatured()
.subscribe(categories => {
console.log('categories', categories);
this.featuredCategories = categories;
});
}
No more errors :)

Categories

Resources