1
votes

I'm working on an angular firebase app and I don't know how to solve a problem. My @Input is always undefined on my ngOnInit() but I have to init something with this Input value.

ERROR TypeError: Cannot read property 'length' of undefined

I tried to add *ngIf as I saw in similar posts or to initialize my room but nothing is working. Do I have to use a timeout or is there a better way to do that ?

Here are the details:

players component:

@Input() room: Room;
    players: Player[];
    playersSubscription: Subscription;

    constructor(private playerService: PlayerService, private router: Router) { }

    ngOnInit() {
        this.playersSubscription = this.playerService.playersSubject.subscribe(
            (players: Player[]) => {
                this.players = players;
            }
        );

        this.playerService.getPlayersRoom(this.room.players);

        this.playerService.emitPlayers();

    }

html:

<div *ngIf="room">
    <ul>
        <li *ngFor="let p of room.players">{{p}}</li>
    </ul>
</div>
usernames:
<div *ngIf="players">
    <ul>
        <li *ngFor="let player of players">
            {{player.username}} // This will not works
        </li>
    </ul>
</div>

service:

    getPlayersRoom(ids: string[]) {

        while (ids.length > 0) {
            var id = ids.splice(0, 1)[0];
            firebase.database().ref('/users/').child(id).once('value', function (snapshot) {
                this.players.push(snapshot.val());
            });
        }
        this.emitPlayers();
}

room:

room: Room;

constructor(private router: Router,
    private route: ActivatedRoute,
    private roomService: RoomService
) { }

ngOnInit(): void {
    this.room = new Room();
    const id = this.route.snapshot.params['id'];
    this.roomService.getRoom(+id).then(
        (room: Room) => {
            this.room = room;
        }
    );
}

html:

<div *ngIf="room">
        Room n°{{room.id}} + password {{room.password}}
    <h1>PLAYERS room</h1>
    <div *ngFor="let p of room.players">
        {{p}}
    </div>
    Players:
    <app-players *ngIf="room" [room]="room"></app-players>
    end
</div>

Edit: The problem is in ngOnInit (players component): the @Input is undefined at this moment (and then it's doing getPlayersRoom(undefined) ). Any idea someone ? Impossible to use a @Input in constructor/ngoninit ? How should I do that then ?

2
Does the Room constructor instantiate the players property to an empty array? I'm asking, because the only location in your code where you use length is on the room.players array.David Walschots
I think this.room.players is the problem. Not only you need to make sure room is populated, but also room.players. Your ngIf needs to apply to that, ex. *ngIf="room?.players"windmaomao
Actually, you might want to work on new Room() to make sure room.players is an empty array upon init. I believe this might make your display layer robust. And you might be able to drop quite a bit *ngIfwindmaomao
Indeed, the problem is room.players, my room is an object (with id, players, password). There is something on room.players (but not at the ngoninit). The line <li *ngFor="let p of room.players">{{p}}</li> is working for example.Emerica
Any idea guys ?Emerica

2 Answers

0
votes

Gut response here, but it looks like your roomService.getRoom(+id) is asynchronous, which means depending on what happens when you initialize new Room(), that property may still be undefined by the time your child component is initialized.

Edit: on reflection, maybe you just need to declare your array properties as empty arrays up front:

players: Player[] = []

0
votes

I found an alternative solution:

service:

getPlayersRoom(ids: string[]) {

        for(let i=0;i<ids.length;i++){
            this.getPlayer(ids[i]).then(
                (player: Player) => {
                    if(player){
                       this.players.push(player);
                    }
                }
            );

        }
        this.emitPlayers();
}

component: I implemented OnChanges and then:

ngOnChanges(){

        if(Object.keys(this.room).length>0){
            this.playerService.getPlayersRoom(this.room.players);
        }
    }

I don't think that's the best way to do that but at least it's working.