0

I am building an app using Angular 4, AngularFire2, and Firebase.

I am running into an issue.

In my app-component.html I have this:

<app-person *ngIf="isPerson" [personId]="personId" [treeUniqueName]="treeUniqueName" [treeId]="treeId"></app-person>

In my app-component.ts I have this:

import { Component } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html'
})

export class AppComponent {
    isTree = false;
    treeId;
    treeUniqueName = '';
    treeDisplayName = '';
    isPerson = false;
    personId = '';
    isLocation = false;
    locationId = '';
    isEvent = false;
    eventId = '';
    isDocument = false;
    documentId = '';
    isSitemap = false;
    data = '';
    headers = new Headers();
    constructor(
        protected db: AngularFireDatabase
    ) {}

    ngOnInit() {

        let cleanPath = location.pathname.toLowerCase().replace(/\/+$/, '');
        //console.info(cleanPath);
        cleanPath = cleanPath || '';
        if (cleanPath === '/sitemap') {
            this.isSitemap = true;
        }

        let routeSegments = cleanPath.split("/");
        //console.info(routeSegments);
        if (routeSegments.length >= 3 && routeSegments[1] == 'family')
        {
            this.treeUniqueName = routeSegments[2].toLowerCase();

            let output = this.db.list('/tree', {
                query: {
                    orderByChild: 'unique',
                    equalTo: this.treeUniqueName
                }
            }).subscribe(tree => {
                this.treeId = tree[0].$key;
                this.treeDisplayName = tree[0].display;
                console.log(this.treeId);
            })

            if (routeSegments.length == 3)
            {
                this.isTree = true;
            }
            else if (routeSegments.length == 5 && routeSegments[3].toLowerCase() == 'person')
            {
                this.isPerson = true;
                this.personId = routeSegments[4].toLowerCase();
            }
            else if (routeSegments.length == 5 && routeSegments[3].toLowerCase() == 'location')
            {
                this.isLocation = true;
                this.locationId = routeSegments[4].toLowerCase();
            }
            else if (routeSegments.length == 5 && routeSegments[3].toLowerCase() == 'event')
            {
                this.isEvent = true;
                this.eventId = routeSegments[4].toLowerCase();
            }
            else if (routeSegments.length == 5 && routeSegments[3].toLowerCase() == 'document')
            {
                this.isDocument = true;
                this.documentId = routeSegments[4].toLowerCase();
            }
        }

    }
}

If I inspect the page, I see that the data attributes are being correctly bound:

<app-person ng-reflect-tree-id="964f000f-573b-481b-9e43-18bc77" ng-reflect-tree-unique-name="doe" ng-reflect-person-id="b051cb21-6419-4b6f-85b5-d0891bc2a166"><!--bindings={
  "ng-reflect-ng-for-of": ""
}--></app-person>

But then in my person.component.ts I have this:

import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Title, Meta } from '@angular/platform-browser';
import { EventPipe } from './event.pipe';

@Component({
    selector: 'app-person',
    templateUrl: './person.component.html'
})

export class PersonComponent implements OnInit, OnDestroy {
    @Input() treeId: string;
    @Input() treeUniqueName: string;
    @Input() personId: string;

    subscriptions: Array<any> = new Array<any>();
    people: Array<any> = new Array<any>();
    constructor(
        private title: Title,
        private meta: Meta,
        protected db: AngularFireDatabase
    ){}

    ngOnInit() {
        console.log(this.treeId);  // FOR SOME REASON THIS IS EMPTY
        this.subscriptions.push(
            this.db.list('/person', {
                query: {
                    queryByChild: 'tree',
                    equalTo: this.treeId,
                    limitToLast: 200
                }
            }).subscribe(people => {
                this.people = people;
            })
        );
    }

    ngOnDestroy() {
        this.subscriptions.forEach(s => s.unsubscribe());
    }   
}

For some reason this.treeId is always empty.

I am guessing this has to do with page lifecycle? If so, how do I get around it?


enter image description here

eat-sleep-code
  • 4,753
  • 13
  • 52
  • 98
  • I see you put a lot of code inside the constructor. We need to keep constructors as light as possible. Refactor that code to a service and inject back to the controller – Vega Aug 25 '17 at 16:16

3 Answers3

0

Correct syntax would be:

<app-person *ngIf="isPerson" [personId]="personId" [treeUniqueName]="treeUniqueName" [treeId]="treeId"></app-person>

You don't need {{}} for the @Input values.

And for the isPerson, you need to set it in the parent component, not in the child via @Input.

Vega
  • 27,856
  • 27
  • 95
  • 103
  • This makes zero difference. `isPerson` is correctly populated and is not a problem. The is issue is with `this.treeId`. – eat-sleep-code Aug 25 '17 at 16:05
  • post the parent code where you initialise treeId also please – Vega Aug 25 '17 at 16:07
  • have you checked it's value in app.component, just after you get it? this.treeId = tree[0].$key; – Vega Aug 25 '17 at 16:14
  • I added this. It actually appears it is executing, but executing _after_ the code from person.component.ts ? See screenshot (in original question). – eat-sleep-code Aug 25 '17 at 16:22
0

Since your treeId is only defined in the callback for the asynchronous function (in your case... the subscribe results), the child component won't wait for it unless you tell it to. It will instantiate virtually immediately after the app component does. You have a boolean for telling the child component to wait for 'isPerson', but it appears there are a number of conditions that will set 'isPerson' to true, regardless of whether treeId is an empty string still or not. All of those conditions are synchronously evaluated following the subscribe part, they won't wait either. You could try not initializing treeId as an empty string, just declare it and leave it undefined.

 treeId: string;

And then change your ngIf statement in the app component template to

 *ngIf="isPerson && treeId"

Vega is right that you normally would avoid putting much of anything in the constructor of a template, but in your case none of that stuff seems to depend on the view or any inputs, so your probably fine. And this is something that I don't think is related to your problem, but how are you getting 'location' in your app component without a location provider?

diopside
  • 2,981
  • 11
  • 23
  • I think you got it :) – Vega Aug 25 '17 at 16:26
  • Tried this, but didn't seem to make a difference. – eat-sleep-code Aug 25 '17 at 16:29
  • What is clean path if you log it right after assigning it? Without a location provider, I dunno how you're getting any values from location. – diopside Aug 25 '17 at 16:31
  • If you're using the Angular location provider, you would do location.path() as a method, to get the current path. But its not clear to me from your code if you are or not. – diopside Aug 25 '17 at 16:33
  • Actually, now it says "undefined". But that's not helpful because now _everyone_ gets returned on the page instead of the one I am asking for. I am not sure what you mean by needing a location provider. location. is inherently built into Typescript. – eat-sleep-code Aug 25 '17 at 16:33
  • so what happens if you console.log(location.pathname) – diopside Aug 25 '17 at 16:34
  • `/family/doe/person/b051cb21-6419-4b6f-85b5-d0891bc2a166` – eat-sleep-code Aug 25 '17 at 16:35
  • that is news to me! I've always used angular's location provider. didn't realize you could read the path without it. You use the location provider if you need to manually rewrite paths. but i guess you dont need it to simply read it – diopside Aug 25 '17 at 16:38
  • me too, I thought you need to import it – Vega Aug 25 '17 at 16:39
  • if cleanPath is undefined right after you assign it, then it will always be assigned to an empty string in the following line. So then route segments would be an empty array. And I'm not sure how any of those conditionals would ever evaluate in that case. – diopside Aug 25 '17 at 16:40
  • I am guessing -- as this is only my second Angular project, just guessing -- that Angular's location provider simply extends the default TypeScript location with additional options. But everything in TypeScript should be accessible within Angular (would be really bad architecture if it doesn't). – eat-sleep-code Aug 25 '17 at 16:41
  • this thread makes it sound like location.pathname would be a property of the window object, not a typescript feature - https://stackoverflow.com/questions/37796449/how-to-get-the-current-url-in-the-browser-in-angular-2-using-typescript – diopside Aug 25 '17 at 16:42
  • @diopside cleanPath is immediately available. It appears to be the issue is that person.component.ts is being fired before the code in my app.component.ts – eat-sleep-code Aug 25 '17 at 16:42
  • Well i'm stumped bc that doesn't appear to be possible at all from your code – diopside Aug 25 '17 at 16:43
  • I would skip the booleans in your code entirely - you can just use the strings for the same thing. If you declare them all as undefined variables typed to string --> stringName: string; <-- They will evaluate to false in an ngIf statement until they are assigned a value – diopside Aug 25 '17 at 16:59
  • @diopside see my self-answer below. I figured out the lifecycle issue. – eat-sleep-code Aug 25 '17 at 18:01
0

I figured out a way around this problem. I moved the code out of the Constructor of app.component.ts and into an ngOnInit. This had no effect.

I then switched the ngOnInit of 'person.component.ts' to be an ngOnChanges. Now this code fires after the app.component.ts code -- which is what is desired.

The this.treeId is now correctly populated.

eat-sleep-code
  • 4,753
  • 13
  • 52
  • 98
  • If it works... it works, but be aware that ngOnChanges is called **constantly** ... with every single change to the page. Sometimes that can be a hundred times from just moving a mouse. So you may be firing that query many many times, or adding a lot of elements to that array. – diopside Aug 25 '17 at 18:09
  • @diopside, do you think if the OP puts a 'flag' after the first load, and skips the next time if it's raised would help? – Vega Aug 25 '17 at 18:28
  • ya that would def prevent it. but another problem is ngOnChanges is called the first time *before* ngOnInit is. Which I found very strange... but angular team defends the logic. – diopside Aug 25 '17 at 18:30
  • Thank you for this insight! I will keep an eye on it. I will leave the console.log info for now to watch to see how often it is being called. – eat-sleep-code Aug 25 '17 at 18:53