async pipe sends 'null' value to the child component

Viewed 13981

I want to pass a value to the child component. this value is an Observable, so I use async pipe.

<child [test]="test$ | async"></child>

test$ is just a normal observable variable, that emits values after a period of time (3000 ms), simulating an API request to the server.

this.test$=timer(3000).pipe(
      mapTo("value")      
 )

in child component, I just want to check test value

@Input() test: any;

constructor(){
    console.log("child/test", this.test); //null
    setTimeout(()=>console.log("child/test (timeout)", this.test),4000) //value

   if(this.test){
     //maintain and check `this.test`
     //this code will not run, because at this point `this.test` is null.
     //we don't know the exact time that `this.test` will have a value
     //this causes that `this.test` is wrong

      this.checked=true 
     }
  }

<div *ngIf="checked">{{test}}</div>

I don't want to change the type of test to be Observable and subscribe to it. I want to receive the final value directly. and I don't want to modify the edit component at all.

using ChangeDetectorRef to manually trigger the change detector is not

@Input() test$:Observable

constructor(){
  this.test$.subscribe(v=>this.test=v)
}

I also made this stackblitz to check the value changing among all the compoonent's hooks.

5 Answers

async pipe will return null when no value is emitted by Observable yet. So, the value of test in child component is:

  • undefined in constructor because @Input() variables are not assigned at this state
  • null after that (e.g. first onChanges hook or onInit hook`) when no value is emitted by the Observable
  • value when the Observable emit new value

Now, you should either create child component only when test variable is not null with *ngIf, or handle correctly the state of child component with nullable test (e.g. Add a progress bar when test is null). The choice is up to you.

Much easier solution:

(test$ | async) || defaultTestValue

You can create variable inside your template like this:

test$ | async; let test;

then later you can check:

*ngIf='test'

if it is true then you can render your child component.

You can use the rxjs operator startWith to set an initial value for your observable. If incase you want an custom starting value.

this.test$=timer(3000).pipe(
  mapTo("value"),
  startWith(0))
Related