0

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);
3
  • 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. Commented Jun 17, 2017 at 12:53
  • Done. And good point! Commented Jun 17, 2017 at 12:55
  • I think it has to do with ChangeDetectionStrategy.OnPush Commented Jun 17, 2017 at 13:02

1 Answer 1

1

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 {}
Sign up to request clarification or add additional context in comments.

2 Comments

Although it would be nice to re-enable OnPush. How would I do that?
I have found out, that if I change ngAfterViewInit to ngAfterContentInit, it works without problems, also with OnPush.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.