3

I am dabbling with learning Angular 4 and I am running into a stumbling block.

I have the following app/content.component.ts file:

import { Component, Input, OnInit } from '@angular/core';
import { AngularFire, FirebaseListObservable } from 'angularfire2';
import { Content } from './content';

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

export class ContentComponent implements OnInit {
    @Input() loadScripts: string;
    items: FirebaseListObservable<any[]>;
    constructor(af: AngularFire) {
        this.items = af.database.list('/pages', { 
            query: {
                orderByChild: 'sortOrder',
                limitToLast: 100
            }
        });
    }
    ngOnInit() {
        console.log(this.loadScripts);
    }
}

I have the following app/content.component.html file:

<div id="content-{{item.$key}}"  *ngFor="let item of items | async | path : 'path'">
    <h2>{{item.title}}</h2>
    <div [innerHTML]="item.content | trustedHtml"></div>
</div>

"items" is a result set from Firebase. I need to pass the value of "item.loadScripts" to the loadScripts variable in the ContentComponent class.

How do I do this?

eat-sleep-code
  • 4,753
  • 13
  • 52
  • 98

2 Answers2

2

You can subscribe to the observable yourself (moved it to ngOnInit). In the subscription you can set this.items and iterate over the result. Because of this change you want to keep track of your subscription and unsubscribe on destruction.

import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { AngularFire, FirebaseListObservable } from 'angularfire2';
import { Content } from './content';

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

export class ContentComponent implements OnInit, OnDestroy {
    @Input() loadScripts: string;
    subscriptions: Array<any> = [];
    items: Array<any>;
    constructor(private af: AngularFire) {}
    ngOnInit() {
        this.subscriptions.push(
            this.af.database.list('/pages', { 
                    query: {
                        orderByChild: 'sortOrder',
                        limitToLast: 100
                    }
                }).subscribe((items) => {
                    this.items = items;
                    this.items.forEach((item) => {
                        // do something with each item.loadScripts
                    });
                });
            );
    }
    ngOnDestroy() {
     this.subscriptions.forEach(s => s.unsubscribe());
    }
}

Make sure to remove the async pipe from your html.

<div id="content-{{item.$key}}"  *ngFor="let item of items | path : 'path'">
    <h2>{{item.title}}</h2>
    <div [innerHTML]="item.content | trustedHtml"></div>
</div>

If you always want to apply the filter, update the code inside the subscribe to something like this (and remove the filter from the html):

this.items = items
    .filter((item) => {
        return checkPathFilter(item);
    });

this.items.forEach((item) => {
    // do something with each item.loadScripts
});

If you always filter down to 1 item, then you can remove the ngFor all together and do this in the subscribe:

this.item = items
    .find((item) => {
        return checkPathFilter(item);
    });

this.loadScripts = item.loadScripts;
Joseph Connolly
  • 891
  • 11
  • 21
  • Since my path pipe does some filtering on the results, do I also need to apply similar filtering logic to the subscribe method in the ngOnInit? – eat-sleep-code Apr 19 '17 at 17:52
  • Also, now that the observable is inside the ngOnInit, I get find name 'af' because it is no longer inside the constructor? – eat-sleep-code Apr 19 '17 at 18:03
  • For the second question, change the constructor's signature to `protected af: AngularFire`, and the `af` service will be set as a member on your class, accessible via `this.af`. – FraserES Apr 19 '17 at 18:05
  • For your first question, you'll need to repeat the filtering process yes. You can inject the pipe in the constructor and use its `transform` method, as in [this answer](http://stackoverflow.com/questions/35144821/angular-2-use-pipes-in-services) – FraserES Apr 19 '17 at 18:07
  • Just updated the code to what @FraserES mentioned. Your other filter should still work. Removing items is the same as after the async pipe. You will need to repeat the filter in the function too. – Joseph Connolly Apr 19 '17 at 18:08
  • I don't know enough about what AngularFire is returning to say if you could apply the pipe once in the component and remove it from the filter - maybe @JosephConnoll can help you with that :) – FraserES Apr 19 '17 at 18:09
  • @JosephConnolly At runtime `this.subscriptions` appears to be undefined as the following error is thrown `Cannot read property 'push' of undefined.` – eat-sleep-code Apr 19 '17 at 19:42
  • Doh, dangers of not actually having the running code. Just initialize it. Updated the code above. subscriptions: Array = []; – Joseph Connolly Apr 19 '17 at 20:04
2

You should move the code which is currently in the constructer into the onInit() method I think.

Since the contents of your firebase request are already returning to the component, you don't need to pass them back from the template. You just need to copy whatever comes back from firebase during your request handling function into the loadScripts variable. However, if loadScripts is defined on each item, then your loadScripts class property will need to be an Array, rather than just a string:

this.items = af.database.list('/pages', { 
        query: {
            orderByChild: 'sortOrder',
            limitToLast: 100
        }
    });
this.items.subscribe(items => this.loadScripts=items);

However, I have a few questions. What do you need to do with each value of loadScripts?

If you need to process each item differently, you'd be better off writing a child component which takes an item as an input and then processes/renders it. If the processing is more simple, you could define a function on your ContentComponent which takes an item as a single argument and then returns whatever value you need.

FraserES
  • 356
  • 1
  • 8
  • Didn't see that Joseph Connolly posted below - his answer is more in depth, except for the bit about asking what you want to do with each item :) – FraserES Apr 19 '17 at 17:46
  • See my question to Joseph Connolly above, maybe you can answer too? – eat-sleep-code Apr 19 '17 at 17:56
  • I ultimately need only one item, but the problem is I need to filter the results before hand. Because Firebase lacks robust querying capabilities like something MS SQL, I end up putting this filtering logic into the custom 'path' pipe. – eat-sleep-code Apr 19 '17 at 18:25
  • Ah - @JosephConnolly has updated his answer and I agree with him! – FraserES Apr 19 '17 at 18:38
  • Thanks for your help on that. Still having a couple of issues (see above) but SO close. – eat-sleep-code Apr 19 '17 at 19:51