Angular 2: Event not firing - javascript

I'm trying to fire an event when my component gets the logged user from a service because I would like to show the navigation bar only when a user is logged in.
Here's my app.component code:
import { Component, OnInit, EventEmitter, Input, Output } from '#angular/core';
import { ROUTER_DIRECTIVES, Router } from '#angular/router';
import { LoginComponent } from './login.component';
import { DashboardComponent } from './dashboard.component';
import { ControlDetailComponent } from './control-detail.component';
import { PageNotFoundComponent } from './pagenotfound.component';
import { SettingsComponent } from './settings.component';
import { UserService } from '../service/user.service';
import { User } from '../object/user';
#Component({
selector: 'myApp',
templateUrl: './app/template/app.component.html',
styleUrls: ['styles.css'],
directives: [ROUTER_DIRECTIVES],
precompile: [LoginComponent, DashboardComponent, ControlDetailComponent, PageNotFoundComponent, SettingsComponent]
})
export class AppComponent {
#Input() loggedUser:User;
#Output() userChanged:EventEmitter<User> = new EventEmitter<User>();
constructor(private _userService:UserService, private _router:Router) {}
ngAfterViewInit() {
if(this._userService.isUserLogged()) {
this.userChanged.emit(this._userService.loggedUser);
}
}
switchUser(event) {
this.loggedUser = event.target.value;
console.log("event triggered");
}
}
And this is its associated template:
<div class="all-content">
<nav (userChanged)="switchUser($event)" *ngIf="loggedUser">
<ul>
<a [routerLink]="['/dashboard']" routerLinkActive="active">Dashboard</a>
<a [routerLink]="['/settings']" routerLinkActive="active">Settings</a>
<a (click)="logout()">Logout</a>
</ul>
</nav>
Someone knows why my event is not fired?
Initially I thought the problem was the *ngIf directive that was preventing the firing of the event but, even removing that, the event is not fired anyway...

Events emitted by #Output()s can only be subscribed to on the parent component or a directive applied on the element. The AppComponent doesn't have a parent component, therefore this approach won't work.
In your case it's better to use a shared service with observables.
https://angular.io/docs/ts/latest/cookbook/component-communication.html

Solved using Observables and a Service, as Günter Zöchbauer suggested (thanks!).
Below my solution:
user.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import { Subject } from 'rxjs/Subject';
import { Control } from '../object/control';
import { User } from '../object/user';
#Injectable()
export class UserService {
private userChangedSource = new Subject<User>();
userChanged = this.userChangedSource.asObservable();
loggedUser:User;
userLoggedIn(user:User) {
this.loggedUser = user;
this.userChangedSource.next(user);
}
...
}
app.component.ts
import { Component, OnInit, EventEmitter, Input, Output } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { ROUTER_DIRECTIVES, Router } from '#angular/router';
import { LoginComponent } from './login.component';
import { DashboardComponent } from './dashboard.component';
import { ControlDetailComponent } from './control-detail.component';
import { PageNotFoundComponent } from './pagenotfound.component';
import { SettingsComponent } from './settings.component';
import { UserService } from '../service/user.service';
import { User } from '../object/user';
#Component({
selector: 'myApp',
templateUrl: './app/template/app.component.html',
styleUrls: ['styles.css'],
directives: [ROUTER_DIRECTIVES],
precompile: [LoginComponent, DashboardComponent, ControlDetailComponent, PageNotFoundComponent, SettingsComponent]
})
export class AppComponent implements OnInit {
loggedUser:User;
constructor(private _userService:UserService, private _router:Router) {
this._userService.userChanged.subscribe(user => {
this.loggedUser = user;
console.log("event triggered");
});
}
...
}
app.component.html
<div class="all-content">
<nav *ngIf="loggedUser" >
<ul>
<a [routerLink]="['/dashboard']" routerLinkActive="active">Dashboard</a>
<a [routerLink]="['/settings']" routerLinkActive="active">Settings</a>
<a (click)="logout()">Logout</a>
</ul>
</nav>
<router-outlet></router-outlet>
<br/>
</div>

Related

Angular routing - On routing to a child, View updates only on refresh of the page

I am learning routing from the example on angular docs.
The problem is that on clicking an element from crisis list, crisis detail isnt displayed right after click.
If i refresh my screen, then relevant crisis detail is displayed.
So the question is why is the detail visible after refreshing? Whats the solution?
//crisis-center-routing-module.ts
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { CrisisCenterHomeComponent } from './crisis-center-home/crisis-center-home.component';
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisCenterComponent } from './crisis-center/crisis-center.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';
const crisisCenterRoutes: Routes = [
{
path: 'crisis-center',
component: CrisisCenterComponent,
children: [
{
path: '',
component: CrisisListComponent,
children: [
{
path: ':id',
component: CrisisDetailComponent
},
{
path: '',
component: CrisisCenterHomeComponent
}
]
}
]
}
];
#NgModule({
imports: [
RouterModule.forChild(crisisCenterRoutes)
],
exports: [
RouterModule
]
})
export class CrisisCenterRoutingModule { }
//crisis-list.component.html
<h2>CRISES</h2>
<ul class="crises">
<li *ngFor="let crisis of crises$ | async" [class.selected]="crisis.id === selectedId">
<a [routerLink]="[crisis.id]">
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
</a>
</li>
</ul>
<router-outlet></router-outlet>
//crisis-list-component.ts
import { CrisisService } from '../crisis.service';
import { Crisis } from '../crisis';
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-crisis-list',
templateUrl: './crisis-list.component.html',
styleUrls: ['./crisis-list.component.css']
})
export class CrisisListComponent implements OnInit {
selectedCrisis: Crisis;
crises: Crisis[];
crises$;
selectedId: number;
constructor(private crisisService: CrisisService, private service: CrisisService, private route: ActivatedRoute) { }
ngOnInit() {
this.crises$ = this.route.paramMap.pipe(
switchMap(params => {
this.selectedId = +params.get('id');
return this.service.getCrises();
})
);
}
onSelect(crisis: Crisis): void {
this.selectedCrisis = crisis;
}
getCrises(): void {
this.crisisService.getCrises()
.subscribe((crises) => {
this.crises = crises;
});
}
}
//crisis-detail.component.html
<button (click)='gotoCrises(crisis)'>Back</button>
<div *ngIf="crisis">
<h2>{{crisis.name | uppercase}} Details</h2>
<div><span>id: </span>{{crisis.id}}</div>
<div>
<label>name:
<input [(ngModel)]="crisis.name" placeholder="name" />
</label>
</div>
</div>
//crisis-detail.component.ts
import { Observable } from 'rxjs';
import { CrisisService } from '../crisis.service';
import { Component, OnInit, Input } from '#angular/core';
import { Crisis } from '../crisis';
import { Router, ActivatedRoute, ParamMap } from '#angular/router';
import { switchMap } from 'rxjs/operators';
#Component({
selector: 'app-crisis-detail',
templateUrl: './crisis-detail.component.html',
styleUrls: ['./crisis-detail.component.css']
})
export class CrisisDetailComponent implements OnInit {
crisis: Crisis;
private crisis$;
constructor(private route: ActivatedRoute, private router: Router, private service: CrisisService) { }
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
this.crisis$ = this.service.getCrisis(id);
this.crisis$.subscribe((crisis) => {
this.crisis = crisis;
});
}
gotoCrises(crisis: Crisis) {
const crisisId = crisis ? crisis.id : null;
this.router.navigate(['/crisises', { id: crisisId, foo: 'foo' }]);
}
}
The problem was that i was getting id from the url using
activatedRoute.snapshot.paramMap.get('id)
The router-outlet renders the route once. For other clicks in the list, the detail view isnt updated.
In order to constantly listen to changes in id in the url, I had to subscribe to
ActivatedRoute.url
This solution was helpful https://stackoverflow.com/a/47030238/2416260

function share in header and sidebar component angular 6

I need to sidebar component function in sidebar.
my header component
import { Component, OnInit, Input,ViewChild } from '#angular/core';
import { SidebarComponent } from '../sidebar/sidebar.component';
#ViewChild(SidebarComponent) SidebarComponent;
ngOnInit() {
this.SidebarComponent.testFunction();
}
sidebar component
testFunction() {
console.log('value');
}
I added an essential code block for the understanding purpose. when I use the above code error said,
ERROR TypeError: Cannot read property 'testFunction' of undefined
at HeaderComponent.push../src/app/layout/components/header/header.component.ts.HeaderComponent.ngOnInit (header.component.ts:57)
can u help me to fix this issue?
call it after child view is initialized.
#ViewChild(SidebarComponent) sidebarComponent: SidebarComponent;
ngAfterViewInit() {
this.sidebarComponent.testFunction();
}
For sharing of functions in Angular, it is better to use a service and call it's functions in both of the components.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class SharedService {
constructor() { }
sharedFunction(){
console.log('here');
}
}
And in both of components, component1:
import { SharedService } from '../shared.service';
import { Component, OnInit } from '#angular/core';
#Component({
selector: '.....',
templateUrl: '.......',
styleUrls: ['........']
})
export class Component1 implements OnInit{
constructor(private sharedService: SharedService) { }
ngOnInit() {
this.sharedService.sharedFunction();
}
}
component2:
import { SharedService } from '../shared.service';
import { Component, OnInit } from '#angular/core';
#Component({
selector: '.....',
templateUrl: '.......',
styleUrls: ['........']
})
export class Component2 implements OnInit{
constructor(private sharedService: SharedService) { }
ngOnInit() {
this.sharedService.sharedFunction();
}
}
Could be done using a service. For illustration purpose let's call it HeaderAndSidebarService:
Filename: header-and-sidebar.service.ts
#import { Injectable } from "#angular/core";
#Injectable()
export class HeaderAndSideBarService {
public testFunction() {
console.log('value');
}
}
To use the service, provide it within both header and sidebar component:
import { Component, OnInit, Input,ViewChild } from '#angular/core';
import { HeaderAndSideBarService } from "./header-and-sidebar.service";
#Component({
...,
providers: [HeaderAndSideBarService]
})
export class HeaderComponent {
constructor(private service: HeaderAndSideBarService ) { }
ngOnInit() {
this.service.testFunction();
}
}

Angular 7: problem with include Component

i have this component, already registered in the app.module.ts:
import { Component } from '#angular/core';
import { MessageService } from 'primeng/api';
#Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.css'],
providers: [MessageService]
})
export class ModalComponent {
constructor(
private messageService: MessageService
) { }
onConfirm() {
}
showError(error) {
}
}
but i cannot include this component in another component.
i get this error:
ERROR Error: Uncaught (in promise): Error: StaticInjectorError(AppModule)[TopbarComponent -> ModalComponent]:
StaticInjectorError(Platform: core)[TopbarComponent -> ModalComponent]:
this is my TopbarComponent:
import { Component } from '#angular/core';
import { SessionStorageService } from 'angular-web-storage';
import { Router } from "#angular/router"
import { AppComponent } from '../app.component';
import { ModalComponent } from '../modal/modal.component';
import { HomeComponent } from '../home/home.component';
import { JhttpService } from '../jhttp.service';
#Component({
selector: 'app-topbar',
templateUrl: './topbar.component.html'
})
export class TopbarComponent {
user = this.session.get('USER');
constructor(
private jhttpService: JhttpService,
private session: SessionStorageService,
private router: Router,
private glob: AppComponent,
private modal: ModalComponent,
private app: HomeComponent
) { }
onLogoutButtonClick(event) {
this.modal.showError('CIAO');
event.preventDefault();
}
}
any suggestion??
Remove components from the constructor injection.
import { Component } from '#angular/core';
import { SessionStorageService } from 'angular-web-storage';
import { Router } from "#angular/router"
import { AppComponent } from '../app.component';
import { ModalComponent } from '../modal/modal.component';
import { HomeComponent } from '../home/home.component';
import { JhttpService } from '../jhttp.service';
#Component({
selector: 'app-topbar',
templateUrl: './topbar.component.html'
})
export class TopbarComponent {
user = this.session.get('USER');
constructor(
private jhttpService: JhttpService,
private session: SessionStorageService,
private router: Router
) { }
onLogoutButtonClick(event) {
this.modal.showError('CIAO');
event.preventDefault();
}
}
Do not add Components in the constructor.
Also make sure that the other ModalComponent needs to be part of the module where it's being used. Either import the module of 'ModalComponent' in the module where it is used or add ModalComponent to the declaration array of the module where you are using it.

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