Angular how to get height of dynamically created div and send it to sibling component?

I have one parent and 2 child components like below

Parent-component.html

<child-component-1 [id]="id"></child-component-1>
<child-component-2></child-component-2>

child-component-1 has ngb-carousel component to show warnings and alerts to user.And this component is being created due to id and its result comes from API like below

child-component-1.ts

alertFound;

this.service.checkAlert(id).subscribe((result:any)=>{
 if(result.alertFound){
 this.alertFound=true;
}
})

child-component-1.html

<ngb-carousel #carousel *ngIf="alertFound" class="carousel-alert" [interval]="false" [wrap]="false"
  [showNavigationIndicators]="false">
........................(Things go on here after result comes)
</ngb-carousel>

In child-component-2,I need to get the height of this ngb-alert to make dynamic height calculation on the screen.If it exists,I need to subtract it from window.innerheight.

child-component-2.html

<div [style.height.px]="actualHeight"></div>

child-component-2.ts

this.actualHeight = window.innerHeight - 269;

Interestingly on child-component-1 when I tried to track it like below

@ViewChild('carousel', {read: ElementRef, static:false}) elementView: ElementRef;

ngAfterViewInit(){
  console.log(this.elementView.nativeElement.offSetHeight);
  //throws height of created carousel first,then if next id doesnt have alert still shows me the height which previously created
}

it shows previously created carousel height even though result is false and carousel couldn't be created at that moment.I guess this happening because result sometimes arrives very late,sometimes fast.To listen detections on viewchild I found something like below which works exactly as I wanted and solved emitting non-created alert height problem

  @ViewChildren('carousel', {read: ViewContainerRef}) viewContainerRefs;

      ngAfterViewInit() {
        this.viewContainerRefs.changes.subscribe(item => {
          if (item) {
             console.log(item._results[0]._lContainer[0][0].offsetHeight);
          }
        })
      }

Now problem is sending this height to child-component-2 where I should calculate height dynamically.

I considered 2 options.First one is creating subject on a service,emitting height to it from child-component-1 and listening it on child-component-2 like below

service.ts

  alertActive = new BehaviorSubject(0);
  setAlertHeight(value:number){
    this.alertActive.next(value)
  }

child-component-1

  ngAfterViewInit() {
    this.viewContainerRefs.changes.subscribe(item => {
      if (item) {
       this.service.setAlertHeight(item._results[0]._lContainer[0][0].offsetHeight);
      }
    })
  }

child-component-2

this.alertActive.subscribe(value=>{
 if(value){
   this.actualHeight-=value;
  }
})

On the example above,it causes the problem to bring height,even though alert not created.I logged subscribes on console from child-2 and child-1 and noticed that child-2 prints to console even though child1 didnt emit anything to it.

So I considered another option to send height from child-1 to parent via @Output and then via Input to child2 like below

child-1

  @Output() transferHeight: EventEmitter<any> = new EventEmitter();
  this.viewContainerRefs.changes.subscribe(item => {
      if (item) {
      this.transferHeight.emit(item._results[0]._lContainer[0][0].offsetHeight);
      }
    })

parent

    <child-component-1 (transferHeight)="transferedHeight($event)" [id]="id"></child-component-1>
    <child-component-2  [transferredHeight]="transferredHeight"></child-component-2>
 alertHeight;
 transferedHeight(comingHeight){
    this.alertHeight=comingHeight;
  }
  

child-2

  @Input() transferredHeight;
  ngOnInit(){
    this.actualHeight-=this.transferredHeight;
   }

this one really handles the problem that I mentioned previously.But if carousel created after child-2 created due to late network resut ,it returns undefined.Therefore I tried to use ngOnChanges to listen changes on the Input variable like below

   ngOnChanges(changes: SimpleChanges) {
    console.log(changes.transferredHeight.currentValue)
} 

this console.log doesnt print anything although I implemented onChanges.So Im waiting for your help.If there's a way to listen viewContainerRefs.changes of child-1 from child-2 would be the best case

Upvotes: 3

Views: 3453

Answers (1)

MikeJ
MikeJ

Reputation: 2324

The reason this attempt:

alertActive = new BehaviorSubject(0);
setAlertHeight(value:number){
  this.alertActive.next(value)
}

triggered the height to be set even when the alert was not active was because you used a BehaviorSubject rather than just a Subject. A BehaviorSubject provides an initial value (0 in your case), so immediately upon subscription the subscriber will receive that initial value (or the latest emitted value).

So you were on the right track, you just needed to use a regular Subject instead because then subscribers won't receive any values until the Subject emits.

So your service could look like this:

private _alertActive = new Subject<number>();
get alertActive(): Observable<number> {
  return this._alertActive.asObservable();
}

setAlertHeight(height: number) {
  this._alertActive.next(height);
}

(Note that the Subject is a private member but is exposed via a getter as an Observable. In general, subscribers shouldn't have access to the raw Subject unless they'll also be emitting values from it.)

And your child-2 component would subscribe to it like so:

this.service.alertActive.subscribe((height: number) => {
  this.actualHeight -= height;
});

You were also on the right track using ViewChildren and subscribing to its changes, but it might be simpler if you reference the ElementRefs rather than the ViewContainerRefs. That would look like this:

import { NgbCarousel } from "@ng-bootstrap/ng-bootstrap";

@ViewChildren(NgbCarousel, { read: ElementRef }) carousels: QueryList<ElementRef>;


ngAfterViewInit() {
  this.carousels.changes.subscribe((els: QueryList<ElementRef>) => {
    const height = els.first ? els.first.nativeElement.offsetHeight : 0;
    this.service.setAlertHeight(height);
  });
}

Here's a StackBlitz showing it working with this approach.

Upvotes: 2

Related Questions