TypeError: Cannot read property 'map' of undefined with Angular v6 - json

For some reason the response JSON is not mapping correctly
Here is my html.
profile-search.component.html
<h3>Enter Username</h3>
<input (keyup)="search($event.target.value)" id="name" placeholder="Search"/>
<ul>
<li *ngFor="let package of packages$ | async">
<b>{{package.name}} v.{{package.repos}}</b> -
<i>{{package.stars}}</i>`enter code here`
</li>
</ul>
Here is component that the html pulls from.
profile-search.component.ts
import { Component, OnInit } from '#angular/core';
import { Observable, Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { NpmPackageInfo, PackageSearchService } from './profile-search.service';
#Component({
selector: 'app-package-search',
templateUrl: './profile-search.component.html',
providers: [ PackageSearchService ]
})
export class PackageSearchComponent implements OnInit {
withRefresh = false;
packages$: Observable<NpmPackageInfo[]>;
private searchText$ = new Subject<string>();
search(packageName: string) {
this.searchText$.next(packageName);
}
ngOnInit() {
this.packages$ = this.searchText$.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(packageName =>
this.searchService.search(packageName, this.withRefresh))
);
}
constructor(private searchService: PackageSearchService) { }
toggleRefresh() { this.withRefresh = ! this.withRefresh; }
}
Service that component pulls from.
profile-search.service.ts
import { Injectable, Input } from '#angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { HttpErrorHandler, HandleError } from '../http-error-handler.service';
export interface NpmPackageInfo {
name: string;
}
export const searchUrl = 'https://api.github.com/users';
const httpOptions = {
headers: new HttpHeaders({
'x-refresh': 'true'
})
};
function createHttpOptions(packageName: string, refresh = false) {
// npm package name search api
// e.g., http://npmsearch.com/query?q=dom'
const params = new HttpParams({ fromObject: { q: packageName } });
const headerMap = refresh ? {'x-refresh': 'true'} : {};
const headers = new HttpHeaders(headerMap) ;
return { headers, params };
}
#Injectable()
export class PackageSearchService {
private handleError: HandleError;
constructor(
private http: HttpClient,
httpErrorHandler: HttpErrorHandler) {
this.handleError = httpErrorHandler.createHandleError('HeroesService');
}
search (packageName: string, refresh = false): Observable<NpmPackageInfo[]> {
// clear if no pkg name
if (!packageName.trim()) { return of([]); }
// const options = createHttpOptions(packageName, refresh);
// TODO: Add error handling
return this.http.get(`${searchUrl}/${packageName}`).pipe(
map((data: any) => {
return data.results.map(entry => ({
name: entry.any[0],
} as NpmPackageInfo )
)
}),
catchError(this.handleError('search', []))
);
}
}
I have tried to alter
return this.http.get(`${searchUrl}/${packageName}`).pipe(
map((data: any) => {
return data.results.map(entry => ({
name: entry.any[0],
} as NpmPackageInfo )
)
to
login: data.login, and login: entry.login but keep getting the below error.
http-error-handler.service.ts:33 TypeError: Cannot read property 'map'
of undefined
at MapSubscriber.project (profile-search.service.ts:49)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next
(map.js:75)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next
(Subscriber.js:93)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next
(map.js:81)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next
(Subscriber.js:93)
at FilterSubscriber.push../node_modules/rxjs/_esm5/internal/operators/filter.js.FilterSubscriber._next
(filter.js:85)
at FilterSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next
(Subscriber.js:93)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber.notifyNext
(mergeMap.js:136)
at InnerSubscriber.push../node_modules/rxjs/_esm5/internal/InnerSubscriber.js.InnerSubscriber._next
(InnerSubscriber.js:20)
at InnerSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next
(Subscriber.js:93)

results in data.results is probably undefined, check that the data object matches the schema you're expecting it to.

map working on array but this.http.get(${searchUrl}/${packageName}) return object not array.
so data.results is undefined.

This is how I converted my object into an array, if anyone has a better way of doing please let me know.
return this.http.get(`${searchUrl}/${packageName}`).pipe(
map((data: any) => {
console.log(data);
var profile = Object.keys(data).map(function(key) {
return [(key) + ': ' + data[key]];
}
);
console.log(profile);
data = profile;
return data;
}),
catchError(this.handleError<Error>('search', new Error('OOPS')))
);
}
}

I fixed this issue by eliminating ".results"
from
.map((data: any) => this.convertData(data.results))
to
.map((data: any) => this.convertData(data))

To avoid the error, change
map((items) => items.map
to
map((items) => items?.map
Then set your result set as an empty array:
this.list = data ?? [];
PS: Used with Angular 14. In older versions you may need to change last one to data ? data : []

Related

How to parse an array of json object to typescript (object) after a get method

I am a beginner in angular and start to build my first app.My goal is to build a generic service that will be inherited from others service. I am following the structure of this link to my approach Generic HTTP Service .In read method i`m using Serializer class to convert the response json object to my typescript an it work. I got a map error. How can I solve it?
Service code:
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import 'rxjs/add/operator/map';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Resource } from '../models/resource.model';
import { Observable } from 'rxjs/Observable';
import { Serializer } from '../serializer/serializer';
import { AuthenticationService } from './authentication.service';
#Injectable()
export class SharedService<T extends Resource> {
constructor(
private httpClient: HttpClient,
private url: string,
private endpoint: string,
private authentication: AuthenticationService,
private serializer: Serializer
) { }
create(resource: T) {
let headers = new HttpHeaders();
headers = headers.set('Content-Type', 'application/json; charset=utf-8');
return this.httpClient.post(`${this.url}/${this.endpoint}`, JSON.stringify(resource), { headers: headers });
}
//PUT
update(item: T): Observable<T> {
return this.httpClient.put<T>(`${this.url}/${this.endpoint}`, JSON.stringify(item), { headers: this.addHeaders() })
.map(data => this.serializer.fromJson(data) as T);
}
//GET
read(id: number): Observable<T> {
return this.httpClient.get(`${this.url}/${this.endpoint}/${id}`, { headers: this.addHeaders() })
.map((data: any) => this.serializer.fromJson(data) as T);
}
//GET ALL
list(): Observable<T[]> {
return this.httpClient.get<T>(`${this.url}/${this.endpoint}` , {headers : this.addHeaders()})
.map((data: any) =>
this.convertData(data.items));
}
protected convertData(data: any): T[] {
return data.map(item => {this.serializer.fromJson(item)});
}
protected addHeaders() {
let token = ('Bearer ' + this.authentication.getToken()).valueOf();
let headers = new HttpHeaders();
headers = headers.set('Content-Type', 'application/json; charset=utf-8').set('Authorization', token);
return headers;
}
}
UserService:
import { Injectable } from '#angular/core';
import { SharedService } from './shared.service';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { User } from '../models/user/user.model';
import { AuthenticationService } from 'app/service/authentication.service';
import { UserSerializer } from '../serializer/user-serializer';
import { NgForm } from '#angular/forms';
#Injectable()
export class UserService extends SharedService<User>{
constructor(httpClient: HttpClient, authenticate: AuthenticationService) {
super(httpClient,
'http://localhost:8084/SuperCloud/webresources',
'user',
authenticate,
new UserSerializer()
);
}
UserSerializer:
import { User } from "../models/user/user.model";
import { Serializer } from "./serializer";
import { Resource } from "../models/resource.model";
export class UserSerializer extends Serializer {
fromJson(json: any): Resource {
const user = new User();
user.id = json.id;
user.name = json.name;
user.surname = json.surname;
user.email = json.email;
user.phoneNumber = json.phoneNumber;
user.password = json.password;
user.username = json.username;
user.active = json.active;
console.log('serializer');
console.log(user);
return user;
}
}
User model:
import { Resource } from "../resource.model";
export class User extends Resource{
username: string;
email: string;
name: string;
surname: string;
phoneNumber: string;
password?: string;
active : boolean;
}
UserService inherited inherited:
ngOnInit() {
this.userService.list().subscribe(
(data) => console.log(data)
);
}
Error:
core.es5.js:1020 ERROR TypeError: Cannot read property 'map' of
undefined
at UserService.SharedService.convertData (shared.service.ts:53)
at MapSubscriber.eval [as project] (shared.service.ts:48)
at MapSubscriber._next (map.js:79)
at MapSubscriber.Subscriber.next (Subscriber.js:95)
at MapSubscriber._next (map.js:85)
at MapSubscriber.Subscriber.next (Subscriber.js:95)
at FilterSubscriber._next (filter.js:90)
at FilterSubscriber.Subscriber.next (Subscriber.js:95)
at MergeMapSubscriber.notifyNext (mergeMap.js:151)
at InnerSubscriber._next (InnerSubscriber.js:25)
First of all, I assume the data that you passed into convertData function is not an array.
Only Array or Observable have map function in this case.
Also, chained function has been changed into pipeable operators in RxJS 6
https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md
Secondly, looking at the error message - I don't think the data value returned from the endpoint has value.
Third, data.map(item => {this.serializer.fromJson(item)}); - if the arrow function inside the map function is wrapped in curly bracket, you need to have return keyword.
in other word, data.map(item => {this.serializer.fromJson(item)}); should be data.map(item => this.serializer.fromJson(item)); or data.map(item => {return this.serializer.fromJson(item)});
Use subscribe instead of map to return the response.
return this.httpClient
.put<T>(`${this.url}/${this.endpoint}`, JSON.stringify(item), {
headers: this.addHeaders()
})
.subscribe(data => this.serializer.fromJson(data) as T);
BTW RXJs6 has changed the implementation of using observable map function

Angular 4 map certain JSON data to class and return observable

So I am trying to learn some basic Angular by creating an application that fetches and displays the current weather of a location using OpenWeather API.
This is what I have in my code currently:
app.component.ts:
import { Component } from '#angular/core';
import { WeatherService } from './weather.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [WeatherService]
})
export class AppComponent {
title = 'Ng-Weather';
cityName: string;
weather: Weather;
constructor(private weather: WeatherService) { }
search() {
this.weather.getWeatherbyName(this.cityName)
.subscribe(res => this.weather = res);
console.log(this.weather);
}
}
weather.service.ts:
import { Injectable } from '#angular/core';
import { Http, Response, URLSearchParams } from '#angular/http';
import { Observable } from 'rxjs';
import { Weather } from './weather';
#Injectable()
export class WeatherService {
APIurl = "http://api.openweathermap.org/data/2.5/weather";
Appid = "xxx";
weather: Weather;
constructor(private http: Http) { }
getWeatherbyName(name: string): Observable<any> {
let myParams = new URLSearchParams();
myParams.append('appid', this.Appid);
myParams.append('q', name);
return this.http.get(this.APIurl , { search: myParams} )
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
this.weather.city = body.name;
this.weather.description = body.weather[0].main;
this.weather.temp = body.main.temp;
console.log(this.weather);
}
private handleError(error: Response | any) {
console.error(error.message || error);
return Observable.throw(error.message || error);
}
}
weather.ts:
export class Weather {
city: String;
description: String;
temp: String;
}
So basically I am trying to map a JSON returned from OpenWeather API and get only some parts of the data and not the whole thing. The JSON returned is like following:
{
"coord":{
"lon":80.28,
"lat":13.09
},
"weather":[
{
"id":802,
"main":"Clouds",
"description":"scattered clouds",
"icon":"03n"
}
],
"base":"stations",
"main":{
"temp":303.15,
"pressure":1008,
"humidity":79,
"temp_min":303.15,
"temp_max":303.15
},
"visibility":6000,
"wind":{
"speed":3.1,
"deg":210
},
"clouds":{
"all":40
},
"dt":1504805400,
"sys":{
"type":1,
"id":7834,
"message":0.0017,
"country":"IN",
"sunrise":1504744074,
"sunset":1504788314
},
"id":1264527,
"name":"Chennai",
"cod":200
}
When the above code is executed, I get this error:
weather.service.ts:32 Cannot set property 'city' of undefined
Also how do I return an observable of type Weather and return that variable weather and catch it on the app.component.ts?
You are not creating an instance of the weather object before assigning its properties. You can do that explicitly like this:
this.weather = new Weather();
this.weather.city = body.name;
this.weather.description = body.weather[0].main;
this.weather.temp = body.main.temp;
console.log(this.weather);
OR
You can do something like this:
this.weather = {
city: body.name,
description: body.weather[0].main,
temp: body.main.temp
}
console.log(this.weather);
And to answer the second part of your question, you should be able to do this:
getWeatherbyName(name: string): Observable<Weather> {
// your other code
}
private extractData(res: Response) {
// your other code
return this.weather;
}
And to answer the third part of your question ... Observables are asynchronous. This means that they do not immediately return a value. Rather they provide for definition of a callback function that is executed when the data is returned. That means that the data is undefined until the data is returned and the callback function is executed.
So if you want to access the returned data in your code, you need to do in WITHIN the callback function. Like this:
search() {
this.weather.getWeatherbyName(this.cityName)
.subscribe(res => {
this.weather = res;
console.log(this.weather);
});
}

Angular: 'Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays'

I've created an angular app which gets data from a json file. But I'm having issues with showing the data in html. A lot of variables are in dutch, I'm sorry for that. I'm also a bit new to all of this :)
This is my service:
import {Injectable} from '#angular/core';
import {Http, RequestOptions, Response, Headers} from '#angular/http';
import {Observable} from "rxjs";
import {Afdelingen} from "./models";
#Injectable()
export class AfdelingService {
private afdelingenUrl = '/assets/backend/afdelingen.json';
constructor(private http: Http) {
}
getAfdelingen(): Observable<Afdelingen[]> {
return this.http.get(this.afdelingenUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = <Afdelingen[]>res.json();
return body || {};
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
addAfdeling(afdelingsNaam: string, afdeling: any): Observable<Afdelingen> {
let body = JSON.stringify({"afdelingsNaam": afdelingsNaam, afdeling: afdeling});
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
return this.http.post(this.afdelingenUrl, body, options)
.map(res => <Afdelingen> res.json())
.catch(this.handleError)
}
}
This is part of my json file:
{
"afdelingen": [
{
"afdelingsNaam": "pediatrie",
"kamernummer": 3.054,
"patientid": 10001,
"patientennaam": "Joske Vermeulen",
"reden": "Appendicitis",
"opname": "12/05/2017",
"ontslag": "28/06/2017",
"behandelingstype": "nazorg",
"behandelingsomschrijving": "wondverzorging",
"behandelingsdatum": "10/06/2017",
"behandelingstijd": "10:20",
"vegitarisch": false,
"Opmerkingen": "",
"sanitair": true,
"kinderverzorgingsruimte": false,
"salon": true,
"hulp": true,
"width": 5,
"height": 5
},
{
"afdelingsNaam": "pediatrie",
"kamernummer": 3.055,
"patientid": 10002,
"patientennaam": "Agnes Vermeiren",
"reden": "Beenbreuk",
"opname": "18/05/2017",
"ontslag": "30/06/2017",
"behandelingstype": "nazorg",
"behandelingsomschrijving": "wondverzorging",
"behandelingsdatum": "10/06/2017",
"behandelingstijd": "10:20",
"vegitarisch": true,
"Opmerkingen": "",
"sanitair": true,
"kinderverzorgingsruimte": false,
"salon": true,
"hulp": false,
"width": 5,
"height": 5
}]}
The Component:
import {Component, OnInit, Input} from '#angular/core';
import {Afdelingen} from "../models";
import {AfdelingService} from "../afdeling.service";
import {PatientService} from "../patient.service";
#Component({
selector: 'app-afdeling',
templateUrl: './afdeling.component.html',
styleUrls: ['./afdeling.component.css']
})
export class AfdelingComponent implements OnInit {
afdeling: Afdelingen[];
errorMessage:string;
constructor(private afdelingService: AfdelingService, private patientService: PatientService) { }
ngOnInit() {
this.getData()
}
getData() {
this.afdelingService.getAfdelingen()
.subscribe(
data => {
this.afdeling = data;
console.log(this.afdeling);
}, error => this.errorMessage = <any> error);
}
}
and the html:
<ul>
<li *ngFor="let afd of afdeling">
{{afd.patientid}}
</li>
</ul>
As the error messages stated, ngFor only supports Iterables such as Array, so you cannot use it for Object.
change
private extractData(res: Response) {
let body = <Afdelingen[]>res.json();
return body || {}; // here you are return an object
}
to
private extractData(res: Response) {
let body = <Afdelingen[]>res.json().afdelingen; // return array from json file
return body || []; // also return empty array if there is no data
}
Remember to pipe Observables to async, like *ngFor item of items$ | async, where you are trying to *ngFor item of items$ where items$ is obviously an Observable because you notated it with the $ similar to items$: Observable<IValuePair>, and your assignment may be something like this.items$ = this.someDataService.someMethod<IValuePair>() which returns an Observable of type T.
Adding to this... I believe I have used notation like *ngFor item of (items$ | async)?.someProperty
You only need the async pipe:
<li *ngFor="let afd of afdeling | async">
{{afd.patientid}}
</li>
always use the async pipe when dealing with Observables directly without explicitly unsubscribe.
I was the same problem and as Pengyy suggest, that is the fix. Thanks a lot.
My problem on the Browser Console:
PortafolioComponent.html:3 ERROR Error: Error trying to diff '[object
Object]'. Only arrays and iterables are allowed(…)
In my case my code fix was:
//productos.service.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
#Injectable()
export class ProductosService {
productos:any[] = [];
cargando:boolean = true;
constructor( private http:Http) {
this.cargar_productos();
}
public cargar_productos(){
this.cargando = true;
this.http.get('https://webpage-88888a1.firebaseio.com/productos.json')
.subscribe( res => {
console.log(res.json());
this.cargando = false;
this.productos = res.json().productos; // Before this.productos = res.json();
});
}
}

setting google map API key from angular 2 service

Im using sebastine google map in angular 2 application. I know AgmCoreModule.forRoot({ apiKey: "xxxxxxxx" }) can be used to set API key but I need to set API key from a angular service in my #component is it possible....help needed.
You may need to update provide a custom provider like this { provide: MapsAPILoader, useClass: CustomLazyAPIKeyLoader } where you have imported AgmCoreModule.
And in CustomLazyAPIKeyLoader class override the load method.
import { Injectable, Inject } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { MapsAPILoader, LAZY_MAPS_API_CONFIG, LazyMapsAPILoaderConfigLiteral, GoogleMapsScriptProtocol } from 'angular2-google-maps/core';
import { DocumentRef, WindowRef } from 'angular2-google-maps/core/utils/browser-globals';
#Injectable()
export class CustomLazyAPIKeyLoader extends MapsAPILoader {
private _scriptLoadingPromise: Promise<void>;
private _config: LazyMapsAPILoaderConfigLiteral;
private _windowRef: WindowRef;
private _documentRef: DocumentRef;
constructor( #Inject(LAZY_MAPS_API_CONFIG) config: any, w: WindowRef, d: DocumentRef, private http: Http) {
super();
this._config = config || {};
this._windowRef = w;
this._documentRef = d;
}
load(): Promise<void> {
if (this._scriptLoadingPromise) {
return this._scriptLoadingPromise;
}
const script = this._documentRef.getNativeDocument().createElement('script');
script.type = 'text/javascript';
script.async = true;
script.defer = true;
const callbackName: string = `angular2GoogleMapsLazyMapsAPILoader`;
this.http.get("getKey")
.subscribe((res: any) => {
this._config.apiKey = res._body;
script.src = this._getScriptSrc(callbackName);
this._documentRef.getNativeDocument().body.appendChild(script);
});
this._scriptLoadingPromise = new Promise<void>((resolve: Function, reject: Function) => {
(<any>this._windowRef.getNativeWindow())[callbackName] = () => { console.log("loaded"); resolve(); };
script.onerror = (error: Event) => { reject(error); };
});
return this._scriptLoadingPromise;
}
private _getScriptSrc(callbackName: string): string {
let protocolType: GoogleMapsScriptProtocol =
(this._config && this._config.protocol) || GoogleMapsScriptProtocol.HTTPS;
let protocol: string;
switch (protocolType) {
case GoogleMapsScriptProtocol.AUTO:
protocol = '';
break;
case GoogleMapsScriptProtocol.HTTP:
protocol = 'http:';
break;
case GoogleMapsScriptProtocol.HTTPS:
protocol = 'https:';
break;
}
const hostAndPath: string = this._config.hostAndPath || 'maps.googleapis.com/maps/api/js';
const queryParams: { [key: string]: string | Array<string> } = {
v: this._config.apiVersion || '3',
callback: callbackName,
key: this._config.apiKey,
client: this._config.clientId,
channel: this._config.channel,
libraries: this._config.libraries,
region: this._config.region,
language: this._config.language
};
const params: string =
Object.keys(queryParams)
.filter((k: string) => queryParams[k] != null)
.filter((k: string) => {
// remove empty arrays
return !Array.isArray(queryParams[k]) ||
(Array.isArray(queryParams[k]) && queryParams[k].length > 0);
})
.map((k: string) => {
// join arrays as comma seperated strings
let i = queryParams[k];
if (Array.isArray(i)) {
return { key: k, value: i.join(',') };
}
return { key: k, value: queryParams[k] };
})
.map((entry: { key: string, value: string }) => { return `${entry.key}=${entry.value}`; })
.join('&');
return `${protocol}//${hostAndPath}?${params}`;
}
}
this.http.get("getKey")
.subscribe((res: any) => {
this._config.apiKey = res._body;
script.src = this._getScriptSrc(callbackName);
this._documentRef.getNativeDocument().body.appendChild(script);
});
Above code will make it async.
I added an resolver to get the API key
import { Resolve, ActivatedRouteSnapshot } from '#angular/router'
import { Injectable, Inject } from '#angular/core'
import { SomeService } from '../services/some.service'
import { LazyMapsAPILoaderConfigLiteral, LAZY_MAPS_API_CONFIG } from '#agm/core'
import { Observable, of } from 'rxjs'
import { map, catchError } from 'rxjs/operators'
#Injectable()
export class GoogleMapAPiResolver implements Resolve<boolean> {
constructor(
private someService: SomeService,
#Inject(LAZY_MAPS_API_CONFIG)
private config: LazyMapsAPILoaderConfigLiteral
) {}
resolve(router: ActivatedRouteSnapshot): Observable<boolean> {
return this.someService.getGoogleMapApiKey().pipe(
catchError(error => {
return of(false)
}),
map(response => {
this.config.apiKey = response
return true
})
)
}
}
The SomeService consume an endpoint that return the Key
you have put the API key in the app.module.ts under the #NgModule
and make sure to enable Maps JavaScript API in the google cloud console
https://console.cloud.google.com/apis/library/maps-backend.googleapis.com
Thanks!
#NgModule({
imports: [
BrowserModule,
FormsModule,
AgmCoreModule.forRoot({
// please get your own API key here:
// https://developers.google.com/maps/documentation/javascript/get-api-key?hl=en
apiKey: 'API_KEY'
})
],
declarations: [ AppComponent, HelloComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }

How can I sanitize css properties to use in template given from a data service

I need to generate sanitized css property to use with my component template to set the background image of the div:
<div *ngFor="let Item of Items"
[style.background-image]="Item.imageStyle
(click)="gotoDetail(Item.iditems)">
</div>
using data obtained through a data service. The component is:
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { DomSanitizer } from '#angular/platform-browser';
import { OnInit } from '#angular/core';
import { Item } from '../models/Item';
import { CollectionDataService } from '../services/CollectionData.service';
#Component({
selector: 'mainpage',
templateUrl: 'app/mainpage/mainpage.component.html',
styleUrls: ['app/mainpage/mainpage.component.css']
})
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.collectionDataService.getItems().subscribe(
Items => this.Items = Items
);
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
constructor(
private router: Router,
private sanitizer: DomSanitizer,
private collectionDataService: CollectionDataService
) {
}
gotoDetail($iditems: number): void {
this.router.navigate(['/viewer', $iditems]);
}
}
But it doesn't work because the statement that generates the sanitized property
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
doesn't find the loaded data. The error that I'm seeing in the browser console is:
core.umd.js:3070 EXCEPTION: Uncaught (in promise): Error: Error in ./MainpageComponent class MainpageComponent_Host - inline template:0:0 caused by: Cannot read property 'map' of undefined
TypeError: Cannot read property 'map' of undefined
The data service is:
import { Injectable } from '#angular/core'
import { Http } from '#angular/http'
import { Item } from '../models/Item';
import { DomSanitizer } from '#angular/platform-browser';
#Injectable()
export class CollectionDataService {
constructor(
private http: Http,
private sanitizer: DomSanitizer
) { }
getItems() {
return this.http.get('app/mocksdata/items.json').map(
response => <Item[]>response.json().items
)
}
}
And the provided items.json:
{
"items": [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
}
If I set static data in the component, instead of using the data service, everything works:
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.Items = [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
How can I force the sanitizer statement to wait that the async data are fully loaded? Alternatively how can I generate sanitized properties directly in the service?
EDIT
The best answer comes from PatrickJane below:
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}
I also solved this problem working directly in the service method (credits), but it is more verbose:
return this.http.get('app/mocksdata/items.json')
.map( (responseData) => {
return responseData.json().items;
})
.map(
(iitems: Array<any>) => {
let result:Array<Item> = [];
if (iitems) {
iitems.forEach((iitem) => {
iitem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+iitem.iditems+".jpg)");
result.push(<Item>iitem);
});
}
return result;
}
)
The subscribe function is async so your map function called before the subscribe function run. So in this phase the array is undefined because you doesn't set any initial value.
The solution is to do this inside the subscribe function and to initialize the Items with empty array.
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}