Angular error "Cannot read property 'firstName' of undefined" - html

Hi i am working on a search service to look for usernames in a elasticsearch database but i got the following error when i want to display the f.e. firstName of the user: Cannot read property 'firstName' of undefined.
I am working with Angular and Elasticsearch
service:
export class SearchService {
getElastic = 'http://34.62.28.281:9200/users/_doc/_search?q=';
private handleError: HandleError;
constructor(
private http: HttpClient,
httpErrorHandler: HttpErrorHandler) {
this.handleError = httpErrorHandler.createHandleError('TimelineService');
}
/** GET elasticsearch result */
getElasticResult( text: string ): Observable<User> {
this.http.get<User>(this.getElastic + text).subscribe(res => console.log(res));
return this.http.get<User>(this.getElastic + text, {responseType: 'json'});
}
EDIT:
The new HTML form:
<form [formGroup]="angForm2" *ngIf="user != null" (ngSubmit)="getUser()" class="form-inline my-5 my-lg-0">
<input id="searchText" name="searchText" class="form-control" type="string" placeholder="Search for user" aria-label="Post"
formControlName="searchText" required>
<p>{{user?.firstName}}</p>
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
the new component:
export class SearchServiceComponent implements OnInit {
user: User;
angForm2 = this.fb.group({
searchText: ['', Validators.required]
});
ngOnInit() {
this.getUser();
this.getObject();
}
constructor(private searchservice: SearchService, private fb: FormBuilder) { }
getUser() {
const userName = this.angForm2.value.searchText;
console.log(this.angForm2.value, userName);
this.searchservice.getElasticResult(userName).subscribe(user => (this.user = user));
}
getObject() {
return this.user;
}
}
Output of user and this.user:
User interface:
export interface User {
$oid: string;
firstName: string;
lastName: string;
username: string;
password: string;
follows: User[];
}

I guess getObject() is getting called when you load the form, by the time user is undefined so you are getting that error. why dont you just use user?.firstName since you already have the variable defined ?
<p>{{user?.firstName}}</p>

In your html you need to do something like this
<p>{{usersFirstName}}</p>
In your ts code, once you get the response from the server set this usersFirstName.
usersFirstName : string;
getUser() {
const userName = this.angForm2.value.searchText;
console.log(this.angForm2.value, userName);
this.searchservice.getElasticResult(userName).subscribe(user => (this.user = user)
this.usersFirstName = user.name;
);
}

1.SearchServiceComponent should implement OnInit class and implement its ngOnInit method
2. Call both the methods inside ngOnInit method sequentially
3. Check if this.user is not equal to null or undefined and handle it using ngIf condition
Answered by Sajitharan
Example for OnInit
TS
#Component({selector: 'my-cmp', template: `...`})
class MyComponent implements OnInit {
ngOnInit() {
this.getUser();
this.getObject()
}
getUser() {
const userName = this.angForm2.value.searchText;
console.log(this.angForm2.value, userName);
this.searchservice.getElasticResult(userName).subscribe(user => (this.user = user.hits.hits[0]._source));
}
getObject(){}
}

Related

Update method passes the old value instead of the new value in Angular

I am trying to make an Update method and everything works fine except for a value that is selected from a drop-down-list. When the PUT method is called, the API always receives the old value instead of the newly selected one.
This is the list I am talking about:
edituser.component.html
<mat-form-field>
<mat-label>Choose a role </mat-label>
<mat-select *ngIf="roles" [(ngModel)]="userRoleID" name="role">
<mat-option *ngFor="let role of roles" [value]="role.id" [(ngModel)]="el.userRoleID">{{role.role}}</mat-option>
</mat-select>
</mat-form-field>
edituser.component.ts
export class EdituserComponent implements OnInit {
id: string;
sub: any;
user: User[];
roles: Role[];
userRoleID: string;
constructor(private route: ActivatedRoute, private userService: UserService, private router:Router, private roleService:RoleService) {
this.roleService.getRoles().subscribe((result) => {
this.roles = result
this.useRoles(this.roles);
});
}
ngOnInit(): void {
this.sub = this.route.params.subscribe(params => {
this.id = params['id'];
console.log(this.id);
this.userService.getUserById(this.id).subscribe((result) => this.user = result);
});
}
updateUser(user: User){
this.userService.updateUser(user).subscribe(
(result: User) => {
this.userService.getUsers();
});
this.router.navigateByUrl("");
}
useRoles(roles:any)
{
console.log(roles);
}
}
Why does it keep passing the wrong, outdated value to the API when trying to update?

How to get a directive to react to an EventEmitter in a component

I have a CustomComponent which emits a value (let's just call it "error") if a http request to the back end api returns an error. How can I get a directive (call it Form Directive), applied to this form, to recognize when the "error" value is emitted by CustomComponent?
Code for CustomComponent:
export class CustomComponent extends FormComponent<Custom> {
constructor(
protected fb: FormBuilder,
private httpService: HttpService) {
super(fb);
}
currentVal: string = '';
inputType: string = 'password';
showPasswordTitle: string = 'Show Password';
showPasswordStatus: boolean = false;
form: FormGroup;
#Output() invalidOnError = new EventEmitter<string>();
protected buildForm(): FormGroup {
return this.form = this.fb.group({
fieldA: ['', Validators.required],
fieldB: ['', Validators.required],
fieldC: [''],
fieldD: ['', [Validators.required, Validators.pattern('[0-9]{10}')]]
}
protected doSubmit(): Observable<Custom> {
return this.httpService.callDatabase<Custom>('post', '/api/users/custom', this.value);
};
protected get value(): Registration {
return {
fieldA: this.fieldA.value,
fieldB: this.fieldB.value,
fieldC: this.fieldC.value,
fieldD: this.fieldD.value
};
}
get fieldA() { return this.form.get('fieldA'); }
get fieldB() { return this.form.get('fieldB'); }
get fieldC() { return this.form.get('fieldC'); }
get fieldD() { return this.form.get('fieldD'); }
protected onError() {
if (this.error.length) {//error.length indicates some of the fields in the form are already registered in the database
Object.keys(this.error).forEach(element => {
let formControl = this.form.get(this.error[element])
this.currentVal = formControl.value;
formControl.setValidators(formControl.validator ? [formControl.validator, unique(this.currentVal)] : unique(this.currentVal))
formControl.updateValueAndValidity()
this.invalidOnError.emit('error');
})
}
}
Code for FormComponent:
export abstract class FormComponent<T> implements OnInit {
protected form: FormGroup = null;
submitted = false;
completed = false;
error: string = null;
constructor(protected fb: FormBuilder) {}
ngOnInit() {
this.form = this.buildForm();
}
onSubmit() {
this.submitted = true;
if (this.form.valid) {
this.doSubmit().subscribe(
() => {
this.error = null;
this.onSuccess();
},
err => {
this.error = err
this.onError();
},
() => {
this.submitted = false;
this.completed = true;
}
)
}
}
protected abstract get value(): T;
protected abstract buildForm(): FormGroup;
protected abstract doSubmit(): Observable<T>;
protected onSuccess() {}
protected onError() {}
}
Code for Form Directive (works well when user clicks Submit button, which triggers onSubmit event in CustomComponent):
#Directive({
selector: 'form'
})
export class FormSubmitDirective {
submit$ = fromEvent(this.element, 'submit').pipe(shareReplay(1));
constructor(private host: ElementRef<HTMLFormElement>) {}
get element() {
return this.host.nativeElement;
}
}
I was hoping something like this could be the solution to my question, but this for sure doesn't work.
invalidOnError$ = fromEvent(this.element, 'error').pipe(shareReplay(1));
The idea is to use submit$ or invalidOnError$ from the directive to focus on the first invalid field in the form. Works fine for submit$, but not invalidOnError$. Appreciate some help - still fairly new to Angular.
I got this to work in a round about manner, by using the #Input decorator in another form directive which also imports submit$ from Form Directive.
No changes to code for FormComponent and Form Directive vs. what's shown in the question.
Relevant code from Custom component:
export class CustomComponent extends FormComponent<Custom> {
invalidOnError: string = '';
form: FormGroup;
protected buildForm(): FormGroup {
return this.form = this.fb.group({
fieldA: ['', Validators.required],
fieldB: ['', Validators.required],
fieldC: [''],
fieldD: ['', [Validators.required, Validators.pattern('[0-9]{10}')]]
}
protected doSubmit(): Observable<Custom> {
invalidOnError = '';
return this.httpService.callDatabase<Custom>('post', '/api/users/custom', this.value);
};
protected get value(): Registration {
return {
fieldA: this.fieldA.value,
fieldB: this.fieldB.value,
fieldC: this.fieldC.value,
fieldD: this.fieldD.value
};
}
get fieldA() { return this.form.get('fieldA'); }
get fieldB() { return this.form.get('fieldB'); }
get fieldC() { return this.form.get('fieldC'); }
get fieldD() { return this.form.get('fieldD'); }
protected onError() {
if (this.error.length) {//error.length indicates some of the fields in the form are already registered in the database
invalidOnError = 'invalid'
Object.keys(this.error).forEach(element => {
let formControl = this.form.get(this.error[element])
this.currentVal = formControl.value;
formControl.setValidators(formControl.validator ? [formControl.validator, unique(this.currentVal)] : unique(this.currentVal))
formControl.updateValueAndValidity()
this.invalidOnError.emit('error');
})
}
}
Relevant code from CustomComponentTemplate:
<form class="bg-light border" appFocus="FieldA" [formGroup]="CustomForm"
[invalidOnError]="invalidOnError" (ngSubmit)="onSubmit()">
Relevant code from invalidFormControlDirective (imports submit$ from Form Directive):
#Directive({
selector: 'form[formGroup]'
})
export class FormInvalidControlDirective {
private form: FormGroup;
private submit$: Observable<Event>;
#Input() invalidOnError: string = ''; //this is the #Input variable invalidOnError
constructor(
#Host() private formSubmit: FormDirective,
#Host() private formGroup: FormGroupDirective,
#Self() private el: ElementRef<HTMLFormElement>
) {
this.submit$ = this.formSubmit.submit$;
}
ngOnInit() {
this.form = this.formGroup.form;
this.submit$.pipe(untilDestroyed(this)).subscribe(() => {
if (this.form.invalid) {
const invalidName = this.findInvalidControlsRecursive(this.form)[0];
this.getFormElementByControlName(invalidName).focus();
}
});
}
ngOnChanges(){
of(this.invalidOnError).pipe(filter(val => val == 'invalid')).subscribe(() => {
if (this.form.invalid) {
const invalidName = this.findInvalidControlsRecursive(this.form)[0];
this.getFormElementByControlName(invalidName).focus();
}
});
}
ngOnDestroy() { }
// findInvalidControlsRecursive and getFormElementByControlName defined functions to get invalid controls references
}
That said, I'd be interested in 1) somehow bringing code under onChanges lifecyle into ngOnInit lifecyle in invalidFormControlDirective (couldn't get that to work), and 2) find out if there is some way to emitting an event and processing it with Rxjs fromEventPattern as opposed to passing the #Input variable invalidOnError into invalidFormControlDirective.

Angular 8: Send Form data as a Service to Another Component

How do I sent all the Formgroup data as a Service in Angular to Another Component? I am looking for alternative to ControlValueAccessor. So anytime someone writes something on a form, the receiver will get the value data.
Trying to edit the code below to work.
Address Sender Form:
export class AddressFormComponent implements OnInit {
editAddressForm: FormGroup;
constructor(private formBuilder: FormBuilder) {
this.editAddressForm = this.formBuilder.group({
'streetName' : [null, Validators.required, Validators.maxLength(64)],
'city' : [null, Validators.required, Validators.maxLength(32)],
'state' : [null, Validators.required, Validators.maxLength(16)],
'postalCode' : [null, Validators.required, Validators.maxLength(16)]
});
}
ngOnInit() {
}
// What should I write as Event Code??
}
Service:
export class AddressService {
private messageSource = new Subject();
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(currentMessage) {
this.messageSource.next(currentMessage);
}
}
Receiver:
export class AddressCombinedReceiverComponent implements OnInit {
message: any;
constructor(private readonly addressService: AddressService) {
this.addressService.currentMessage.subscribe(currentMessage => this.message = currentMessage);
}
ngOnInit() {
}
}
Related question: Angular 2 - formControlName inside component
This doesnt look far away from what you need, in your AddressFormComponent try the following. Ensure to clean up the subscription when needed, added below.
public subs = new Subscription();
public editAddressForm: FormGroup;
constructor(addressService: AddressService){}
public ngOnInit(): void {
this.editAddressForm = this.formBuilder.group({
'streetName' : [null, Validators.required, Validators.maxLength(64)],
'city' : [null, Validators.required, Validators.maxLength(32)],
'state' : [null, Validators.required, Validators.maxLength(16)],
'postalCode' : [null, Validators.required, Validators.maxLength(16)]
});
this.subs.add(this.editAddressForm.valueChanges.subscribe(data =>
{
this.addressService.changeMessage(this.editAddressForm);
}));
}
public ngOnDestroy(): void
{
this.subs.unsubscribe();
}
That should in turn push the data to the subject you have an in turn cause the subscribers of that subject to receive the data. To confirm try the following
export class AddressCombinedReceiverComponent implements OnInit {
public message: any;
constructor(private readonly addressService: AddressService) {}
public ngOnInit() {
this.addressService.currentMessage.subscribe(currentMessage => {
this.message = currentMessage;
console.log(this.message); // should be the form coming through
});
}
}
You don't need to respond to an event. FormGroups already have an observable that streams the changes.
Example
// address-form.component.ts
export class AppComponent implements OnDestroy {
editAddressForm: FormGroup;
ngOnDestroy() {
// cleanup
this.addressService.formValues$ = null;
}
constructor(private addressService: AddressService) {
this.editAddressForm = new FormGroup({
streetName: new FormControl(null, { validators: [Validators.required, Validators.maxLength(64)]}),
city: new FormControl(null, { validators: [Validators.required, Validators.maxLength(32)]}),
state: new FormControl(null, { validators: [Validators.required, Validators.maxLength(16)]}),
postalCode: new FormControl(null, { validators: [Validators.required, Validators.maxLength(16)]}),
});
this.addressService.formValues$ = this.editAddressForm.valueChanges;
}
}
// address.service.ts
export class AddressService {
formValues$: Observable<any>;
constructor() { }
}
In regards to the receiver, if the intention is to show the form changes in the template, then I recommend using the async pipe for that, so that you only subscribe when it's defined, like:
// address-combined-receiver.component.ts
export class SimpleComponent implements OnInit {
addressValues$: Observable<any>;
constructor(private addressService: AddressService) {
this.addressValues$ = this.addressService.formValues$;
}
ngOnInit() {
}
}
<!-- address-combined-receiver.component.html -->
<ng-container *ngIf="addressValues$ | async as addressValues">
<pre>{{ addressValues | json }}</pre>
</ng-container>

Trying to access single document from Firestore in Typescript and pass it to HTML

I'm trying to access a single document in my Firestore (the one who has message "b"). I'm able to find the correct document and print it to the console log, but when I try assigning it to a public variable, one which will be accessed in HTML as default values for the table, the console tells me the variable is undefined, and or that it can't be set to doc.data() contents.
I've tried using the where() function, as well as mergemap() but I'm getting errors in my code editor (for mergemap--Property 'mergeMap' does not exist on type 'Observable<{}>'). I've also tried looping through items (which holds all the documents and I AM able to access in my HTML) but I can't figure out a way to get the length of the Observable. I've also tried changing the type of defaultdoc to no avail.
Here's what I've got so far
public defaultdoc: Array<any>;
public items: Observable<any[]>;
constructor(private data: DataService, private fb: FormBuilder, private afs: AngularFirestore) {
}
ngOnInit() {
this.myForm = this.fb.group({
title: ['', Validators.required],
message:['', Validators.required]
})
//this.myForm.valueChanges.subscribe(console.log);
let inputString = "b";
const collection: AngularFirestoreCollection<Item> = this.afs.collection('postreply');
this.items = this.afs.collection('postreply').valueChanges();
this.afs.collection("postreply").get().toPromise().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
//console.log(doc.id, " => ", doc.data());
console.log(doc.data().message)
if(doc.data().message == inputString){
this.defaultdoc.push(doc.data());
console.log(this.defaultdoc)
}
});
});
console.log(this.defaultdoc)
}
Here's the HTML
<td><input type="text" class="form-control" formControlName = message value = {{defaultdoc.message}}/></td>
<td><input type="text" class="form-control" formControlName = title value = {{defaultdoc.title}}/></td>
The default values should appear in the two table rows created in the HTML.
I think your "this" is not pointing out the variable you are using in HTML.
Try using self like this:
public defaultdoc: Array<any>;
public items: Observable<any[]>;
constructor(private data: DataService, private fb: FormBuilder, private afs: AngularFirestore) {
}
ngOnInit() {
this.myForm = this.fb.group({
title: ['', Validators.required],
message:['', Validators.required]
})
//this.myForm.valueChanges.subscribe(console.log);
let inputString = "b";
const collection: AngularFirestoreCollection<Item> = this.afs.collection('postreply');
this.items = this.afs.collection('postreply').valueChanges();
let self = this;
this.afs.collection("postreply").get().toPromise().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
//console.log(doc.id, " => ", doc.data());
console.log(doc.data().message)
if(doc.data().message == inputString){
self.defaultdoc.push(doc.data());
console.log(self.defaultdoc)
}
});
});
console.log(this.defaultdoc)
}
Hope it works!

Property 'filter' does not exist on type 'Object'. When trying to filter response

Im trying to get data from a json file that equal the name of the player in the url. For example: localhost:4200/players/Febiven should only return the information about Febiven. Im using Angular 6
So far I have this code:
player.service.ts
get(ingameName){
return <Observable<Player>> this.http.get(endpoint).map(response =>{
let data = response.filter(item=>{
if (item.ingameName == ingameName) {
return item
}
});
if (data.length == 1){
return data[0]
}
return {}
})
.catch(this.handleError)
}
private handleError(error:any, caught:any): any{
console.log(error, caught)
}
player-info.component.ts
export interface Player {
ingameName: string;
name: string;
intro: string;
image: string;
info: string;
team: string;
dob: string;
earnings: string;
role: string;
game: string;
favourite: string;
IDs: string;
}
export class PlayerInfoComponent implements OnInit {
players: Player[] = null;
private routeSub:any;
private req:any;
ingameName:string;
player : player;
constructor(private route: ActivatedRoute, private plService : PlayerService) { }
ngOnInit() {
this.routeSub = this.route.params.subscribe(params => {
this.ingameName = params['ingameName'];
this.req = this.plService.get(this.ingameName).subscribe(data=>{
this.player = data as player
})
});
Im getting the error 'Property 'filter' does not exist on type 'Object'. And I don't really have an idea how to fix this, I looked at multiple answers, but none seemed to work for me. If someone could help me with fixing this error thatd be great
Thanks
filter only exists on arrays. Your response is an object. You can do this instead:
get(ingameName){
return <Observable<Player>> this.http.get(endpoint).map(response =>{
let data = response.json();
if (data.ingameName == ingameName){
return data;
}
return {};
})
.catch(this.handleError)
}
Try this it will work:
define a parameter inside your class & use it in ngOnInit() function like this:
export class VideoDetailComponent implements OnInit, OnDestroy {
data_new:any;
ngOnInit() {
this.http.get("assets/json/videos.json").subscribe(data =>{
this.data_new = data;
this.data_new.filter(item=>{
console.log(item)
// do your work here
})
})
}
}