spstrademark
spstrademark

Reputation: 475

Angular observable value undefined after value changes

TypeError: Cannot read property 'ip' of null

provider.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { DeviceInterface } from '../interfaces/Device';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class DeviceService {

  private _devices = new BehaviorSubject<DeviceInterface[]>([]);
  private baseUrl = 'api/monitoring';
  private dataStore: { devices: DeviceInterface[] } = { devices: [] };
  readonly devices = this._devices.asObservable();

  constructor(private http: HttpClient) { }

  loadAll() {
    this.http.get<DeviceInterface[]>(`${this.baseUrl}/devices`).subscribe(
      data => {
        this.dataStore.devices = data;
        this._devices.next((<any>Object).assign({}, this.dataStore).devices);
      },
      error => console.log('Could not load todos.')
    );
  }

  create(device: DeviceInterface) {
    this.http
      .post<DeviceInterface>(`${this.baseUrl}/add`, device)
      .subscribe(
        data => {
          this.dataStore.devices.push(data);
          this._devices.next((<any>Object).assign({}, this.dataStore).devices);
        },
        error => console.log('Could not create todo.')
      );
  }


}

component.ts

import { Component, OnInit } from '@angular/core';
import { DialogService, Dialog } from '../providers/dialog.provider';
import { fadeInOut } from '../app.component.animation';
import { AppComponent } from '../app.component'
import {
  LanguageService,
} from '../providers/language.provider';

import { DeviceService } from '../providers/device.provider';
import { DeviceInterface } from '../interfaces/Device';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Component({

  selector: 'app-monitoring',
  templateUrl: './monitoring.component.html',
  styleUrls: ['./monitoring.component.scss'],
  animations: [fadeInOut],
})

export class AppMonitoring implements OnInit {

  dialog: Dialog;
  ShowDevices: boolean;
  devices: Observable<DeviceInterface[]>;

  constructor(
    public language_service: LanguageService,
    public  device_service: DeviceService,
    private dialog_service: DialogService,
    app: AppComponent
  ) {

    app.done$.pipe().subscribe(result => {
      this.ShowDevices = true;
    });

  }

  ngOnInit() {
    this.devices = this.device_service.devices;
    this.device_service.loadAll();
    this.ShowDevices = false;
    this.dialog_service.DialogState.subscribe(dialog => this.dialog = dialog);
  }

  AddDevice() {
    this.dialog = Dialog.DIALOG_ADD;
    this.dialog_service.ChangeDialog(this.dialog);
  }

  Scan() {
    this.dialog = Dialog.DIALOG_SCAN;
    this.dialog_service.ChangeDialog(this.dialog);
  }

}

The html template

  <div [@fadeInOut] *ngIf="ShowDevices">

    <div class="box-header" style="margin-bottom:15px;">
      <div class="div-right" style="line-height: 35px;">

        <a href="#" onclick="return false;" (click)="AddDevice()" class="pull-right"><i matTooltipPosition="below" class="fa fa-plus"></i> </a>
        <a href="#" onclick="return false;" (click)="Scan()" class="pull-right"><i matTooltipPosition="below" class="fa fa-search"></i> </a>
      </div>
    </div>

    <app-device class="flexItemSensor" *ngFor="let device of devices | async; index as i;" [ip]="device?.ip" [name]="device?.name"> </app-device>
  </div>

When i add you using

this.device_service.create(post);

The new item has been added without the input values receiving the following error and the application freezes.

The create function itself is called in a 3rd component with the following code

  AddDevice() {

    let post: DeviceInterface = {
      ip: this.AddDeviceForm.value.ip,
      name: this.AddDeviceForm.value.name,
    }
    this.device_service.create(post);

  }

Cannot read property 'ip' of null

I know the item is being stored in the database successfully, when i restart the program i can see the new item with his values, what is the proper way an item to be added with his value shown ?

Best regards!

Edit: I have tried adding the following code in ngOnInit() and on devices being added the new values of devices are indeed null

 ngOnInit() {

     this.device_service.devices.subscribe(devices => {
      console.log(devices);
    } ); 
    }

Edit 2 : Issue is resolved, turn out that in the back end controller should return the following :

        return Ok(device);

Upvotes: 1

Views: 1486

Answers (2)

Barremian
Barremian

Reputation: 31115

The BehaviorSubject would immediately emit the current value it holds on subscription. By the time the async triggers the subscription the value of the observable is still the default (an empty array []). So there is no ip property.

Option 1

Use safe navigation operator ?. to check if the value is defined before trying to access it's properties

<app-device *ngFor="let device of devices | async; index as i;"  [ip]="device?.ip" [name]="device?.name"> </app-device>

Option 2

Try to check beforehand if the array is non-empty using *ngIf directive.

<ng-container *ngIf="(devices | async) as devicesData">
  <ng-container *ngIf="devicesData.length > 0">
    <app-device *ngFor="let device of devicesData; index as i;"  [ip]="device.ip" [name]="device.name"> </app-device>
  <ng-container>
<ng-container>

Upvotes: 2

Aakash Garg
Aakash Garg

Reputation: 10979

Try this

 <app-device *ngFor="let device of devices | async; index as i;"  [ip]="device?.ip" [name]="device?.name"> </app-device>

Upvotes: 2

Related Questions