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.
Related
Hi i'm building a chat app with angular for a school project i'm using firebase for my backend and i have an issue with my ngfor.
For exemple if i reload the page i will see nothing unless i hover my routerlink on my navbar. However sometime it will work after some time on the page without any action
When i recieve message i need to be on the page to see them ...
When i reload my page in first time my array is empty this may be what makes the ngfor bug
array on reload.
I'm using ngOnInit() to subscribe :
messages: Message[];
messageSubscription: Subscription;
constructor(private messageService: MessageService, private router: Router) {
}
ngOnInit(): void {
this.messageSubscription = this.messageService.messageSubject.subscribe(
(messages: Message[]) => {
console.log(messages)
this.messages = messages;
}
);
this.messageService.getMessage();
this.messageService.emitMessage();
}
ngOnDestroy(): void {
this.messageSubscription.unsubscribe();
}
This is my html template :
<div *ngFor="let message of messages" class="message-box">
<img [src]="message.photoURL" class="profile-picture">
<div class="content-box">
<div class="information">
<p class="username">{{message.displayName}}</p>
<p class="date">{{message.createdAt | date: 'short'}}</p>
</div>
<p class="text">{{message.text}}</p>
</div>
</div>
Here you can find my service with my getMessage() function and emitMessage():
messages:Message[] = [];
messageSubject = new Subject<Message[]>();
constructor() { }
emitMessage(){
this.messageSubject.next(this.messages);
}
saveMessage(newMessage: Message){
firebase.database().ref('/message').push(newMessage);
}
getMessage(){
firebase.database().ref('/message')
.on('value', (data) => {
this.messages = data.val() ? Object.values(data.val()): [];
this.emitMessage();
});
}
And this is the repo of my project: https://github.com/Zuxaw/AngularChatApp
If anyone has a solution I'm interested
Problem is, your firebase library is not Angular specific.
This means you some times need to make sure its code, mostly its event callbacks, run within an Angular zone (google to read about it) to make sure a change detection 'tick' is invoked when data changes.
message.service.ts
import { Injectable, NgZone } from '#angular/core';
// ...
constructor(private zone: NgZone) { }
// ..
getMessage(){
firebase.database().ref('/message')
.on('value', (data) => {
this.zone.run(() => {
this.messages = data.val() ? Object.values(data.val()): [];
this.emitMessage();
});
});
}
I think you might need to use the child_added event instead of value in your getMessage method.
Check if you're receiving data on time in your getMessage method, if not it's most probably, because of the event.
But one thing that I don't understand is why you're calling emitMessage inside getMessage and also calling it inside your component after getMessage, try to evade that.
I'm pretty new to Angular and I'm not quite sure if I'm just doing something wrong with #Input() and ngOnChanges() in my code or if my whole setup isn't correct.
My setup is as follows:
I have some API generated data. My service gets the data and holds logic to do some filtering.
My parent component holds a filtercomponent which has a button "apply filter" (which uses the logic in my service) and a tablecomponent to display the data.
The filtering works fine and I do get the desired filtered data but how do I pass JUST this array to the tableviewcomponent? If I do it via #Input() and run ngOnChanges I get a nested array.
How do I solve this?
Parent TS:
tabellenDaten: any[];
constructor(private filterservice: BdService) {}
ngOnInit() {}
onDisplayTable(filter: BdFilter) {
this.filterservice.getBdTabelle(filter).subscribe(
(daten) => {
console.log('tabellendatenneu', daten);
this.tabellenDaten = daten;
},
(error) => console.log('error: ', error),
() => { }
);
}
Parent HTML which holds a filtercomponent and a tablecomponent:
<div class="v-flex-container-filter">
<app-allfilter-bd
(emitFilter)="onDisplayTable($event)"></app-allfilter-bd>
</div>
<div class="v-flex-container">
<app-tabelle
[tabellenDaten]="tabellenDaten"></app-tabelle>
</div>
When I log it I do get the desired table like: Array(148)[{...}, {...}, {...}, ...].
Now, when using [tabellenDaten]="tabellenDaten" and using #Input()...
Child TS (table logic):
#Input() tabellenDaten: any[];
ngOnChanges(...tabellenDaten: any) {
this.dataSource = new MatTableDataSource<any>(tabellenDaten);
console.log('TABELLENDATEN', tabellenDaten);
}
...I do get the results of the ngOnChanges method (currentValue, firstChange and previousValue) which ALSO holds my data array but how do I get JUST the single array?
Any help is very much appreciated.
The implementation of ngOnChanges is: ngOnChanges(changes: SimpleChanges): void
So your code should be:
ngOnChanges(changes: simpleChanges) {
// if 'tabellenDaten' has changed it will be available as a field on 'changes'.
if (changes.tabellenDaten) {
this.dataSource = new MatTableDataSource<any>(changes.tabellenDaten.currentValue);
}
}
Another approach is to use RxJS with async pipe provided by angular.
Instead of subscribe this.filterservice.getBdTabelle(filter), you can assign the observable :
tabellenDaten$: Observable<any[]>;
constructor(private filterservice: BdService) {}
ngOnInit() {}
onDisplayTable(filter: BdFilter) {
this.tabellenDaten$ = this.filterservice.getBdTabelle(filter);
}
Then in your template use async pipe :
<div class="v-flex-container-filter">
<app-allfilter-bd
(emitFilter)="onDisplayTable($event)"></app-allfilter-bd>
</div>
<div class="v-flex-container">
<app-tabelle
[tabellenDaten]="tabellenDaten$ | async"></app-tabelle>
</div>
And finally you don't need to assign your array to MatTableDataSource but you can pass you array directly to mat-table :
<mat-table [dataSource]="tabellenDaten">
You should always consider to not subscribe your Observable in your component unless you unsubscribe it manually. Otherwise use async and let the pipe subscribe/unsubscribe for you.
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.].
I have created my own directive, that hides content if user is not logged: *onlyUser. When I try to use it in component that utilises ng/animation I sometimes get an error:
Cannot read property 'insertNode' of undefined at TransitionAnimationEngine
Directive looks like this:
export class OnlyUserDirective {
constructor(private _templateRef: TemplateRef<any>,
private _viewContainer: ViewContainerRef,
private _userContextService: UserContextService) {
this._userContextService.isLogged$().subscribe(x => {
if (x === true) {
this._viewContainer.createEmbeddedView(this._templateRef);
} else {
this._viewContainer.clear();
}
});
}
}
And when I try to use it within a component with #Component({ animations: [...] }) I sometimes get the error from this question's beginning. Is this expected behavior, or an angular bug?
It is a known bug -> https://github.com/angular/angular/issues/19712.
I am checking if it is working with Angular 5
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