Angular 2: {{object}} works, {{object.child}} throws error - javascript

Been working with Angular v1 for some time now and since Angular v2 came in Beta, been playing around with that.
Now I've got this piece of code, but can't get it to work, really don't know why. Somehow, when I print {{profileUser | json}} everything works fine (profileUser is an object).
But when I want to print a child of that object (for example {{profileUser.name}} or {{profileUser.name.firstName}}), Angular throws the following error:
EXEPTION: TypeError: undefined is not an object (evaluating 'l_profileUser0.name') in [ {{profileUser.name}} in ProfileComponent#4:11.
It's really confusing to me, should be just one of the simplest things around.. Just started with TypeScript btw..
Here is some code - ProfileService.ts:
import { Injectable } from 'angular2/core';
import { Headers } from 'angular2/http';
import { API_PREFIX } from '../constants/constants';
import { AuthHttp } from 'angular2-jwt/angular2-jwt';
import 'rxjs/add/operator/map';
#Injectable()
export class ProfileService {
API_PREFIX = API_PREFIX;
constructor(private _authHttp:AuthHttp) {
}
getProfileData(username:string):any {
return new Promise((resolve, reject) => {
this._authHttp.get(API_PREFIX + '/users/username/' + username)
.map(res => res.json())
.subscribe(
data => {
resolve(data.data);
},
err => {
reject(err);
}
)
;
});
}
}
And here is my ProfileComponent:
import {Component, OnInit} from 'angular2/core';
import {RouteParams} from 'angular2/router';
import {ProfileService} from '../../services/profile.service';
#Component({
selector: 'profile',
templateUrl: './components/profile/profile.html',
directives: [],
providers: [ProfileService]
})
export class ProfileComponent implements OnInit {
public username:string;
public profileUser:any;
constructor(private _profileService: ProfileService,
private _params: RouteParams) {
this.username = this._params.get('username');
}
ngOnInit() {
this.getProfileData(this.username);
}
getProfileData(username:string):void {
this._profileService.getProfileData(username)
.then(data => {
this.profileUser = data;
console.log(data);
})
;
}
}
Finally, the profile.html template:
<pre> <!-- works! -->
{{profileUser | json}}
</pre>
or..
<pre> <!-- throws the error -->
{{profileUser.name | json}}
</pre>
or..
<pre> <!-- throws the error -->
{{profileUser.name.firstName}}
</pre>
FYI, the profileUser looks like this:
{
"id": "9830ecfa-34ef-4aa4-86d5-cabbb7f007b3",
"name": {
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe"
}
}
Would be great if somebody could help me out, this is really holding me back to get familiar with Angular v2. Thanks!

In fact your profileUser object is loaded from an HTTP request and it can be null at the beginning. The json pipe simply does a JSON.stringify.
It's what your error message said: undefined is not an object (evaluating 'l_profileUser0.name').
You need to be sure that your profileUser object isn't null to be able to get its name attribute and so on. This can be done using an *ngIf directive:
<div *ngIf="profileUser">
{{profileUser.name | json}}
</div>
When the data will be there, the HTML block will be displayed.
As Eric stated the Elvis operator could also help you. Instead of having {{profileUser.name | json}}, you could use {{profileUser?.name | json}}.
Hope it helps you,
Thierry

It happens because when your controller is created, the profileUser is undefined. And, when you use {{profileUser | json}} the filter json knows that you data is undefined and do nothing. When the profileUser is finally defined, the angular updates thw whole thing and then profileUser | json works. But, when you use {{ profileUser.anything | json}} you will get an error because profileUser starts undefined.
You can solve it, setting a empty profile to your variable at beginning of your controller, just like that:
profileUser = { name: {}};
This way, profileUser never will be undefined

Related

Reading an environment variable before it is defined [duplicate]

This question already has answers here:
Angular2: Cannot read property 'name' of undefined
(7 answers)
Closed 3 years ago.
I am using an environment variable to be able to read something from a JSON and display in my HTML. My issue is that my HTML is trying to read the environment variable before it has been defined in the .ts and therefore I get an error.
I am currently defining the variable in ngOnit() but this gives me an error. I am using httpclient to be able to read the JSON (from a server) and obviously what is happening is that the variable is being read in the HTML before httpclient has got the data.
HTML
<p>Player One is: {{ id.playerone }} </p>
.ts
import { HttpClient } from '#angular/common/http';
export class ApComponent implements OnInit {
id: any = [];
constructor(private httpService: HttpClient) { }
ngOnInit() {
this.httpService.get('http://server/info.json').subscribe(
result => {
this.id = result;
},
error => {
console.log('Error Occured', error);
}
);
}
}
JSON
{
"playerone":"ajf806",
"playertwo":"hof934"
}
I get the expected output of Player One is: ajf806 but I also get an error in the console which is:
ERROR TypeError: Cannot read property '0' of undefined.
It does work and I get the output but I don't want to have the error in the console. Is there a way to delay the HTML reading the environment variable until the JSON has been read?
Change your variable like this:
id: any;
also change your template like this:
<p>Player One is: {{ id?.playerone }} </p>
Another version of the above code [a bit better]:
import { HttpClient } from '#angular/common/http';
export class ApComponent implements OnInit {
id$: Observable<any>;
constructor(private httpService: HttpClient) { }
ngOnInit() {
this.id$ = this.httpService.get('http://server/info.json')
.pipe(
catchError((error) => {
//handle your error
console.log(error);
})
)
);
}
}
Now change your template to make use of async pipe like this:
<ng-conatiner *ngIf="(id$ | async) as id">
<p>Player One is: {{ id.playerone }} </p>
</ng-container>
NOTICE - you are not subscribing to the observable in your component. async pipe is taking care of subscription management [i.e. subscribing/unsubscribing.].

Angular 6.0.5 - Cannot access property that's there

I, for the life of me cannot understand why I can't access a property on an angular 6 class. Here is some code:
#Component({
selector: 'admin-badge-component',
templateUrl: './badge.component.html'
})
export class AdminBadgeComponent implements OnInit {
// Badge Object
public badgeObject: IVisitorBadge = null;
// On Init
public ngOnInit() {
this.route.params.subscribe((params) => {
// Get Badge Object From API
this.visitorService.getVisitorBadge(params['aid'],params['vid'])
.subscribe((response: IVisitorBadge) => {
console.log(response);
this.badgeObject = response;
});
});
}
}
the console.log outputs every thing as intended:
{
"id":2,
"visit_id":325,
"visitor_id":45,
"created_at":"2018-09-29 15:00:10",
"updated_at":"2018-09-29 15:00:10",
"visitor": {
...
"firstname": "matthew",
"lastname": "brown",
...
}
}
However, when I goto access and display the visitor firstname in my template using the following code:
<div>
<h3>
{{ badgeObject?.visitor?.firstname }} {{ badgeObject?.visitor?.lastname }}
</h3>
</div>
Nothing displays. If I try to access the properties directly without the ? notation, I get cannot access 'firstname' of undefined. Even if I wrap the template in *ngIf and check for property first. I've also tried initting and setting a loadingBool that gets set to false after I have the API response, and using it in the *ngIf still nothing.
Here is screenshot of full class: https://imgur.com/a/eEfCSL3
public constructor(private _change: ChangeDetectorRef) { }
this.visitorService.getVisitorBadge(params['aid'],params['vid'])
.subscribe((response: IVisitorBadge) => {
this.badgeObject = response;
this._change.markForCheck();
});
});
You have to tell the change detector that the component is dirty when you lazy load data. The first time the template is rendered the value of badgeObject is null, but later it is assigned a value.
Use the ChangeDetectorRef:
https://angular.io/api/core/ChangeDetectorRef
Found the issue. Not mentioned above is the this.visitorService.getVisitorBadge method, which I was accidentally setting the responseType to text in the HttpClient callout. Reset that back to json, now it's working.

Property 'map' does not exist on type 'AngularFireList<{}>'

I'm having a tough time trying to figure this one out. I get the following error when I run Ionic Serve:
Property 'map' does not exist on type 'AngularFireList<{}>'.
I have searched for a fix for some time and can't find anything that has worked, so here I am. Current versions are:
Ionic Framework: 3.9.2
Ionic App Scripts: 3.1.9
Angular Core: 5.0.1
Angular Compiler CLI: 5.0.1
Node: 9.11.1
I have worked out all the other bugs and migrated everything to newer versions (changing Firebase listings to Angular.)
The code that throws the error is the .map() object, here:
afDB.list('/feed').map((feeds) =>
This is my code:
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams, ModalController ,
LoadingController} from 'ionic-angular';
import { AngularFireDatabase, AngularFireList} from 'angularfire2/database';
import 'rxjs/add/operator/map'; // you might need to import this, or not
depends on your setup
#IonicPage()
#Component({
selector: 'page-feed',
templateUrl: 'feed.html'
})
export class FeedPage {
feeds: AngularFireList<any[]>;
feedView: string = "activity";
constructor(public navCtrl: NavController, public navParams: NavParams ,public modalCtrl: ModalController,public loadingCtrl: LoadingController, public afDB: AngularFireDatabase) {
let loadingPopup = this.loadingCtrl.create({
spinner: 'crescent',
content: ''
});
loadingPopup.present();
this.feeds = <AngularFireList<any[]>> afDB.list('/feed').map((feeds) => {
return feeds.map((feeds) => {
feeds.images = afDB.list('/feed/'+feeds.$key+'/images')
loadingPopup.dismiss().catch(() => console.log('ERROR CATCH: LoadingController dismiss'));
return feeds
})
})
}
}
I am learning so if the answer is obvious, please explain it. Thanks in advance.
When you call list method on AngularFireDatabase you get back an AngularFireList. Even though there is List in the name it's not an array or a stream that would have the map method.
This is the definition for this type (you can see this by using go to definition on the AngularFireList in your editor or by browsing the code source):
export interface AngularFireList<T> {
query: DatabaseQuery;
valueChanges(events?: ChildEvent[]): Observable<T[]>;
snapshotChanges(events?: ChildEvent[]): Observable<SnapshotAction[]>;
stateChanges(events?: ChildEvent[]): Observable<SnapshotAction>;
auditTrail(events?: ChildEvent[]): Observable<SnapshotAction[]>;
update(item: FirebaseOperation, data: T): Promise<void>;
set(item: FirebaseOperation, data: T): Promise<void>;
push(data: T): ThenableReference;
remove(item?: FirebaseOperation): Promise<void>;
}
In order to get the stream you need to use one of the methods returning an Observable, and assuming you want the values that would be valueChanges.
So your code should be something like:
afDB.list('/feed').valueChanges.map(...)
And the result would be a stream, meaning Observable<any>. This means that in the template you would need to use the Async pipe.
We seem to have fixed it by doing the following - and by we, I mean all credit goes to my friend R. Jackson.
First, I added this to the imports
import {Observable} from 'rxjs/Observable'
Next, under the exports section, I the line:
feeds: AngularFireList;
to
feeds: Observable;
then, putting those changes into play
this.feeds = afDB.list('/feed').valueChanges().map((feeds:any)
This no long throws any errors and serves without any hiccups.

Declaring and initializing a parameter from a Promise result from onNgInit (Angular2) [duplicate]

Edit: It looks like my main problem now is that I can't seem to display async data from an object. I have a promise containing the data object, and when I use
{{ data | async }}
it will display
[object Object]
The issue is, I want to be able to display all the different attributes; i.e, Name, Symbol, etc. In Angular 1, I would just use
{{ data.Name | async }}
but that doesn't work here, since the async pipe tries to resolve the data.Name promise, which doesn't exist. I want to resolve the data promise and then display the Name key from it. At the moment, I'm working on creating my own pipe to display a key from an async object, but I'm wondering if there's a built-in Angular 2 pipe or function to handle this!
I've created a StockService class that returns a Promise containing an object to my StockInfo class, which contains the HTML to be displayed. I want to display the name of this object in my HTML, but I can't seem to get it to display.
In my StockInfo constructor:
this.stock.getStockData(this.ticker, http).then(function(val) {
this.data = val;
this.name = new Promise<string>(function(resolve) {
resolve(this.data.Name);
});
});
where this.stock is the StockService object.
In my HTML:
<h2>{{name | async}}</h2>
I've tried a number of different arrangements before settling on this one. I want the StockService class to handle the data fetching and the StockInfo class to handle the display. In Angular 1, I would create a factory for fetching data and handle the data processing in the controller, but I'm not quite sure how to go about this in Angular 2.
Is there a way to get it to display, or are there better ways to design my code that I should look into? Thanks!
You do not need any special pipe. Angular 2 suppport optional field. You just need to add ? in your object
{{ (data | async)?.name }}
or
{{(name | async)?}}
There's nothing wrong with the accepted answer above. But it becomes a hassle to append | async? when we need to display many properties of the object. The more convenient solution is as follows:
<div *ngIf="data | async as localData">
<div> {{ localData.name }} </div>
<div> {{ localData.property1 }} </div>
<div> {{ localData.property2 }} </div>
</div>
You can also use pluck from rxjs/observable:
{{ user.pluck("name") | async }}
Pluck
Returns an Observable containing the value of a specified nested property from all elements in the Observable sequence. If a property can't be resolved, it will return undefined for that value.
If you work with Observable you can display data like this way:
<div *ngIf="data | async; let _data">
<h3>{{_data.name}}</h3>
</div>
or
<h3>{{(data | async).name}}</h3>
I think you are making this too complex, and just need to do something like this.
this.name =
this.stock.getStockData(this.ticker, http)
.then( val => val.Name )
and
<h2>{{name.Name | async}}</h2>
So I ended up writing my own asynchronous key pipe. Huge thanks to Simon for helping guide me here.
import {Pipe} from 'angular2/core';
#Pipe({
name: 'key',
pure: false
})
export class KeyPipe {
private fetchedPromise: Promise<Object>;
private result: string;
transform(value: Promise<Object>, args: string[]) {
if(!this.fetchedPromise) {
this.fetchedPromise = value
.then((obj) => this.result = obj[args[0]] );
}
return this.result;
}
}
Usage:
<h2>{{ data | key: 'Name' }}</h2>
Someone please comment if Angular has its own functions for resolving a key from an asynchronous object.
The OP asked for promises but in case people are using Observables, adapting #user2884505's answer, since pluck isn't directly available on observables as a method in recent versions of RxJS, you may have something like this :
import { Pipe, PipeTransform } from '#angular/core';
import { Observable } from 'rxjs';
import { pluck } from 'rxjs/operators';
#Pipe({
name: 'asyncKey',
pure: false
})
export class AsyncKeyPipe implements PipeTransform {
private observable: Observable<Object>;
private result: Object;
transform(value: any, ...args: any[]): any {
if (!this.observable) {
this.observable = value.pipe(pluck(...args));
this.observable.subscribe(r => this.result = r);
}
return this.result;
}
}
And then, you can use it, even for nested keys :
{{ user$ | asyncKey: 'address' : 'street' }}

Global variables Ionic 2

I'm having some difficulties with Ionic 2 and setting up global variables. The structure of my app is as follows:
Main app
|
|--- Page1 (Info)
|--- Page2 (Map)
|--- Page3 (List)
|
|--- ItemTabsPage
|
|---tab1
|---tab2
|---tab3
My intention is to show a list in Page3, and once one item is selected, to show additional information in tabs.
I send the information from Page 3 to the page with the tabs using:
itemTapped(event, item) {
this.nav.push(ItemTabsPage, {
item: item
});
}
The problem is that I can't do the same to send the info to the child tabs. I would like to show different information depending on which item is selected. I have tried defining an injectable globalVars.js to store the value in a variable:
import {Injectable} from 'angular2/core';
#Injectable()
export class GlobalVars {
constructor(myGlobalVar) {
this.myGlobalVar = "";
}
setMyGlobalVar(value) {
this.myGlobalVar = value;
}
getMyGlobalVar() {
return this.myGlobalVar;
}
}
and then updating the code of itemTapped in the list as follows:
itemTapped(event, item) {
this.nav.push(ItemTabsPage, {
item: item
});
this.globalVars.setMyGlobalVar(item);
}
However, I always get the same error:
Uncaught EXCEPTION: Error during evaluation of "click"
ORIGINAL EXCEPTION: TypeError: Cannot read property 'setMyGlobalVar' of undefined
The code for page3 is:
import {Page, NavController, NavParams} from 'ionic-angular';
import {ItemService} from '../services/ItemService';
import {ItemTabsPage} from '../item/item-tabs/item-tabs';
import {GlobalVars, setMyGlobalVar} from '../../providers/globalVars';
import {Http} from 'angular2/http';
import 'rxjs/add/operator/map';
#Page({
templateUrl: 'build/pages/item-list/item-list.html',
providers: [ItemService]
})
export class ItemListPage {
static get parameters() {
return [[NavController], [NavParams], [Http]];
}
constructor(nav, navParams, http, globalVars) {
this.nav = nav;
// If we navigated to this page, we will have an item available as a nav param
this.selectedItem = navParams.get('item');
this.http = http;
//this.items = null;
this.globalVars = globalVars;
this.http.get('https://website-serving-the-info.com/items.json').map(res => res.json()).subscribe(data => {
this.items = data.items;
},
err => {
console.log("Oops!");
});
}
itemTapped(event, item) {
this.nav.push(ItemTabsPage, {
item: item
});
this.globalVars.setMyGlobalVar(item);
}
}
Anyone have any suggestion? My Ionic installation is:
Cordova CLI: 6.1.1
Gulp version: CLI version 3.9.1
Gulp local: Local version 3.9.1
Ionic Framework Version: 2.0.0-beta.4
Ionic CLI Version: 2.0.0-beta.25
Ionic App Lib Version: 2.0.0-beta.15
OS: Distributor ID: LinuxMint Description: Linux Mint 17.3 Rosa
Node Version: v5.11.0
The easiest way I use is to create a file app/global.ts
export var global = {
myvar : 'myvar 01',
myfunction : function(msg) {
alert(msg);
}
};
Then import and use freely in other classes:
import {global} from "../../global";
constructor() {
global.myfunction('test');
}
and if you want to use this global to component HTML page as below
export class HomePage {
Global: any = Global;
now it is available in HTML as below
<div [style.display]="Global.splash ? 'flex': 'none'" >
You're on the right track. And some of the other answers will work, but the Ionic team is recommending you not use globals via a globals file. Instead, they recommend the use of Providers (as you're attempting to do).
You're provider is missing the actual variable declaration.
#Injectable()
export class GlobalVars {
myGlobalVar: string = '' // this is the line you're missing
constructor(myGlobalVar) {
this.myGlobalVar = "";
}
}
You should also note that you are not exporting the function setMyGlobalVar(). You are exporting the class GlobalVars which contains the function setMyGlobalVar().
I believe if you make those changes it should work.
edit
I'd also be careful of this line this.globalVars = globalVars; in your Page3. This will cause a rewrite of your globalVars each time Page3 is created.
I have exactly the same scenario, and would like to share my approach.
my understanding is that, in ionic2, the injection is implemented as instance. which means each time you enter a page, a new instance of the injection is created.
so direct access to a static value does not fit here; you have to somehow bridge the gap.
my approach goes as this:
you still defined a static value in your service provider, yet you define instance "getter", and "setter" for that value.
in your page implementation, you inject the service as a parameter of the constructor.
in the constructor, you have to "new" an instance of the service; and call the "getter", and "setter". see my code snippets below:
export class TransSender {
static _count:number = 0;
static _pushed:number = 0;
...
public static setter(count:number, pushed:number,...) {
TransSender._count = count;
TransSender._pushed = pushed;
}
public get count(){
return TransSender._count;
}
public get pushed(){
return TransSender._pushed;
}
...
}
I actually provide a static collective setter for the service to get value from backend in a static way.
my page implementation runs likes this
import {TransSender} ...;
#Component({
templateUrl: 'build/pages/basics/basics.html',
providers: [TransSender]
})
export class Page {
...
constructor(tSender: TransSender,...) {
...
tSender = new TransSender();
TransSender.setter(1,2,3,4,5,6,7,8);
console.log(tSender.count);
}
}
in your display (html), your will refer to tSender rather than TransSender
this might look a bit stupid. yet I can not find any other solution.
with the release of ionic2 Beta9, bootstrap was re-introduced into the frame. so I am exploring new possibilities
cheers
In your class ItemListPage, try this static parameters method before your constructor:
static get parameters() {
return [[NavController], [NavParams], [Http], [GlobalVars]];
}
I am thinking that you are setting your globalVars variable in the constructor to 'undefined' and therefore you cannot call a function on something that is undefined.
You seem to inject the GlobalVars provider incorrectly in ItemLisyPage.

Categories