42

Is there a way in Angular2 to have an event fired when my component becomes visible? It is placed in a tabcontrol and I want to be notified when the user switches. I'd like my component to fire an event.

James Lawruk
  • 30,112
  • 19
  • 130
  • 137
Shimrod
  • 3,115
  • 2
  • 36
  • 56
  • If this is a tab control you've implemented, surely you could add an event emitter and emit in the same place you change tab? – Seer Nov 17 '16 at 11:21
  • Well I use primeng's tab control, and the only event that exists is called before the change, so the control is not yet visible at this time :( – Shimrod Nov 17 '16 at 11:46

6 Answers6

28

What I finally did (which is not very beautiful but works while I don't have a better way to do it...) is to use the ngAfterContentChecked() callback and handle the change myself.

@ViewChild('map') m;
private isVisible: boolean = false;
ngAfterContentChecked(): void
{
    if (this.isVisible == false && this.m.nativeElement.offsetParent != null)
    {
        console.log('isVisible switched from false to true');
        this.isVisible = true;
        this.Refresh();
    }
    else if (this.isVisible == true && this.m.nativeElement.offsetParent == null)
    {
        console.log('isVisible switched from true to false');
        this.isVisible = false;
    }
}
Shimrod
  • 3,115
  • 2
  • 36
  • 56
  • 1
    What is m.nativeElement ? – dGayand Dec 28 '18 at 20:42
  • 1
    @dGayand nativeElement points to the native DOM HTML element. – Thomas Bindzus Mar 06 '19 at 04:22
  • 1
    This may work, but consider that ngAfterContentChecked is been executed at any kind of event in father or child trhee component. Its maybe called 100 times and you only really require 1 execution of this 100 times – Daniel Delgado Aug 07 '19 at 20:16
  • 1
    very strange that Angular framework is not providing a solution. ngAfterContentChecked didn't work for me. It was not triggered when componant was removed from DOM ex: with ngIf. https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent – Ced Jan 12 '22 at 14:45
13

There is no such event, but if you're using a tab control, the proper way to do this would be to create a tab change @Output for your tab control if it's custom, otherwise, most tab controls (like ng-bootstrap) have some tab change event as well.

If your component has to be aware of this, you can use this tab change event to detect which tab is visible, and if you know which tab is visible, you also know if your component is visible or not. So you can do something like this:

onTabChange(event) {
    this.currentTab = /** Get current tab */;
}

And then you can send it to your component itself if you have an input:

@Input() activated: boolean = false;

And then you can apply it with:

<my-component [activated]="currentTab == 'tabWithComponent'"></my-component>

Now you can listen to OnChanges to see if the model value activated changed to true.


You can also refactor this to use a service with an Observable like this:

@Injectable()
export class TabService {
  observable: Observable<any>;
  observer;

  constructor() {
    this.observable = Observable.create(function(observer) {
      this.observer = observer;
    });
  }
}

When a component wishes to listen to these changes, it can subscribe to tabService.observable. When your tab changes, you can push new items to it with tabService.observer.next().

g00glen00b
  • 41,995
  • 13
  • 95
  • 133
  • 1
    That's a great idea, however I don't thnik it work in my case because the tabcontrol I use is from primeng and they only provide an event on tab change _before_ the tab has actually changed => my component is not visible at this time... – Shimrod Nov 17 '16 at 12:23
8

You can use the ngAfterViewInit() callback

https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html

Update

The new Intersection Observer API can be used for that https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API See also https://stackoverflow.com/a/44670818/217408

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 3
    it doesn't seem to fit my needs, I placed a log inside this callback, and it's only called once (regardless if my component is visible or not). – Shimrod Nov 17 '16 at 09:54
  • 11
    Angular doesn't have a way to know when a component becomes visible if it's just hidden behind another DOM element.. If you use `*ngIf=""` to show/hide, then component when the tab is selected or deselected, then `ngAfterViewInit()` is called every time. – Günter Zöchbauer Nov 17 '16 at 09:56
  • 3
    ngAfterViewInit is called after the view is initialized, not after the element is viewed – Chris McCowan Jul 19 '18 at 18:52
1

Best way to work around this limitation of Angular is to use a shared service that provides a Subject your component can subscribe to. That way new values could be pushed onto the Observable and the components which subscribe get the newest data and can act accordingly.

Fyi: The difference between a normal Observable and a Subject is that a Subject is multicast whereas an Observable could only be subscribed to by one Subscriber.

As a small example I show you a possible implementation of a shared-service and following the subscription inside the component that needs this new data.

Shared-service:

// ...

private actualNumberSubject = new Subject<number>()
public actualNumber$ = this.actualNumberSubject.asObservable()

/**
 * @info CONSTRUCTOR
 */
constructor() {}

/**
 * @info Set actual number
 */
setActualNumber(number: number) {
    this.actualNumberSubject.next(internalNumber)
}

// ...

Push new value onto the subject from anywhere where shared.service is imported:

// ...
this.sharedService.setActualNumber(1)

Subscribe to sharedService.actualNumber$ in component to process/display that new data:

// ...
this.sharedService.actualNumber$.subscribe(number => {
    console.log(number)
    
    // e.g. load data freshly, etc.
})

// ...
Torsten Barthel
  • 3,059
  • 1
  • 26
  • 22
  • 1
    Perfect for async response dependencies, where features within other components need to be updated to match a new state. Such as account or environment change events. – Peter O Brien May 09 '23 at 10:20
0

For those watching at home, you can now use ngAfterContentInit() for this, at least on Ionic anyway. https://angular.io/guide/lifecycle-hooks

Christopher Cook
  • 780
  • 8
  • 16
-1

I have the same purpose and cannot get a satisfy approach to it. The first answer will call so many times. There is a compromised way I used, of course, not elegant either. In parent component, I set a method:

parentClick() {
  setTimeout(() => {
    // TO-DO 
    This.commonService.childMethod();
  }, time);
}

Maybe the method not accurate in time, but in some way, you reach the destiny.