Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 component DOM binds to the property in component before it has bean resolved in OnInit method

I am learning Angular 2 and trying to follow their tutorial. Here is the code of the service that returns "Promise" of a mock object Folder.

import {Injectable, OnInit} from "@angular/core";
import {FOLDER} from "./mock-folder";
import {Folder} from "./folder";
@Injectable()
export class FolderService {
  getFolder():Promise<Folder>{
    return Promise.resolve(FOLDER);
  }
}

It is declared in providers of my FolderModule

import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {FolderComponent} from "./folder.component";
import {MaterialModule} from "@angular/material";
import {FolderService} from "./folder.service";
@NgModule({
  imports:[CommonModule, MaterialModule.forRoot()],
  exports:[FolderComponent],
  declarations:[FolderComponent],
  providers:[FolderService]
})
export class FolderModule{

}

Folder component should import FolderService and use it to obtain the Folder object.

import {Component, OnInit} from "@angular/core";
import {Folder} from "./folder";
import {FolderService} from "./folder.service";
@Component({
  selector: 'folder',
  moduleId: module.id,
  templateUrl: "./folder.component.html"
})
export class FolderComponent implements OnInit {
  folder:Folder;

  constructor(private folderService:FolderService) {
  }
  ngOnInit():void {
    this.getFolder();
  }
  getFolder() {
    this.folderService.getFolder().then((folder) => this.folder = folder);
  }
}

And yes, i do import my FolderModule in the root module

@NgModule({
  imports: [BrowserModule, CommonModule, MaterialModule.forRoot(), FolderModule, AppRoutingModule],
  providers:[],
  declarations: [AppComponent, LifeMapComponent, MyPageNotFoundComponent],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Here is the folder component template

<md-grid-list cols="3" [style.background] ="'lightblue'" gutterSize="5px">
  <md-grid-tile *ngFor="let card of folder.cards">{{card.title}}</md-grid-tile>
</md-grid-list>

And here is the error i get in the console

EXCEPTION: Uncaught (in promise): Error: Error in http://localhost:3000/app/folders/folder.component.html:1:16 caused by: Cannot read property 'cards' of undefined TypeError: Cannot read property 'cards' of undefined

export class Folder {
  public id:number;
  public title:string;
  public cards:Card[];
}

export class Card{
  id :number;
  title:string;
}
like image 491
Voland Mortes Avatar asked Jan 13 '17 18:01

Voland Mortes


2 Answers

Voland,

This can be solved by using the "Elvis" operator on the collection being iterated over.

<md-grid-tile *ngFor="let card of folder.cards">{{card.title}}</md-grid-tile>

Should instead be:

<md-grid-tile *ngFor="let card of folder?.cards">{{card?.title}}</md-grid-tile>

Note the "?" after folder -- this will coerce the whole path to 'null', so it won't iterate. The issue is with the accessor on a null object.

You could also declare folder to an empty array [] to prevent this.

EDIT: To any onlookers, note that the Elvis operator is not available in your Typescript code, as it's not supported by the language. I believe that Angular 2 supports it though, so it is available in your views (really useful for AJAX requests, where your data has not arrived at the point of component instantiation!)

like image 169
chrispy Avatar answered Nov 01 '22 23:11

chrispy


Use *ngIf directive:

<md-grid-list *ngIf="folder" cols="3" [style.background] ="'lightblue'" gutterSize="5px">
  <md-grid-tile *ngFor="let card of folder.cards">{{card.title}}</md-grid-tile>
</md-grid-list>

Angular tries to render the html before the promise is resolved, therefore folder is undefined and the exception is thrown.
With *ngIf="folder" you tell Angular that it should ignore child elements if the expression is falsy.

<md-grid-tile *ngFor="let card of folder.cards">{{card.title}}</md-grid-tile> will be added to the DOM if folder is not undefined.

like image 1
lenny Avatar answered Nov 02 '22 00:11

lenny