Angular 6 HttpClient using Map - javascript

For learning purposes I am trying to get datas from a json fake API and add "hey" to all titles before returning an Observable. So far I can display the data if I don't use Map and even while using map if I console.log my variable it says Observable but it does not display in my template.
<div class="col-6" *ngIf="courseDatas$ | async as courses else NoData">
<div class="card" *ngFor="let course of courses">
<div class="card-body">
<span><strong>User ID is : </strong>{{course.userId}}</span><br>
<span><strong>Title is : </strong>{{course.title}}</span><br>
<span><strong>Body is : </strong>{{course.body}}</span><br>
<span><strong>ID is : </strong>{{course.id}}</span>
</div>
<ng-template #NoData>No Data Available</ng-template>
</div>
</div>
App component :
import {Component, OnInit} from '#angular/core';
import {PostsService} from "./posts.service";
import {Observable} from "rxjs";
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
courseDatas$ : Observable<any>;
constructor(private posts : PostsService){}
ngOnInit(){
this.courseDatas$ = this.posts.getData();
}
}
posts Service :
import { Injectable } from '#angular/core'
import {HttpClient} from "#angular/common/http";
import {Observable} from "rxjs";
import {map} from "rxjs/operators";
#Injectable({
providedIn: 'root'
})
export class PostsService {
private postURL: string = 'https://jsonplaceholder.typicode.com/posts';
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get(this.postURL).pipe(
map(data => {
for (let datas of (data as Array<any>)){
datas.title = datas.title + "Hey";
}
}),
);
}
So, if I don't use the map operator in my getData method in my service everything displays properly. If I use the map operator if I console.log coursesDatas$ in App.Component the console says Observable so I don't understand why it does not work with my async pipe in the template. Also, if I use console.log(datas.title) inside my map operator it does log every titles with Hey at the end.

map should return something to mutate current property, in your case I guess you should return the data
getData(): Observable<any> {
return this.http.get(this.postURL).pipe(
map(data => {
for (let datas of (data as Array<any>)){
datas.title = datas.title + "Hey";
}
return data;
}),
);
}
by the way you can use Array.prototype's map instead of for loop too, to mutate your data
getData(): Observable<any> {
return this.http.get(this.postURL).pipe(
map(data => data.map(d => (d.title = d.title +"Hey", d));
}),
);
}
note that if curly braces are missing in arrow function, it will return automatically

Related

Casting firestore observables to custom objects

I'm new to angular and firestore and trying to figure out how to cast the data received from firebase directly to models. What is the best approach here?
Currently I get the data, but it looks like it's not casted into a Blimp object. When I try to call getImageUrl() on it in the view, I get the following error message.
ERROR TypeError: _v.context.$implicit.getImageUrl is not a function
So my question: What is the best and cleanest way to cast these observables to the correct local model? I was expecting the tags to cast it by default.
Current code
Custom model class
export class Blimp {
created_at: Date;
file_location: string;
id: string;
constructor() {
console.log('OBJ');
}
getImageUrl() {
return "https://*.com" + this.file_location;
}
}
Service class
import { Injectable } from '#angular/core';
import {Blimp} from '../models/blimp';
import { AngularFirestore } from '#angular/fire/firestore';
import {AngularFireStorage, AngularFireUploadTask} from '#angular/fire/storage';
import {Observable} from 'rxjs';
import {finalize} from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class BlimpService {
blimps: Observable<Blimp[]>;
constructor(private fireStore: AngularFirestore, private fireDisk: AngularFireStorage) { }
getBlimps() {
this.blimps = this.fireStore.collection<Blimp>('blimps').valueChanges();
return this.blimps;
}
}
Display component
import { Component, OnInit } from '#angular/core';
import {BlimpService} from '../../services/blimp.service';
import {Observable} from 'rxjs';
import {Blimp} from '../../models/blimp';
#Component({
selector: 'app-blimp-viewer',
templateUrl: './blimp-viewer.component.html',
styleUrls: ['./blimp-viewer.component.scss'],
})
export class BlimpViewerComponent implements OnInit {
blimps: Observable<Blimp[]>;
constructor(private blimpService: BlimpService) { }
ngOnInit() {
this.blimps = this.blimpService.getBlimps();
}
}
View
<ul>
<li *ngFor="let blimp of blimps | async">
{{ blimp | json}}
<img [src]="blimp.getImageUrl()" />
</li>
</ul>
Update #1
Changed the code to
I now have changed your example to: getBlimps() {
this.blimps = this.fireStore.collection<Blimp>('blimps')
.valueChanges()
pipe(map(b => {
let blimp = new Blimp();
blimp.created_at = b.created_at;
blimp.file_location = b.file_location;
blimp.id = b.id;
return blimp;
}));
return this.blimps;
}
This still complains in the view about the getImageUrl() not being found on the object.
# Solution
Looks like I forget a . (dot) in the last code
This code works:
this.blimps = this.fireStore.collection<Blimp>('blimps')
.valueChanges()
.pipe(map(collection => {
return collection.map(b => {
let blimp = new Blimp();
blimp.created_at = b.created_at;
blimp.file_location = b.file_location;
blimp.id = b.id;
return blimp;
});
}));
return this.blimps;
Concept :
You don't cast an observable to an object model. An observable is a stream which has a lifecycle.
An observable emits value to its subscribers, you need to subscribe to your observable to be notified when it emits value. You also need to close the subscription or the subscription will last until your observable complete causing memory leaks.
I can see you're using | asyncin your html template, it's a subscription handled by angular that auto-unsubscribe when needed.
Get data :
You need to map the data you received to a Blimp object, you can use map operator.
blimps$: Observable<Blimp[]>; // naming convention, suffix your observable with $
blimps$ = this.fireStore.collection<Blimp>('blimps')
.valueChanges()
.pipe(map(collection => {
return collection.map(b => {
let blimp = new Blimp();
blimp.created_at = b.created_at;
blimp.file_location = b.file_location;
blimp.id = b.id;
console.log(blimp);
console.log(b);
return blimp;
});
}));
return this.blimps;
As we changed blimps to blimps$, change your html template :
*ngFor="let blimp of blimps$ | async"
EDIT :
You can use your class constructor to initialize your object :
export class Blimp {
created_at?: Date;
file_location?: string;
id?: string;
constructor(blimp: Blimp = {}) {
this.created_at = blimp.created_at;
this.file_location = blimp.file_location;
this.id = blimp.id;
}
getImageUrl() {
return `https://*.com${this.file_location}`; // use string interpolation here
}
blimps$ = this.fireStore.collection<Blimp>('blimps')
.valueChanges()
.pipe(map(collection => {
return collection.map(b => new Blimp(b));
}));

Angular method returns undefined

As a beginner, I facing a problem with Angular and Observables. I have API for getting information about one specific restaurant in the database, but I have to get it with a POST request. I successfully get restaurantID from auth.service and another API when the restaurant is logged in, But when I tried to log restaurant in console, I get undefined. Uniformly I don't have permission to show API here. The code:
restaurant.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Restaurant } from '../models/Restaurant';
import { LoggedRestaurant } from '../models/LoggedRestaurant';
#Injectable({
providedIn: 'root'
})
export class RestaurantService {
private restaurantUrl = 'xxxxxxxxxxxx';
public restaurant: Restaurant;
public loggedRestaurant: LoggedRestaurant
public restaurantID;
constructor(private http: HttpClient) { }
public getRestaurant(): Observable<LoggedRestaurant> {
return this.http.post<LoggedRestaurant>(this.restaurantUrl, this.restaurantID);
}
}
informacije.component.ts
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../services/auth.service';
import { RestaurantService } from '../services/restaurant.service';
import { Restaurant } from '../models/Restaurant';
import { LoggedRestaurant } from '../models/LoggedRestaurant';
import { Observable } from 'rxjs';
#Component({
selector: 'app-informacije',
templateUrl: './informacije.component.html',
styleUrls: ['./informacije.component.scss']
})
export class InformacijeComponent implements OnInit {
restaurant: Restaurant;
loggedRestaurant: LoggedRestaurant;
restaurantID;
constructor(private restaurantService: RestaurantService, private authService: AuthService ) { }
getRestaurant() {
return this.restaurantService.getRestaurant()
}
ngOnInit() {
this.restaurant = this.authService.currRestaurant[0];
console.log(this.restaurant)
console.log(this.loggedRestaurant)
this.restaurantID = this.restaurant.id;
console.log(this.restaurantID)
this.restaurantService.restaurantID =this.restaurantID;
}
}
httpClient.post() returns an observable (RXJS). So you need to subscribe to that. Otherwise, you may use the async pipe.
in your html, you can try this,
<span>{{getRestaurant() | aync}}</span>
OR,
you can declare a variable in your ts like data, and,
this.restaurantService.getRestaurant().subscribe(payload => {
this.data = payload;
})
and in your html, you can add,
<span *ngIf="data">{{data}}</span>
You need to subscribe to your API call.
In informacije.component.ts
getRestaurant() {
return this.restaurantService.getRestaurant()
.subscribe(data => this.restaurant = data);
}
This will asign the value returned by your service to your restaurant field in an asynchronous fashion.
In ngOnInit() call getRestaurant as follows
async ngOnInit() {
let restaurant = await this.getRestaurant().toPromise();
...
}

(Angular2) JSON data (http.get()) is undefined, and data is not updated in the component

My http-data.service accepts json for output in the component template. Initially, the console shows that the first few calls are given undefined, and the following calls are already taking json, but also if you check the component, then the component shows that the method that outputs the data to the component is called only once and since the data has not yet arrived it writes undefined , But not updated after the arrival of json. Help please understand why? Thank you
My http-data.service:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
import {Response} from '#angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class HttpService{
constructor(private http: Http) {}
getDataOrganizations(): Observable<any[]>{
return this.http.get('http://localhost:3010/data')
.map((resp:Response)=>{
let dataOrganizations = resp.json().organization;
return dataOrganizations;
});
}
getDataModules(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataModules = resp.json().modules;
return dataModules;
});
}
getDataPresets(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataPresets = resp.json().presets;
return dataPresets;
});
}
getDataModuleItems(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataModuleItems = resp.json().module_items;
return dataModuleItems;
});
}
}
My data-all.service
import { Injectable, EventEmitter } from '#angular/core';
import {Response} from '#angular/http';
import { ModuleModel } from './model-module';
import { ModuleItemsModel } from './model-module-items';
import data from '../data/data-all';
import { PriceService } from './price.service';
import { HttpService } from './http-data.service';
#Injectable()
export class ModuleDataService {
constructor(private priceService: PriceService, private httpService: HttpService){
this.dataMinMaxSum = {minSum: 0, maxSum: 0}
}
private currentPopupView: EventEmitter<any> = new EventEmitter<any>();
private dataModules: ModuleModel[] = this.getDataModules();
private dataMinMaxSum: {};
private dataCalculateVariationOrg: any[];
private dataChangeExecutor: any[];
subscribe(generatorOrNext?: any, error?: any, complete?: any) {
this.currentPopupView.subscribe(generatorOrNext, error, complete);
}
calculte(){
return this.priceService.getDataPrice();
}
getDataModules(){
this.httpService.getDataModules().subscribe(((modules)=>{this.dataModules = modules; console.log(this.dataModules);}));
console.log('dataModules');
console.log(this.dataModules);
return this.dataModules;
}
---------------------------------------------------------------------------
}
My left-block.component
import { Component, OnInit} from '#angular/core';
import { ModuleDataService } from '../../service/data-all.service';
import { ModuleModel } from '../../service/model-module';
#Component({
moduleId: module.id,
selector: 'modules-left-block',
templateUrl: './modules-left-block.html',
styleUrls: ['modules-left-block.css']
})
export class ModuleLeft implements OnInit{
modules: ModuleModel[];
constructor(private modulesAll: ModuleDataService){}
ngOnInit(){
this.modules = this.modulesAll.getDataModules();
console.log("view");
console.log(this.modulesAll.getDataModules());
}
onToggle(module: any){
this.modulesAll.toggleModules(module);
}
}
My left-block.component.html
<div class="modules-all">
<div class="modules-all-title">Все модули</div>
<div class="module-item" *ngFor="let module of modules" [ngClass]="{ 'active': module.completed }" (click)="onToggle(module)">{{module?.title}}</div>
</div>
In the component this.modulesAll.getDataModules () method is why it is executed only once without updating (write in console => undefined), if there are any thoughts, write, thanks.
This behaviour is due to the .subscribe() method does not wait for the data to arrive and I'm guessing you already know this. The problem you're facing is because, you have .subscribe to the getDataModules() service in the wron place. You shouldn't subscribe to a service in another service (at leat in this case). Move the subscribe method to the left-block.component and it should work.
getDataModules() {
this.httpService.getDataModules().subscribe(((modules) => {
this.dataModules = modules;
console.log(this.dataModules);
}));
console.log('dataModules');
console.log(this.dataModules);
return this.dataModules;
}
It should look somethig like this:
#Component({
moduleId: module.id,
selector: 'modules-left-block',
templateUrl: './modules-left-block.html',
styleUrls: ['modules-left-block.css']
})
export class ModuleLeft implements OnInit {
modules: ModuleModel[] = new ModuleModel();
constructor(private modulesAll: ModuleDataService, private httpService: HttpService) {}
ngOnInit() {
this.getDataModles();
//this.modules = this.modulesAll.getDataModules();
console.log("view");
//console.log(this.modulesAll.getDataModules());
}
onToggle(module: any) {
this.modulesAll.toggleModules(module);
}
getDataModules(): void {
this.httpService.getDataModules().subscribe(((modules) => {
this.modules = modules;
console.log(this.dataModules);
}));
}
}

Observables with angular 2 isn't working (Http method)

I'm having trouble with my service. I have a service wich get a JSON from the HTTP module and fill a class 'olders' with it. But when I call my service, it doesn't do anything and my class olders is still undifined...
And if I make a *ngFor directive in the yearbook.component.html, I just get nothing...
I think my problem came from my service, but I don't know where is the error...
Even if i put the console.log inside of the subscribe
my yearbook.component :
import {Component} from '#angular/core'
import {Olders} from './olders'
import {OldersService} from './olders.service'
#Component({
moduleId: module.id,
selector: 'yearbook',
templateUrl: 'yearbook.component.html',
})
export class YearbookComponent {
olders : Olders[];
constructor(
private _oldersService : OldersService
){}
ngOnInit() {
this._oldersService.getOldersfromAPI()
.subscribe( res => {this.olders = res,
console.log(this.olders)},
err => console.error(err.status)
);
}
}
and the olders.service :
import {Injectable} from '#angular/core'
import {Http} from '#angular/http'
import {Olders} from './olders'
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/do'
#Injectable ()
export class OldersService {
constructor(private _http:Http) {
}
getOldersfromAPI(){
return this._http.get('../CDN/olders.json')
.do(x => console.log(x))
.map(olders => {olders = olders.json()});
}
}
Thanks in advance for all your answers guys
You are missing a return statement in your mapping:
.map(olders => {olders = olders.json()});
should be:
.map(olders => { return olders = olders.json()});

How to map from one model to another in Angular 2?

I have this function in my Angular 2 component, which calls Web Api:
getNextConjunctionApi(): Observable<any> {
return this._http.get(this.uri + '/GetNextConjunction')
.map((res: Response) => res.json());
}
Web Api returns a complex object, which I would like to map to an Angular 2 model called ClientModel:
export class ClientModel {
prop1: string;
prop2: string;
...
}
Can this mapping be done by rewriting the map functionality, or need I do it in some other way?
.map((res: Response) => res.json());
I accomplished this with a slightly different approach. I had my component call a service that would return an observable. My component could then use a specific type that I created. I will show you what I have done for a blog.
posts.component.ts
import { Component, OnInit } from '#angular/core';
import { PostsService } from './posts.service';
import { PostComponent } from '../post/post.component'; // --> This is my custom type
import { Observable } from 'rxjs/Rx';
#Component({
selector: 'app-posts',
templateUrl: './posts.component.html',
providers: [PostsService]
})
export class PostsComponent implements OnInit {
posts: Observable<PostComponent[]>; // --> I use the type here
constructor( private _postsService: PostsService ) { }
ngOnInit() {
this._postsService.getAllPosts()
.subscribe(
posts => { this.posts = posts }, // --> I add the return values here
error => { console.log(error) }
);
}
}
The above has three key pieces. I import the custom type, PostComponent, set posts to an Observable of type PostComponent array, and as the Observable comes back, I add the values to the posts array.
posts.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class PostsService {
constructor( private _http: Http ) {}
getAllPosts(){
return this._http.get('[INSERT API CALL]')
.map((response: Response) => response.json())
.catch(msg => Observable.throw(msg));
}
}
In my service, I only map the response to response.json. This gives me more information than I need. I 'filter' it in my post.component
post.component.ts
import { Component, Input } from '#angular/core';
#Component({
selector: 'post',
templateUrl: './post.component.html'
})
export class PostComponent{
#Input() curPost: {
'id': number,
'title': string,
'author': string,
'date': string,
'body' : string,
};
constructor() { }
}

Categories

Resources