1

I use component on my html template with quite complicated *ngIf condition

<my-component #myComponent *ngIf="..."></my-component>

and in .ts file

@ViewChild('myComponent') public myComponent: MyComponent;

After 'click' on button I send request, show/hide some stuff... on ma page - and after that *ngIf in my component should be set to true. When this.myComponent is not null I can call init(data) function inside it. I try to use rxjs for "wait" when this component is not null (setTimeout approach fails sometimes) as follows

let checking = true;

let data = await this.loadData();

interval(100).pipe(takeWhile(()=> checking)).subscribe( _ => { 
  if(this.myComponent) { 
    this.myComponent.init(data)
    checking = false;
  }
})

// ... (stuff which magic which makes that this.myComponent is not null)

But this code is quite not nice - it checks variable value every 100ms, the code is also quite long and complicated. Is there a more smart solution which e.g. which trigger code exactly only when this.myComponent change value from null (without pinging it every 100ms)?

Update

I forgot to mention than above interval... code is inside some async callback so actually I need to wait when data is ready and myComponent is not null ( I update above code too)

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345

3 Answers3

2

You should move the call this.myComponent.init(data) inside of ngOnInit within MyComponent. Use an @Input() variable to initialize the component with Data.

<my-component #myComponent *ngIf="..." [data]="data"></my-component>

And leverage ngOnInit inside of MyComponent to do the initialization:

@NgComponent({
  selector: 'my-component'
})
export class MyComponent implements OnInit {
   @Input()
   data: Data;

   ngOnInit() {
      if(this.data) {
         this.init(data);
      }
   }
   init(data: Data) {
      ...
   }
}

ngOnInit is guaranteed to be called once per compoonent lifetime. And ngIf will destroy/create the component each time boolean expression is toggled.

Michael Kang
  • 52,003
  • 16
  • 103
  • 135
  • this is quite clever approach :) - however the `data` is inside quite nested async code - so actually I must wait for it too ( I update question) - but I give +1 - thank you – Kamil Kiełczewski Oct 27 '20 at 20:44
2

You could use an EventEmitter on the child component and listen for that:

On the child component, which you send in its ngOnInit function or whenever you're ready for it:

@Output() loaded = new EventEmitter<void>();
await this.getSomeDataFromSomeWhere();
this.loaded.emit();

In the parent component HTML:

<my-component #myComponent *ngIf="..." (loaded)="myComponent.init(data)"></my-component>
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49
  • this is quite clever approach - however on my-component I do ngOnInit() { loaded.emit() } - and in parent html (load)=initMyComponent() and in .ts initMyComponent() { this.myComponent.init(this.data) } - however the data is inside quite nested async code - so actually I must wait for it too. But I give +1 - thank you – Kamil Kiełczewski Oct 27 '20 at 20:45
0

You can simplify checking by use filter and take(1) (which auto-unsubscribe after take first value)

interval(200).pipe(filter(_ => this.myComponent), take(1)).subscribe(_ => {
  // ... this code will run once when this.myComponent is not null
})

const { interval } = rxjs;
const { filter, take } = rxjs.operators;

this.myComponent = null;

setTimeout(()=> myComponent = "init", 1000)

interval(200).pipe(filter(_ => this.myComponent), take(1)).subscribe(_ => {
  console.log('My component is ready!');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.min.js" integrity="sha256-85uCh8dPb35sH3WK435rtYUKALbcvEQFC65eg+raeuc=" crossorigin="anonymous"></script>
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345