Emitting events globally - javascript

What am trying to achieve is I would like to emit a custom event in angular2 globally and have multiple components listen to it so not just the parent-child pattern
In my event source component, I have
export class EventSourceComponent{
#Output() testev = new EventEmitter();
onbtnclick(){
this.testev.emit("i have emitted an event")
}
}
Now I would like other components to get this event
export class CmpTwoComponent{
//here get the emitted event with data
}
How do I achieve the above?

You could use a shared service for that.
export class EventSourceComponent{
constructor(private sharedService : SharedService){}
onbtnclick(){
this.sharedService.testev.next("i have emitted an event")
}
}
export class CmpTwoComponent{
//here get the emitted event with data
constructor(sharedService : SharedService){
sharedService.testev.subscribe((event)=>{console.log(event)})
}
}
and then the sharedService would be
#Injectable()
export class SharedService{
public testev = new Subject()
}
Obviously, if you still need the Output so the parent component could be able to subscribe normally, you could add that too :
export class EventSourceComponent{
#Output() testev = new EventEmitter();
constructor(private sharedService : SharedService){}
onbtnclick(){
this.testev.emit("i have emitted an event")
this.sharedService.testev.next("i have emitted an event")
}
}

There is no pattern in Angular that allows to achieve what you ask for.
The best option that I can think of for you would be to create a custom service. Make some service that you inject into AppComponent (therefore having a single instance of the service). In the Service you can have whatever logic you want.

Related

Angular - Cannot get parent component data

I'm passing a function as parameter from parent to child component. When click event is occurred, function of parent component trigger, but all the property of parent component is undefined. For example,
Parent Component
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(): Observable<any> {
// here this.notificationService is undefined
console.log( this.notificationService );
}
}
Parent html
<notification-menu [unread]= "unreadNotification"></notification-menu>
child Component
export class NotificationMenuComponent implements OnInit {
#Input() updateUnread: Function;
}
child html
<button type="button" class="icon-button" (click)="updateUnread()">
</button>
Now when I click on notification button, unreadNotification is triggered, but value of this.notificationService in console.log is undefined.
How can I solve this?
You should use #Input() to pass values from parent to child and #Output() to pass values from child to parent.
Child HTML:
<button type="button" class="icon-button" (click)="update()">
</button>
Child Component:
export class NotificationMenuComponent implements OnInit {
#Output() updateUnread = new EventEmitter<string>();
update() {
this.updateUnread.emit("I am working man!");
}
}
Parent HTML:
<notification-menu (updateUnread)= "unreadNotification($event)"></notification-menu>
Parent Component:
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(dataFromChild: string) {
console.log(dataFromChild);
}
}
The answer from #nimeresam is good advice - using an #Output is an idomatic way to achieve this.
It's worth noting though, that the reason that your original solution doesn't work is due to the way that javascript handles the this context.
Writing (click)="updateUnread()" is equivalent to saying this.updateUnread() with this being NotificationMenuComponent - as notificationService does not exist on NotificationMenuComponent you get the undefined error.
To have the context of the parent component used, you would need to bind the context to the updateUnread function before passing it into the child component.
This can be achieved either by converting the function to be an arrow functionn, or using Function.bind
See:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
It's normally a good idea to enable the typescript option for --noImplicitThis to help catch these errors (though unsure if it will detect it in this case)
You can use arrow function so that you can use parent component's information. You can try as like as given below.
updateUnreadNotification = () => {
// by using arrow function you can get notificationService information
console.log( this.notificationService );
}
Hope your problem will be solve by this.

I want to create a custom event which can trigger from any component and get listened to any component within my angular 7 app

I want to create a custom event which can trigger from any component and get listened to any component within my angular 7 app
Suppose I have 1 component in which I have a button on click on which I want to trigger my custom event with some data. Next, there will be another component which will constantly listening for that event when it triggers it will execute some code and update the ui accordingly.
How should I implement it?
Well, well, well, what you're looking for is a Shared Service. This shared service will have a BehaviorSubject that will act as a source for the data. Through this, you will be able to push new data streams. And then you will expose this BehaviorSubject asObservable.
You will then subscribe to this Observable from all the components where you want to listen for data changes and then react accordingly.
This is what this is going to look like in code:
import { Injectable } from '#angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
#Injectable()
export class SharedService {
private data: BehaviorSubject<any> = new BehaviorSubject<any>(null);
data$: Observable<any> = this.data.asObservable();
constructor() { }
setData(newData) {
this.data.next(newData);
}
}
You can now inject the SharedService in any controller you want and call setData from the component that you want to push new data from(see the AppComponent from the Sample StackBlitz for more details). And then you'll also be injecting the SharedService in other components and in there, you'll subscribe to data$ in their ngOnInit(see the HelloComponent from the Sample StackBlitz for details)
Here's a Sample StackBlitz for your ref.

Access element over the router-outlet Angular 6

<side-nav [navTitle]="navTitle"></side-nav>
<router-outlet>
</router-outlet>
I have navigation bar at the root component. I created [navTitle] with #Input Decorator inside the side-nav component. side-nav component is placed in another component(root-component). However I want access [navTitle] and change from component which loaded inside the router-outlet acording to which component is loaded. How do I achieve that?
You can't pass any data to router-outlet as to regular component (at the current version of Angular it's not possible, may be it will be added in the future), so the following syntax is invalid:
<router-outlet [dataToPass]="'something'"></router-outlet>
In provided case, you can use services to share data between your components, and I think, that using observable is the best way, because you will get the updated version of data realtime:
data.service.ts
// Other service stuff
#Injectable()
export class DataService {
private navTitle$: BehaviorSubject<string> = new BehaviorSubject<string>('Default nav title');
public setNavTitle(newNavTitle: string): void {
// Sets new value, every entity, which is subscribed to changes (`getNavTitle().subscribe(...)`) will get new value every time it changes
this.navTitle$.next(newNavTitle);
}
public getNavTitle(): Observable<string> {
// Allow to `subscribe` on changes and get the value every time it changes
return this.navTitle$.asObservable();
}
}
side-nav.component.ts
// Other component stuff
export class SideNavComponent implements OnInit, OnDestroy {
public navTitle: string = '';
private getNavTitleSubscription: Subscription;
constructor(private _dataService: DataService) { }
ngOnInit() {
// Will update the value of `this.navTitle` every time, when you will call `setNavTitle('data')` in data service
this.getNavTitleSubscription = this._dataService.getNavTitle()
.subscribe((navTitle: string) => this.navTitle = navTitle);
}
ngOnDestroy() {
// You have to `unsubscribe()` from subscription on destroy to avoid some kind of errors
this.getNavTitleSubscription.unsubscribe();
}
}
And any component, which is loaded in that router-outlet:
any.component.ts
// Other component stuff
export class SideNavComponent implements OnInit {
private navTitleToSet: string = 'Any title';
constructor(private _dataService: DataService) { }
ngOnInit() {
// Set title from current component
this._dataService.setNavTitle(this.navTitleToSet);
}
}
In such case you don't really need to pass the value from root component to side-nav, because you already have a subscription in side-nav component and you will have access to the latest value. If you need navTitle in both root and side-nav components, you can just move the logic with subscription to root.
And here is the working STACKBLITZ.
You can use a service to communicate between components. I have created a short example which would give you a glimpse of how it can be done.
The service being a singleton, has only one instance and hence the properties remain the same.
Hope it helps.
https://stackblitz.com/edit/angular-paziug?file=src%2Fapp%2Fapp.component.ts

draft state/publish changes on save in angular 2

I'm using angular 4 in my application and currently the user is able to make changes over multiple components(drag and drop, remove items, add items and etc...).
Now, for every user action there is a http requests via the relevant service that persist the changes on the DB.
There is a requirement that the user will be able to make this changes and only persist them once he done changing and pressed save. (it can be 10-50 actions from different types.)
How would you suggest refactoring the code in order to support that ? to make an array of user actions, and on save iterate over the array and make the relevant actions one by one, write some http middleware to hold all http calls until 'save' is pressed?
You should look into redux. This would allow a MVP programming model and works just fine with Angular. ng2-redux
Just so you know the problem you are facing has a name.
"Application State Management"
This can be solved via redux like libraries (redux/ rxjs-store rxjs-effects etc)..
Or you could just use plain rxjs BehaviourSubject or Subject as Observable.
here is a plunker example of using plain rxjs observables and angular services to achieve state management.
https://embed.plnkr.co/dEDJri4TziCS91oZiuHb/
TL;DR
This is the services
#Injectable()
export class AppStateService{
private _dataSaved = new Subject<string>();
public dataSaved$ = this._dataSaved.asObservable()
constructor() {}
dispatchSaveEvent(data: String){
this._dataSaved.next(data);
}
}
This is the component that will dispatch the save event
#Component({
selector: 'my-footer',
template: `
<button (click)="saveData($event)">Save</button>
`
})
export class Footer implements OnInit {
constructor(private appState: AppStateService) {}
ngOnInit() {}
saveData(e){
this.appState.dispatchSaveEvent("Some data to save here...");
}
}
This is how you consume the observable in every component that is interested that a saved has occurred
#Component({
selector: 'my-comp-1',
template: `
<h1>Component-1! {{savedDataRecived}}</h1>
`
})
export class Comp1 implements OnInit {
savedDataRecived = "";
constructor(private appState: AppStateService) {}
ngOnInit() {
this.appState.dataSaved$.subscribe(data=> this.handleSaveEvent(data))
}
handleSaveEvent(data: string){
this.savedDataRecived = data;
}
}

Observable isnt triggering

I have two components that should be connected via Observable().
In the first i am declaring
#Injectable()
export class One{
private changeConfirmed = new Subject<boolean>();
changeConfirmed$ = this.changeConfirmed.asObservable();
public init$: EventEmitter<boolean>;
registerChangeConfirmed(category:boolean){
alert('sending')
this.changeConfirmed.next(category);
}
onMsg(){
this.registerChangeConfirmed(true);
}
}
onMsg is event bound to template
and in the second one
Import { One } from './pat/to/it'
#Component({
providers:[One]
})
export class two{
constructor( private childClass : One ){
this.childClass.changeConfirmed$.subscribe( x => {alert(x)})
}
}
However the event does not get emitted.
But when i emit event in class two instead of class one = i include
this.childClass.registerChangeConfirmed(true)
in class two the event gets triggered. Why isn't it working when i invoke it from class one?
Don't provide the service One on the component. This way each component instance will get its own One instance. If you provide it at a common parent (AppComponent) then they will get a shared instance.
Angular2 DI maintains an instance (singleton) per provider.

Categories

Resources