Emit events between nested components grandchild to root component - javascript

I have wheels.component nested to car.component.
wheels.component:
export class WheelsComponent {
#Output() onLoaded : EventEmitter<string>() = new EventEmitter<string>();
private downloadAllFiles(url: string) {
this.onLoaded.emit('Hello, World 1!');
//some operations to wait
this.onLoaded.emit('Hello, World 2!');
};
}
Component car.component is not written at html page, but called through routing at car-routing.module.ts:
#NgModule({
imports: [
RouterModule.forChild([
{
path: 'sfactmessage/:id',
component: CarComponent,
resolve: {
card: cardResolver
}
}
])
],
exports: [RouterModule]
})
export class CarRoutingModule {}
What I want is to handle event emitted from wheels.component, not at car.component, but at app.component.
Is it possible to handle event at app.component?
The plunker sample is not working (sorry, this is my first plunkr example), but gives a view how my app is arranged.

Hello_ friend.
So basically if you want to use events globally in your application you can use a Service in combination with EventEmitter
In this case you create a service for example car.service.ts
import { Injectable, EventEmitter } from '#angular/core';
#Injectable()
export class CarService {
onLoaded : EventEmitter<string> = new EventEmitter<string>();
}
Then use this service in a child component to emit events like this wheels.component.ts
import { Component, EventEmitter } from '#angular/core';
import { CarService } from './car.service';
#Component({
selector: 'wheels',
template: '<a (click)="sendValues()"> Click me to send value </a>'
})
export class WheelsComponent {
constructor(private carService:CarService ){}
sendValues() {
/* Use service to emit events that can be used everywhere in the application */
this.carService.onLoaded.emit('Button in WheelsComponent was clicked ...');
};
}
and then capture this event from AppComponent for example app.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
import { CarService } from './cars/car.service';
import { Subscription } from 'rxjs';
#Component({
selector: 'my-app',
templateUrl: `src/app.component.html`
})
export class AppComponent implements OnInit, OnDestroy{
private subscription: Subscription;
private loading = true;
name = 'Angular';
constructor(private carService: CarService){}
ngOnInit(){
this.subscription = this.carService.onLoaded.subscribe((message) => {
/*
Here you receive events from anywhere where
carService.onLoaded.emit() is used
**/
alert(`From AppComponent -> ${message}`);
});
}
ngOnDestroy(){
/* Don't forget to unsubscribe when component is destroyed */
this.subscription.unsubscribe();
}
}
I M P O R T A N T______________
If you want your service to work globally you need to declare it in the top level providers for example app.module.ts is a good place:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule } from '#angular/forms';
import { AppComponent } from './app.component';
import { CarComponent} from './cars/car.component';
import { WheelsComponent} from './cars/wheels.component';
import { HomeComponent} from './home.component';
import { routing } from './app.routing';
import { CarService } from './cars/car.service';
#NgModule({
imports: [ BrowserModule, FormsModule, routing ],
declarations: [ AppComponent, CarComponent, WheelsComponent, HomeComponent ],
providers: [ CarService ], // <-------- SEE HERE
bootstrap: [ AppComponent ]
})
export class AppModule { }
CLICK HERE TO SEE THE DEMO

Related

Angular 4 - Firefox: NgOnInit() isn't called after constructor

After a redirection (router.navigate), one of my components is created, but its ngOnInit() function is never called.
It's working fine with Chrome, this bug only appears with Firefox.
I've tried putting a console.log in the constructor and it prints - however, I'm not certain that the construction process actually finishes (no way to log that, and the step by step debug is illegible).
The resolvers gathering data do work, and the requests to my APIs are successful.
Using Angular 4.3.1 with webpack 3.12.0 and Firefox 63.0.3.
If you have any idea where I could look, it would help a lot.
home.module.ts
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { SharedModule } from '../../app/shared/shared.module';
import { HomeComponent } from './components';
import { MessageHomeResolver } from './resolvers';
const childRoutesHome: Routes = [
{
path: '',
component: HomeComponent,
resolve: {
messages: MessageHomeResolver,
}
}
];
#NgModule({
imports: [
RouterModule.forChild(childRoutesHome),
SharedModule
],
declarations: [HomeComponent],
exports: [HomeComponent],
providers: [ ]
})
export class HomeModule {}
home.component.ts
import { Component, OnInit } from '../../../../node_modules/#angular/core';
import { ActivatedRoute } from '../../../../node_modules/#angular/router';
#Component({
templateUrl: 'home.component.html',
styleUrls: [ './home.component.scss' ],
})
export class HomeComponent implements OnInit {
constructor(
private route: ActivatedRoute,
) { }
public ngOnInit(): void {
console.log('working');
// This console.log() doesn't show
};

Uncaught Error: Template parse errors: Angular 4

I have been trying to make a simple app in Angular, I was able to make it work in Plunker. Unfortunately, it gives me this error
Can't bind to 'joke' since it isn't a known property of 'app-root'.
that I don't know how to handle.
What is the problem?
joke.component.ts
import { Component, EventEmitter, Input, Output, OnInit } from '#angular/core';
import { Joke } from '../jokes'
#Component({
selector: 'app-joke',
templateUrl: './joke.component.html',
styleUrls: ['./joke.component.css']
})
export class JokeComponent implements OnInit {
constructor() {}
#Input("joke") joke: Joke;
#Output() jokeDeleted = new EventEmitter<Joke>();
deleteItem() {
this.jokeDeleted.emit(this.joke)
}
ngOnInit() {}
}
joke-form.component.spec
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { JokeFormComponent } from './joke-form.component';
describe('JokeFormComponent', () => {
let component: JokeFormComponent;
let fixture: ComponentFixture<JokeFormComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ JokeFormComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(JokeFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
});
joke-list.component
import { Component, OnInit } from '#angular/core';
import {Joke} from '../jokes';
#Component({
selector: 'app-joke-list',
templateUrl: './joke-list.component.html',
styleUrls: ['./joke-list.component.css']
})
export class JokeListComponent implements OnInit{
jokes: Joke[];
constructor() {
this.jokes = [
new Joke("I am telling a joke.", "Haha, that's funny!"),
new Joke("I am telling an even funnier joke.", "Hahahahaha!!"),
new Joke("I am telling the funniest joke.", "HAHAHAHAHAHA!!!!")
]
}
addJoke(joke) {
this.jokes.unshift(joke);
}
deleteJoke(joke) {
let indexToDelete = this.jokes.indexOf(joke)
if (indexToDelete !== -1) {
this.jokes.splice(indexToDelete, 1);
}
}
ngOnInit() {}
}
app.component
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {}
app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import { AppComponent } from './app.component';
import { JokeFormComponent } from './joke-form/joke-form.component';
import { JokeListComponent } from './joke-list/joke-list.component';
import { JokeComponent } from './joke/joke.component';
#NgModule({
declarations: [
AppComponent,
JokeFormComponent,
JokeListComponent,
JokeComponent,
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
From the code you have posted I see that your AppComponent class is empty :
export class AppComponent {}
Since you haven't posted your html code, I am guessing you are doing something similar to the plunker, where my-app in plunker is equivalent to app-root in your question's code:
<app-root *ngFor="let j of jokes" [joke]="j" (jokeDeleted)="deleteJoke($event)"></app-root>
Once you add #Input("joke") joke: Joke to AppComponent class, it should not throw that error anymore:
export class AppComponent {
#Input("joke") joke: Joke;
#Output() jokeDeleted = new EventEmitter<Joke>();
deleteItem() {
this.jokeDeleted.emit(this.joke)
}
}
You can try to delete this OnInit method that angular generates for us in this child joke.component.ts class that implements this #Input method for Property binding [property]. And also restart the server.

component can't find provider from lazy-loaded module

I have a lazy-loaded module which has one service and one component.
I would like to use the service in that component but I get:
Error: No provider for EventService!
The module
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { EventRoutingModule } from './event-routing.module';
import { FormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import { EventListModule } from './../event-list/event-list.module';
import { ModuleWithProviders } from '#angular/core';
import { EventComponent } from './event.component';
import { EventService } from './event.service';
#NgModule({
imports: [
CommonModule,
FormsModule,
HttpModule,
EventRoutingModule,
EventListModule
],
declarations: [EventComponent]
})
export class EventModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: EventModule,
providers: [EventService]
};
}
}
the component
import { Component, OnInit } from '#angular/core';
import { EventService } from './event.service';
#Component({
templateUrl: './event.component.html',
styleUrls: ['./event.component.scss']
})
export class EventComponent implements OnInit {
private eventService: EventService;
constructor(eventService: EventService) {
this.eventService = eventService;
}
ngOnInit() {
this.eventService.getEvents().subscribe(data => console.log(data), error => console.log(error));
}
}
the service
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { AuthHttp } from 'angular2-jwt';
#Injectable()
export class EventService {
private static readonly URL = 'http://localhost:3000/api/events';
constructor(private authHttp: AuthHttp) { }
public getEvents() {
return this.authHttp.get(EventService.URL);
}
}
I have looked at a couple of posts here but havent been able to get a solution from them.
I know providers in lazy-loaded modules are module-scoped and lazy-loaded modules have their own dependency tree.
But it must be possible to inject the provider into the component, mustn't it?
You need to define how you provide your service.
You can define how it is provided at the module level:
#NgModule({
imports: [
CommonModule,
FormsModule,
HttpModule,
EventRoutingModule,
EventListModule
],
declarations: [EventComponent],
providers: [EventService]
})
export class EventModule { ... }
This means that one EventService instance will be available for the whole module.
Or at the component level:
#Component({
templateUrl: './event.component.html',
styleUrls: ['./event.component.scss'],
providers [EventService]
})
export class EventComponent implements OnInit { ... }
This means that one EventService instance will be available for each component instance. This is due to the hierarchical injectors feature. The component defines its own injector which can hold its own instances that are being made available to its children.
[EventService] is equivalent to [ { provide: EventService, useClass: EventService }]. Which means that the key used to inject the dependency is EventService and the instance is being constructed by using the EventService constructor.

Lazy loaded module create multiples instance of the parent service each time is loaded

Every time I navigate from MainComponent to TestListComponent the TestListComponent constructor is triggered and a new instance of the ObservableServiceis created. When I click the link the console show the duplicated messages. Maybe is an angular issue, any help?
main.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import {MainRoutingModule} from "./main-routing.module";
import {MainComponent} from './main.component';
import {ObservableService} from "../../core/services/observable.service";
#NgModule({
imports: [
BrowserModule,
MainRoutingModule,
],
declarations: [MainComponent],
providers: [ObservableService],
bootstrap: [
MainComponent
]
})
export class MainModule { }
main.routing.module.ts
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
export const routes: Routes = [
{ path: 'tests', loadChildren: 'angular/app/modules/test-list/test-list.module#TestListModule'},
{ path: '**', redirectTo: '' }
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class MainRoutingModule {}
observable.service.ts
import { Injectable } from '#angular/core';
import {Subject} from "rxjs/Rx";
import 'rxjs/add/operator/map'
#Injectable()
export class ObservableService {
// Observable string sources
private changeLanguageStatus = new Subject<Object>();
// Observable string streams
changeLanguageStatus$ = this.changeLanguageStatus.asObservable();
constructor(){}
/**
* Change language event
* #param params
*/
changeLanguageEvent(params: Object){
this.changeLanguageStatus.next(params);
}
}
test-list.module.ts
import { NgModule } from '#angular/core';
import {TestListComponent} from "./test-list.component";
#NgModule({
declarations: [
TestListComponent
]
})
export class TestListModule {}
test-list.component.ts
import {Component} from '#angular/core';
import 'rxjs/Rx';
import {ObservableService} from "../../core/services/observable.service";
#Component({
moduleId: module.id,
selector: 'st-test-list',
templateUrl: 'test-list.component.html'
})
export class TestListComponent {
constructor(private observableService:ObservableService) {
observableService.changeLanguageStatus$.subscribe(
data => {
console.log('Test', data);
});
}
}
main.component.ts
import {Component, ViewChild} from '#angular/core';
import 'rxjs/Rx';
import {ObservableService} from "../../core/services/observable.service";
#Component({
moduleId: module.id,
selector: 'st-main',
templateUrl: 'main.component.html'
})
export class MainComponent {
constructor(private observableService:ObservableService) {}
changeLanguage(lang){
this.observableService.changeLanguageEvent({type: lang});
}
}
main.component.html
<!--Dynamic content-->
<router-outlet></router-outlet>
It should be expected behavior that when you navigate to a component via routing it is created and when you navigate back it is destroyed. As far as I know you are experiencing this issue because you are creating what is called an Infinite Observable i.e. you are subscribing to it and waiting for a stream of events, in your case changing language. Because you never unsubscribe from your Observable, the function subscribed to it is kept alive for each new instance of your component. Therefore, rxjs won't handle disposing of your subscription and you will have to do it yourself.
First off I'd suggest you read about Lifecycle hooks. Check out the OnInit and OnDestroy lifecycle hooks.
Use ngOnInit to subscribe to your Observable and use ngOnDestroy to unsubscribe from it as such:
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
#Component({ .... })
export class TestListComponent implements OnInit, OnDestroy
{
private _languageSubscription : Subscription;
ngOnInit(): void
{
this._languageSubscription = observableService.changeLanguageStatus$.subscribe(
data => {
console.log('Test', data);
});
}
ngOnDestroy() : void
{
this._languageSubscription.unsubscribe();
}
}
I hope this will solve your problem.

How can I get a directive/component instance inside another component?

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 :)
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.
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

Categories

Resources