Angular - map always returns undefined - javascript

I have an angular page which has a list of properties. Each property has a link to view the details of the property. I have a function which maps the property by id, however as a return I am always getting undefined (printed in console).
service.ts
getProperties(): Observable<IProperty[]>{
return this.http.get<IProperty>('http://localhost:3000/')
.do(data => this.properties.push(JSON.stringify(data)))
.catch(this.handleError);
}
getProperty(id:number): Observable<IProperty>{
return this.getProperties()
.map((properties: IProperty[])=> properties.find(p=>p.propertyId===id));
}
propertyDetail.component.ts
import { Component, OnInit } from '#angular/core';
import { IProperty } from './property';
import { ActivatedRoute, Router } from '#angular/router';
import { ApiService} from '../api/api.service';
import { PropertyGuardService } from './property-guard.service'
#Component
({
selector: 'propertyDetail',
templateUrl: './propertyDetail.component.html',
providers: [PropertyGuardService]
})
export class PropertyDetailComponent implements OnInit
{
pageTitle:string = "Property Details";
property: IProperty;
errorMessage: string ="";
constructor(private _route : ActivatedRoute,
private _router: Router,
private apiService: ApiService){
console.log(this._route.snapshot.paramMap.get('id'));
}
ngOnInit(){
const param = this._route.snapshot.paramMap.get('id');
if(param){
const id = +param;
this.getProperty(id);
console.log(this.getProperty(id));
}
}
getProperty(id:number)
{
this.apiService.getProperty(id).subscribe(
property => this.property = property,
error => this.errorMessage = <any>error);
}
onBack(): void
{
this._router.navigate(['/properties']);
}
}

You set one property using property => this.property = property by subscribe in the suitable time and now you can use this.property in the view like {{property?.id}}.
If you want to check Property whether being fetched properly or not on the console, you can use as follows:
getProperty(id:number){
this.apiService.getProperty(id).subscribe(
property => {
this.property = property,
console.log(property) // check property returned here
});
}

Related

Have realtime updates for a single Firestore document

There is a lot of documentation and examples of firestore collections getting realtime updates. However, there is very little for those who wish to have a single document have real time updates. I want to have a single document (an item), on a page where only the item will be viewed and manipulated and any changes to document, will have realtime updating.
Here is my component that wants to do stuff with the item:
import { Component, OnInit } from '#angular/core';
import { ItemsService } from '../shared/items.service';
import { ActivatedRoute, Router } from '#angular/router';
#Component({
selector: 'app-view-item',
templateUrl: './view-item.component.html',
styleUrls: ['./view-item.component.css']
})
export class ViewItem implements OnInit {
item;
private sub: any;
constructor(
// Service used for Firebase calls
private itemsService: ItemsService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
// Item retrieved from */item/:id url
this.sub = this.route.params.subscribe(params => {
this.getItem(params['id']);
});
}
getItem = (id) => {
this.itemsService.getItem(id).subscribe(res => {
console.log(res);
this.item = res;
console.log(this.item);
});
}
And the service it uses for calls:
import { Injectable } from '#angular/core';
import { AngularFirestore, AngularFirestoreDocument } from '#angular/fire/firestore';
#Injectable({
providedIn: 'root'
})
export class ItemsService {
constructor(
private firestore: AngularFirestore
)
getItem(id) {
return this.firestore.collection('items').doc(id).snapshotChanges();
}
}
The log I get for console.log(this.item) is undefined. Calling this.item in the console returns the same. I am unsure of how to proceed and would appreciate any guidance. Logging res in the console returns a byzantine object. Perhaps that's how I access the item, but if so, why is it not saved in this.item and how do I access the item's values?
snapshotChanges returns an observable of actions, not the actual value.
You should extract the value with action.payload.doc.data():
So your code should look like the following example.
getItem(id) {
return this.firestore.collection('items').doc(id).snapshotChanges()
.pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
return { id, ...data };
})
);
}
Or you can use valueChanges of doc.
getItem(id) {
return this.firestore.collection('items').doc(id).valueChanges();
}

_co.photo is undefined console error and error context, but code works as expected

I got problem with angular component.
When I make my component with selector, it works as expected: execute httpget, and render photo with title.
But in console I got two errors:
ERROR TypeError: "_co.photo is undefined"
View_PhotoHolderComponent_0 PhotoHolderComponent.html:2
and
ERROR CONTEXT
...
PhotoHolderComponent.html:2:8
View_PhotoHolderComponent_0 PhotoHolderComponent.html:2
I got html:
<div class="photo-holder">
<h2>{{photo.title}}</h2>
<img src="{{photo.url}}">
</div>
and ts:
import { Component, OnInit } from '#angular/core';
import { Photo } from './photo'
import { PhotoDeliveryService } from '../photo-delivery-service.service'
#Component({
selector: 'app-photo-holder',
templateUrl: './photo-holder.component.html',
styleUrls: ['./photo-holder.component.css']
})
export class PhotoHolderComponent implements OnInit {
photo:Photo
constructor( private photoService : PhotoDeliveryService) {
}
ngOnInit() {
this.photoService.getRandomPhoto().subscribe((data: Photo) => this.photo = {...data})
}
}
and service :
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Photo } from './photo-holder/photo'
#Injectable({
providedIn: 'root'
})
export class PhotoDeliveryService {
value : Number
url : string
constructor(private http: HttpClient) {
this.url = "https://jsonplaceholder.typicode.com/photos/";
this.value = Math.floor(Math.random() * 10) + 1;
}
getRandomPhoto() {
return this.http.get<Photo>(this.getUrl())
}
getUrl(){
return this.url + this.value;
}
}
I suspect that could be made by binding property before query results was returned.
How can I rid off this problem, can I wait for this query, or this is different kind of problem ?
You are getting the error because before your service could resolve, the template bindings are resolved and at that time photo object is undefined.
first thing, you can initialize the photo object but then you might have to detect the changes using ChangeDetectorRef to reflect the value returned by the service.
photo:Photo = {
title:'',
url:''
};
constructor( private photoService : PhotoserviceService, private cdr:ChangeDetectorRef) {
}
ngOnInit() {
this.photoService.getRandomPhoto().subscribe((data: Photo) => {
this.photo = data;
this.cdr.detectChanges();
});
}

Need help accessing variable from Service in Angular 2

Passing the URL id from the last page a user was on to a service that I can reference in a dialog.
issuer.service.ts
import { Injectable, EventEmitter } from '#angular/core';
import { Observable, of } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class IssuerService {
private urlidSource = new BehaviorSubject<string>('');
currentUrlid = this.urlidSource.asObservable();
public onChange: EventEmitter<string> = new EventEmitter<string>();
constructor () {
}
changeUrlid(urlid: string) {
this.currentUrlid = of(urlid);
this.onChange.emit(urlid);
}
getUrlid(currentUrlid: string) {
return this.currentUrlid;
}
}
Page that has the URL id I want (dashboard.component.ts)
import { IssuerService } from './../../issuer.service';
import { ActivatedRoute } from '#angular/router';
import { Router } from '#angular/router';
urlid: string;
constructor(
private route: ActivatedRoute,
private router: Router,
private issuerService: IssuerService,
public dialog: MatDialog
) {}
newUrlid() {
this.issuerService.changeUrlid(this.route.snapshot.paramMap.get('id'));
console.log(this.urlid);
}
ngOnInit() {
// Get URL ID
this.issuerService.onChange.subscribe(urlid => this.urlid = urlid);
this.newUrlid();
}
Component I want to read the value in:
import { ActivatedRoute } from '#angular/router';
import { Router } from '#angular/router';
import { IssuerService } from './../../issuer.service';
urlid: string;
constructor(
private route: ActivatedRoute,
private router: Router,
private issuerService: IssuerService,
public dialog: MatDialog
) {}
ngOnInit() {
this.issuerService.onChange.subscribe(urlid => {
this.urlid = urlid;
console.log(this.urlid);
});
}
So currently when I visit my dashboard page it will display the value of 2 which is correct. My goal is that when a user visits any page I can read this value of 2. How can I access this value? The above code works and my Header displays 2 but only when on the dashboard page. I need it to display 2 no matter what page the user is on.
you can see this example, and It's modified list:
use queryPamas to get query string, not params (DashboardComponent)
use ReplaySubject(1) to return the last urlId; it's don't have a default value, just return prev one value (IssuerService)
get observable from getUrlid and subscribe it in components that want to show url id
export class IssuerService {
private urlidSource = new ReplaySubject<string>(1);
constructor() {
}
changeUrlid(urlid: string) {
this.urlidSource.next(urlid);
}
getUrlid() {
return this.urlidSource;
}
}
export class DashboardComponent implements OnInit {
urlid: string;
constructor(
// private route: ActivatedRoute,
private router: Router,
private issuerService: IssuerService,
// public dialog: MatDialog
) { }
newUrlid() {
// Get URL ID
this.route.queryParams.subscribe((queryParam) => {
const id = queryParam['id'];
if (!id) {
return;
}
this.issuerService.changeUrlid(id);
});
}
ngOnInit() {
this.newUrlid();
this.issuerService.getUrlid().subscribe(urlid => {
this.urlid = urlid;
});
}
}
export class HelloComponent implements OnInit {
urlid;
constructor(
private issuerService: IssuerService
) { }
ngOnInit() {
this.issuerService.getUrlid().subscribe(urlid => {
this.urlid = urlid;
});
}
}
You do not need a parameter for your get Method since you already have the value inside the service,
getUrlid() {
return this.currentUrlid;
}
and you can use retrieve the value in the 2nd component as follows,
this.issuerService.currentUrlid.subscribe((value: string) => {
this.urlid = value;
}

Angular 2 shared service to pass data to component-to-component

I am trying to pass the string value of this.title from my LandingPage.component to my ResultPage.component.
I retrieve the list.show value, and send it to my TitleService in like so in my:
landingpage.component.html
<ol>
<li (click)="selectShow(list.show)" [routerLink]="['/details', list.id]" *ngFor="let list of shows">{{list.show}}
</li>
</ol>
landingpage.component.ts
import { TitleService } from '../../services/title.service';
constructor(private TitleService: TitleService) {}
selectShow(show) {
this.TitleService.fetchTitle(show)
}
The above sends the list.show value to my:
title.service.ts
// this gives us the name of the clicked show, which we send to TitleResolver
#Injectable()
export class TitleService {
fetchTitle(title) {
console.log("title is " + title); // this outputs correctly
return title;
}
}
And here is how I manage the routing in my:
app-routing.module.ts
import { TitleService } from './services/title.service';
const routes: Routes = [
{ path: '', component: LandingPage },
{
path: 'details/:id', component: ResultPage
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [TitleService]
})
My question
Once I receive the title.show value in my service component, I'm unsure how to then send it to my receiving component (resultpage.component)
How can I send my title value from my service to my ResultPage.component?
Make the title a public property of the service like this:
// this gives us the name of the clicked show, which we send to TitleResolver
#Injectable()
export class TitleService {
selectedTitle: string;
fetchTitle(title) {
console.log("title is " + title); // this outputs correctly
this.selectedTitle = title;
return title; // No need to return it.
}
}
Then any other component can inject this service and access this.titleService.selectedTitle
In title.service.ts you can declare a variable called title and have setter and getter:
title: string ="";
// replace fetchTitle with setTitle
// remember to change it in the component too
setTitle(title) {
this.title = title;
}
getTitle() {
return this.title;
}
Then, when ResultPage.component is initialized, call getTitle() from TitleService and set the result to a variable declared in the component.
Here's an example of sharing data via shared services.
Separation of concerns... Your landing page is used to select the list item and navigate to the result page. Let it do just that and only that. Let the ResultPage.component do the rest. Note: Other answers recommend storing the value of the last title in the TitleService. It's not a good idea to store state in a service. Then TitleService cannot be used as a generic way to get any title separate from your current navigation, without side effects.
Remove (click) event. Add 'show' as a QueryParam.
landingpage.component.html
<li [routerLink]="['/details', list.id]"
[queryParams]="{show: list.show}"
*ngFor="let list of shows">
{{list.show}}
</li>
Subscribe to router params and queryparams to get the id and show.
resultpage.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { TitleService } from '../../services/title.service';
#Component({
...
})
export class ResultPageComponent implements OnInit, OnDestroy {
itemId: string;
show: string;
subParams: any; // infinite Observable to be unsubscribed
subQueryParams: any; // infinite Observable to be unsubscribed
constructor(
...
private TitleService: TitleService,
protected route: ActivatedRoute,
protected router: Router,
...
) {}
ngOnInit() {
this.subParams = this.route.params.subscribe(this.onParams);
this.subQueryParams = this.route.queryParams(this.onQueryParams);
}
ngOnDestroy() {
// Delete active subscribes on destroy
this.subParams.unsubscribe();
this.subQueryParams.unsubscribe();
}
onParams = (params: any) => {
this.itemId = params['id'];
}
onQueryParams = (data: any) => {
this.show = data.show;
if(this.show) {
this.TitleService.fetchTitle(this.show)
}
}

How to write console.log wrapper for Angular2 in Typescript?

Is there a way to write a global selfmade mylogger function that I could use in Angular2 typescript project for my services or components instead of console.log function ?
My desired result would be something like this:
mylogger.ts
function mylogger(msg){
console.log(msg);
};
user.service.ts
import 'commons/mylogger';
export class UserService{
loadUserData(){
mylogger('About to get something');
return 'something';
};
};
You could write this as a service and then use dependency injection to make the class available to your components.
import {Injectable, provide} from 'angular2/core';
// do whatever you want for logging here, add methods for log levels etc.
#Injectable()
export class MyLogger {
public log(logMsg:string) {
console.log(logMsg);
}
}
export var LOGGING_PROVIDERS:Provider[] = [
provide(MyLogger, {useClass: MyLogger}),
];
You'll want to place this in the top level injector of your application by adding it to the providers array of bootstrap.
import {LOGGING_PROVIDERS} from './mylogger';
bootstrap(App, [LOGGING_PROVIDERS])
.catch(err => console.error(err));
A super simple example here: http://plnkr.co/edit/7qnBU2HFAGgGxkULuZCz?p=preview
The example given by the accepted answer will print logs from the logger class, MyLogger, instead of from the class that is actually logging.
I have modified the provided example to get logs to be printed from the exact line that calls MyLogger.log(), for example:
get debug() {
return console.debug.bind(console);
}
get log() {
return console.log.bind(console);
}
I found how to do it here: https://github.com/angular/angular/issues/5458
Plunker: http://plnkr.co/edit/0ldN08?p=preview
As per the docs in developers.mozilla,
The bind() method creates a new function that, when called, has its
this keyword set to the provided value, with a given sequence of
arguments preceding any provided when the new function is called.
More information about bind here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
If you want to use 'console.log' function just in your component you can do this:
import { Component, OnInit } from '#angular/core';
var output = console.log;
#Component({
selector: 'app-component',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
constructor() { }
ngOnInit() { }
printFunction(term: string): void {
output('foo');
}
}
How about using console on your main service, So we can customize and apply console.log conditionally:
myComponent.ts
export class myComponent implements OnInit {
constructor(
private config: GlobalService
) {}
ngOnInit() {
this.config.log('func name',{a:'aval'},'three');
}
}
global.service.ts
#Injectable()
export class GlobalService {
constructor() { }
this.prod = true;
public log(one: any, two?: any, three?: any, four?: any) {
if (!this.prod) {
console.log('%c'+one, 'background:red;color:#fff', two, three, four);
}
}
}
(Note: first parameter should be string in this example);
For toggling console.log ON\OFF:
logger.service.ts:
import { Injectable } from '#angular/core';
#Injectable()
export class LoggerService {
private oldConsoleLog = null;
enableLogger(){
if (this.oldConsoleLog == null) { return; }
window['console']['log'] = this.oldConsoleLog;
}
disableLogger() {
this.oldConsoleLog = console.log;
window['console']['log'] = function () { };
};
}
app.component.ts:
#Component({
selector: 'my-app',
template: `your templ;ate`
})
export class AppComponent {
constructor(private loggerService: LoggerService) {
var IS_PRODUCTION = true;
if ( IS_PRODUCTION ) {
console.log("LOGGER IS DISABBLED!!!");
loggerService.disableLogger();
}
}
}
I created a logger based on the provided information here
Its very basic (hacky :-) ) at the moment, but it keeps the line number
#Injectable()
export class LoggerProvider {
constructor() {
//inject what ever you want here
}
public getLogger(name: string) {
return {
get log() {
//Transform the arguments
//Color output as an example
let msg = '%c[' + name + ']';
for (let i = 0; i < arguments.length; i++) {
msg += arguments[i]
}
return console.log.bind(console, msg, 'color:blue');
}
}
}
}
Hope this helps
type safer(ish) version with angular 4, typescript 2.3
logger.service.ts
import { InjectionToken } from '#angular/core';
export type LoggerService = Pick<typeof console,
'debug' | 'error' | 'info' | 'log' | 'trace' | 'warn'>;
export const LOGGER_SERVICE = new InjectionToken('LOGGER_SERVICE');
export const ConsoleLoggerServiceProvider = { provide: LOGGER_SERVICE, useValue: console };
my.module.ts
// ...
#NgModule({
providers: [
ConsoleLoggerServiceProvider,
//...
],
// ...
my.service.ts
// ...
#Injectable()
export class MyService {
constructor(#Inject(LOGGER_SERVICE) log: LoggerService) {
//...
There is now an angular2 logger component on NPM which supports log levels.
https://www.npmjs.com/package/angular2-logger

Categories