14

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.

Sh eldeeb
  • 1,589
  • 3
  • 18
  • 41

5 Answers5

15

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.

HTN
  • 3,388
  • 1
  • 8
  • 18
14

app.component.html

<ng-container *ngIf="(test$ | async) as test; else defaultTmpl">
    <child [test]="test"></child>
</ng-container>
<ng-template #defaultTmpl>Default Template</ng-template>

For more details please take a look: https://ultimatecourses.com/blog/angular-ngif-async-pipe

hexacyanide
  • 88,222
  • 31
  • 159
  • 162
Alexander
  • 262
  • 1
  • 11
  • this is a very good idea, but actually I want to send `test` directly to the child component even it don't have a value yet. this makes the child component display a default template until `test` get it's value without the need to implement a default template myself in my parent component. – Sh eldeeb May 08 '20 at 15:26
  • Okay, another solution is to use `ngOnChanges()` hook to track changes in your `@Input()`. It would minor changes in child component however you can properly track input params. Take a look please: https://dev.to/nickraphael/ngonchanges-best-practice-always-use-simplechanges-always-1feg – Alexander May 08 '20 at 15:46
  • @Alexander - Please note that he doesn't want to modify the child component at all. – ConnorsFan May 08 '20 at 15:50
  • 1
    @ConnorsFan - anyway he has to make some adjustments due to the fact that he wants to apply switching between `default` template and template with data in the child component – Alexander May 08 '20 at 15:55
12

Much easier solution:

(test$ | async) || defaultTestValue
rob2d
  • 964
  • 9
  • 16
3

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.

KamLar
  • 428
  • 4
  • 8
  • Inside your parent component. Then you use simply `ngIf` to check if `test` is anything else than `undefined` or `null` and if so, you pass it to child component. – KamLar May 08 '20 at 14:31
1

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))
Ram
  • 451
  • 10
  • 18