0
votes

I have a situation in Angular (4) where an async-pipe doesn't bind to an Observable set in ngAfterViewInit(), unless I initialise it in the constructor using Observable.interval(1000). If I remove the .interval-part, the async pipe will not subscribe to the observable when ngAfterViewInit() runs.

I would like to initialise the observables like this in the top of the class, but that breaks the functionality:

resultStream: Observable<string>;
genderStream: Observable<number>;

I have to declare them like this:

// Weird fix: I need to add .interval(?) for async pipe to work later on...
resultStream: Observable<string> = Observable.interval(1000).map(() => "");
genderStream: Observable<number> = Observable.interval(1000);

So my solution now feels a bit unsatisfactory, since I have to use the .interval() setup.

The problem is reproduced in this plunker: https://plnkr.co/edit/q3TR5iszU1swFrTjajEa

Click a gender, and enter an integer in the input field to show a result.

Can you help me with an easy/elegant solution for this? Thanks.

The complete class is this:

import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, ChangeDetectionStrategy } from '@angular/core';
import { Observable } from 'rxjs/Rx';


@Component({
    selector: 'my-app',
    template: `
    <div class="row">
    <div class="col-md-6">
        <form class="form-inline">
            <input #distance class="form-control" placeholder="Distance">
            <button #femaleGender type="button" class="btn btn-default" [class.active]="(genderStream | async) === 1">Female</button>
            <button #maleGender type="button" class="btn btn-default" [class.active]="(genderStream | async) === 0">Male</button>
        </form>
    </div>
    <div class="col-md-6">
        <h4>Result: {{ resultStream | async }}</h4>
    </div>
    </div>
`,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements AfterViewInit {

    // Weird fix: I need to add .interval(?) for async pipe to work later on...
    resultStream: Observable<string> = Observable.interval(1000).map(() => "");
    genderStream: Observable<number> = Observable.interval(1000);

    @ViewChild('distance') distance: ElementRef;
    @ViewChild('maleGender') maleGender: ElementRef;
    @ViewChild('femaleGender') femaleGender: ElementRef;


    ngAfterViewInit() {
        let distance$ = Observable.fromEvent(this.distance.nativeElement, 'keyup')
            .map((event: Event) => event.target)
            .map((element: HTMLInputElement) => element.value)
            .map(value => parseInt(value))
            .filter(value => !isNaN(value));

        // In the calculation 0 is used for males and 1 for females.
        let male$ = Observable.fromEvent(this.maleGender.nativeElement, 'click').map(() => 0);
        let female$ = Observable.fromEvent(this.femaleGender.nativeElement, 'click').map(() => 1);
        let gender$ = Observable.merge(male$, female$);

        this.genderStream = gender$;

        this.resultStream = Observable.combineLatest(
            distance$,
            gender$,
            (distance, gender) => calculateResult(distance, gender).toFixed(1)
        );
    }
}

const calculateResult = (distance: number, gender: number) => 18.38 + (0.033 * distance) - (5.92 * gender);
1
Please take the relevant content from your plunkr and edit it into your post. Links expire eventually, which will result in your post being more or less useless for other users experiencing a similar problem. Plus you are essentially asking someone who is trying to help to jump through some hoops in order to provide that help. - The Head Rush
Done. And good point! - skovmand
I think it has to do with ChangeDetectionStrategy.OnPush - skovmand

1 Answers

1
votes

I figured it out.

It was because the parent component had change detection set to ChangeDetectionStrategy.OnPush. Disabling it in the component AND the parent page did the trick.

import { Component, ChangeDetectionStrategy } from '@angular/core'

@Component({
    selector: 'parent-page',
    // Disabling OnPush change detection did the trick:
    // changeDetection: ChangeDetectionStrategy.OnPush,
    templateUrl: './parent.html'
})
export class ParentPage {}