1

I have an AlertComponent that I would like to use as a directive in my AppComponent and expose it so that it's available (as a sort of singleton) to all the routes/children components from AppComponent. But I can't seem to find a way to get the instance of the AlertComponent object used as a directive in order to call it's methods and see the changes made on the directive (i.e. add/remove alerts to/from the page).

Here is AlertComponent:

import { Component } from 'angular2/core';

import { Alert } from './model';

@Component({
    selector: 'alerts',
    templateUrl: './alert/index.html'
})
export class AlertComponent {
    alerts: Array<Alert>;

    constructor() {}

    add(alert: Alert) {
        this.alerts.push(alert);
    }

    remove(index: number) {
        this.alerts.splice(index, 1);
    }

    clear() {
        this.alerts = [];
    }
}

export { Alert };

And AppComponent:

import { Component, OnInit, provide } from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
import { HTTP_PROVIDERS, RequestOptions } from 'angular2/http';
import { CookieService } from 'angular2-cookie/core';

import { UserComponent } from '../user/component';
import { AlertComponent, Alert } from '../alert/component';

import { ExtendedRequestOptions } from '../extended/RequestOptions';
import { UtilObservable } from '../util/observable';

@Component({
    selector: 'app',
    template: `
<alerts></alerts>
<router-outlet></router-outlet>
`,
    //styleUrls: [ 'app/style.css' ],
    directives: [
        ROUTER_DIRECTIVES,
        AlertComponent
    ],
    providers: [
        ROUTER_PROVIDERS,
        HTTP_PROVIDERS,
        provide(RequestOptions, { useClass: ExtendedRequestOptions }),
        CookieService,
        UtilObservable,
        AlertComponent
    ]
})
@RouteConfig([{
    path: '/user/:action',
    name: 'User',
    component: UserComponent,
    useAsDefault: true
}
])
export class AppComponent implements OnInit {

    constructor(public _alert: AlertComponent) {}

    ngOnInit() {
        this._alert.add(new Alert('success', 'Success!'));
    }
}

I'd like to have the same instance of AlertComponent available to all descendant routes/children of AppComponent (e.g. UserComponent), so as to add alerts to the same directive.

Is this possible? Or is there another, more proper way to do this?

[Update]

The chosen solution answers the title question, but I also wanted to have a simple solution to share alerts among my components. Here's how to do that:

AlertComponent:

import { Component } from 'angular2/core';

import { Alert } from './model';

export class Alerts extends Array<Alert> {}

@Component({
    selector: 'alerts',
    templateUrl: './alert/index.html'
})
export class AlertComponent {
    constructor(public alerts: Alerts) {}

    add(alert: Alert) {
        this.alerts.push(alert);
    }

    remove(index: number) {
        this.alerts.splice(index, 1);
    }

    clear() {
        this.alerts.length = 0;
    }
}

export { Alert };

AppComponent:

import { Component, provide } from 'angular2/core';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
import { HTTP_PROVIDERS, RequestOptions } from 'angular2/http';

import { AlertComponent, Alerts } from '../alert/component'
import { UserComponent } from '../user/component';

import { ExtendedRequestOptions } from '../helpers/extensions';

@Component({
    selector: 'app',
    template: `<router-outlet></router-outlet>`,
    directives: [
        ROUTER_DIRECTIVES
    ],
    viewProviders: [
        provide(Alerts, { useValue: [] })
    ],
    providers: [
        ROUTER_PROVIDERS,
        HTTP_PROVIDERS,
        provide(RequestOptions, { useClass: ExtendedRequestOptions })
    ]
})
@RouteConfig([{
    path: '/user/:action',
    name: 'User',
    component: UserComponent,
    useAsDefault: true
}
])
export class AppComponent {}

Basically, I'm providing a singleton array of alerts that's used by every AlertComponent.

You can move the provide() to providers (instead of viewProviders) if you want to use it outside of directives, but if not, keep it simple and restrict it this way.

Hope this helps someone :)

Kesarion
  • 2,808
  • 5
  • 31
  • 52

2 Answers2

3

You need to use ViewChild decorator to reference it:

@Component({
})
export class AppComponent implements OnInit {
  @ViewChild(AlertComponent)
  _alert: AlertComponent;

  ngAfterViewInit() {
    // Use _alert
    this._alert.add(new Alert('success', 'Success!'));
  }
}

@ViewChild is set before the ngAfterViewInit hook method is called.

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • Thanks for the answer! Is there any way that I can share the same ViewChild/Directive data between two or more components? – Kesarion May 11 '16 at 12:40
  • I don't think so. In this case, you need to leverage a shared service with obsersables. This way you can notify and be notified. See this link for more details: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service – Thierry Templier May 11 '16 at 12:42
2

expose it so that it's available (as a sort of singleton) to all the routes/children components from AppComponent.

Or is there another, more proper way to do this?


Create and bootstrap a service for AlertComponent, like this

AlertService

import {Injectable} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import 'rxjs/add/operator/share';

@Injectable()
export class AlertService {
  private _alerts: Array<Alert> = [];
  public alertsChange: Subject<Array<Alert>> = new Subject();

  public get alerts(): Array<Alert> {
    return this._alerts;
  }

    add(alert: Alert) {
        this._alerts.push(alert);
        this.alertsChange.next(this._alerts);
    }

    remove(index: number) {
        this._alerts.splice(index, 1);
        this.alertsChange.next(this._alerts);
    }

    clear() {
        this._alerts = [];
        this.alertsChange.next(this._alerts);
    }
}

Bootstrap AlertService

import {bootstrap} from '@angular/platform-browser-dynamic';
import {YourApp} from 'path/to/YourApp-Component';
import { AlertService} from 'path/to/alert-service';

bootstrap(YourApp, [AlertService]);

AlertComponent

import { Component } from 'angular2/core';
import { Alert } from './model';
import { AlertService} from 'path/to/alert-service';
@Component({
    selector: 'alerts',
    templateUrl: './alert/index.html'
})
export class AlertComponent {
    alerts: Array<Alert>;

    constructor(alertService: AlertService) {
      alertService.alertsChange.subscribe((moreAlerts: Array<Alert>) => {
         this.alerts = moreAlerts;
      })
    }
}

All the routes/children components

(sample):

import { Component} from '@angular/core';
import { AlertService} from 'path/to/alert-service';
@Component({
   template: `.....`
})
export class SampleComponent {

  constructor(public alerts: AlertService){} 

  ngOnInit(){
    this.alerts.add(new Alert('success', 'Success!'));
  }

  ngOnDestroy(){
    this.alerts.clear();
  }
}

To see other alike examples see this question

Community
  • 1
  • 1
Ankit Singh
  • 24,525
  • 11
  • 66
  • 89
  • That's definitely going to work. Thanks! Thierry Templier nailed the title question first, though, so I'm going to be fair and give this one to him. I think new people like me should see his answer first, before implementing something more complicated; for example I chose to adapt to Thierry's simpler example. Definitely a thumbs up to you as well. Thanks again! – Kesarion May 11 '16 at 09:24
  • 1
    You're welcome.... you did the right thing .... I don't know hot to act humble but that's very nice of you to clarify and give feedback.... cheers – Ankit Singh May 11 '16 at 10:12