Angular: Event Emitter not received - javascript

I have a service with an EventEmitter that gets fired if the data changes.
#Output() reservationChangedEvent = new EventEmitter<any>();
public notifyReservationsChanged() {
this.reservationChangedEvent.emit({});
}
That the data changes is triggered by a modal that is started from a controller.
In my controller I subscribe for those events:
ngOnInit() {
...
this.reservationService.reservationChangedEvent.subscribe(() => this.reloadData());
}
My problem is that I can not receive events in my overview component. If I subscribe for events (for checking) in my service or my modal I do receive them.
Any idea why the overview controller can not receive events?

You should change:
#Output() reservationChangedEvent = new EventEmitter<any>();
to:
reservationChangedSubject = new Subject<any>();
reservationChangedEvent = this.reservationChangedSubject.asObservable()
and this:
public notifyReservationsChanged() {
this.reservationChangedEvent.emit({});
}
to:
public notifyReservationsChanged() {
this.reservationChangedSubject.next({});
}

#Output() and EventEmitter are meant to be used only in components, not in services.
For services, you should use Subject instead.
Your service should contain:
private reservationChangedSource = new Subject<any>();
reservationChanged$ = this.reservationChangedSource.asObservable();
notifyReservationsChanged() {
this.reservationChangedEvent.next({});
}
In your component:
reservationChangeSubscription: Subscription;
ngOnInit() {
...
this.reservationChangeSubscription = this.reservationService.reservationChanged$
.subscribe(() => this.reloadData());
}
ngOnDestroy() {
this.reservationChangeSubscription.unsubscribe();
}

Related

Typescript control flow behavior

I am new to JS, TS and Angular...
So I have this angular component:
export class AdminProductsMenuComponent implements OnInit{
constructor(private productService: ProductService,
private alertService: AlertService,
private router: Router) {
this.subscribeToDeleteProductEvents();
}
productsAdminModel: IGetProductAdminModel[] = [];
private productId: string;
ngOnInit(): void {
this.executeGetAllProductsAsAdmin();
}
executeGetAllProductsAsAdmin() {
this.productService.getAllProductsAsAdmin().subscribe({
next: (productData) => this.productsAdminModel = productData
})
}
private subscribeToDeleteProductEvents() {
this.alertService.getSubjectAlertEvent().subscribe({
next: (isConfirmed) => {
if (isConfirmed) {
this.productService.deleteProduct(this.productId).subscribe({
next: () => {
this.reloadCurrentResources();
}
});
}
}
});
}
private reloadCurrentResources(): void {
// save current route first
this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
this.router.navigate(['/AdminProducts']); // navigate to same route
});
}
executeProductDelete(id: string) {
this.productId = id;
this.alertService.confirmationAlertProductDelete();
}
}
Brief explanation:
I have subscription in the constructor which listens for events during the lifetime of the component.
An event is fired when the last method is called (through the template) which prompts a SweetAlert confirm dialog. Depending on the selected the event is true or false.
Now here is the tricky part - if I move the executeProductDelete() method above reloadCurrentResources() and subscribeToDeleteProductEvents() and invoke it (executeProductDelete) it will complete the action and throw error
I have a feeling that it executes again the subscribeToDeleteProductEvents() and reloadCurrentResources() .
If I move the executeDeleteProduct() as the last method, no error occurs.
Why is this behavior? I have a feeling that they continue to run synchronously. They are not invoked anywhere else.
There seems to be 2 main problems there:
Avoid at all costs "reloading" the same component, try to abstract the reload logic into methods. This could cause weird issues and unecessary loads, as the SPA is meant to be a single page application.
Since you are problably re-instancianting the component over and over again through your reloadResources, the alert service behaviour subjects creates new subscriptions. And since you haven't unsubscribed from them, they will keep listening forever.

Angular 9 call function when two components loaded

I am trying to have two components, <app-map> and <app-markers-list>.
<app-map> loads Google Maps API and displays a map on the page. It emits a mapLoaded event in ngAfterViewInit()
#Output() mapLoaded: EventEmitter<boolean> = new EventEmitter();
<app-markers-list> loads a list of markers via Angular's HttpClient. It emits a markersLoaded event at the end of HttpClient.get().subscribe() Observable.
#Output() markersLoaded: EventEmitter<boolean> = new EventEmitter();
How do I catch these two events at once so I can call another component's function that will populate the map with the markers?
I think there is a simple solution.
#Output()
allLoaded = new EventEmitter();
oneLoaded = false;
ngAfterViewInit() {
this.emitAllLoaded();
}
yourFunctionWhereHttpClientGetLocated() {
this.http.get(..).subscribe(() => {
this.emitAllLoaded();
});
}
emitAllLoaded() {
if (oneLoaded) this.allLoaded.emit();
oneLoaded = true;
}
You might don't need to use Observable or Subject
I am assuming you are trying to capture events from <app-map> and <app-markers-list> in a parent component.
You can capture as we generally do, put binding in parent template as below:
<app-map (mapLoaded)="mapLoaded($event)"></app-map>
<app-markers-list (markersLoaded)="markersLoaded($event)"></app-markers-list>
Now you can manage these events in parent component. You can use subjects or observables and observable operators (combineLatest, of). Please find the code below how you can use it.
import { combineLatest, of } from 'rxjs';
// this will be your parent component.
export class ParentComponent {
mapLoaded = new Subject()
mapLoaded$ = this.mapLoaded.asObservable();
markerListLoaded = new Subject()
markerListLoaded$ = this.markerListLoaded.asObservable();
constructor() {
const combinedValues = combineLatest(mapLoaded$, markerListLoaded$);
combinedValues.subscribe((value) => {
// Here you can write code when you receive notification from both the events.
})
}
mapLoaded(mapLoadedData) {
this.mapLoaded$.next('map loaded successfully');
}
markersLoaded(markersLoadedData) {
this.markerListLoaded$.next('markers loaded successfully');
}
}

Angular 4 codemirror performance issue (detaching zone)

I've run into big performance problem when I tried to implement codemirror, firstly I used ng2-codemirror but it's killed my application which has only list of scripts and codemirror component. I found out that creating instance (by instance i mean CodeMirror.fromTextArea(...)) should be run outside angular so I copied component from ng2-codemirror (code attached below) to my project and wrap init method with NgZone.runOutsideAngular, it helps but just a bit. So I've started profiling my angular project with Javascript profiler in chrome debugger and vanilla js codemirror with the same code to edit. My small research shows me that without zone.js codemirror is executing same function with about 8% of my CPU resources however with zone.js in angular project it takes 48%. So I've started to completly detach zone from codemirror component, So I tried with ChangeDetectionRef.detach() but the zone still is attached to codemirror (checked with JS profiler again).
Summary:
runOutsideAngular does not help to detach codemirror source code from zone, same is for detaching changeDetector for codemirror component
Code for my modified codemirror component
export class MpCodemirrorComponent implements AfterViewInit, OnDestroy {
#Input() config;
#Output() change = new EventEmitter();
#Output() focus = new EventEmitter();
#Output() blur = new EventEmitter();
#Output() cursorActivity = new EventEmitter();
#ViewChild('host') host;
#Output() instance = null;
_value = '';
private changeSubject: Subject<any> = new Subject<any>();
constructor(
private ngZone: NgZone,
private changeDetector: ChangeDetectorRef
) {
this.changeDetector.detach();
}
get value() { return this._value; }
#Input() set value(v) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
ngOnDestroy() {
}
ngAfterViewInit() {
this.changeDetector.detach();
this.config = this.config || {};
this.codemirrorInit(this.config);
this.changeSubject
.debounceTime(500)
.distinctUntilChanged()
.subscribe(change => this.updateValue(change));
}
codemirrorInit(config) {
this.ngZone.runOutsideAngular(() => {
this.instance = CodeMirror.fromTextArea(this.host.nativeElement, config);
this.instance.setValue(this._value);
this.instance.on('change', () => {
this.changeSubject.next(this.instance.getValue());
// this.updateValue(this.instance.getValue());
});
this.instance.on('focus', (instance, event) => {
this.focus.emit({instance, event});
});
this.instance.on('cursorActivity', (instance) => {
this.cursorActivity.emit({instance});
});
this.instance.on('blur', (instance, event) => {
this.blur.emit({instance, event});
});
})
}
updateValue(value) {
this.value = value;
this.onTouched();
this.change.emit(value);
}
writeValue(value) {
this._value = value || '';
if (this.instance) {
this.instance.setValue(this._value);
}
}
onChange(_) {}
onTouched() {}
registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn) { this.onTouched = fn; }
}
I've run out of ideas how to completly detach zone for 3rd party library, any help is appreciated.
P.S. Don't mind doubled changeDetector.detach().

Angular service not updating subscribed components

I have an Angular 2/4 service which uses observables to communicate with other components.
Service:
let EVENTS = [
{
event: 'foo',
timestamp: 1512205360
},
{
event: 'bar',
timestamp: 1511208360
}
];
#Injectable()
export class EventsService {
subject = new BehaviorSubject<any>(EVENTS);
getEvents(): Observable<any> {
return this.subject.asObservable();
}
deleteEvent(deletedEvent) {
EVENTS = EVENTS.filter((event) => event.timestamp != deletedEvent.timestamp);
this.subject.next(EVENTS);
}
search(searchTerm) {
const newEvents = EVENTS.filter((obj) => obj.event.includes(searchTerm));
this.subject.next(newEvents);
}
}
My home component is able to subscribe to this service and correctly updates when an event is deleted:
export class HomeComponent {
events;
subscription: Subscription;
constructor(private eventsService: EventsService) {
this.subscription = this.eventsService.getEvents().subscribe(events => this.events = events);
}
deleteEvent = (event) => {
this.eventsService.deleteEvent(event);
}
}
I also have a root component which displays a search form. When the form is submitted it calls the service, which performs the search and calls this.subject.next with the result (see above). However, these results are not reflected in the home component. Where am I going wrong? For full code please see plnkr.co/edit/V5AndArFWy7erX2WIL7N.
If you provide a service multiple times, you will get multiple instances and this doesn't work for communication, because the sender and receiver are not using the same instance.
To get a single instance for your whole application provide the service in AppModule and nowhere else.
Plunker example
Make sure your Component is loaded through or using its selector. I made a separate component and forgot to load it in the application.

How to get items in asynchronous environment in different components?

I have main component with this code(without imports):
class AppComponent {
products = null;
productsUpdated = new EventEmitter();
constructor(product_service: ProductService) {
this._product_service = product_service;
this._product_service.getList()
.then((products) => {
this.products = products;
this.productsUpdated.emit(products)
});
}
}
With template:
<left-sidenav [productsReceived]="productsUpdated"></left-sidenav>
And component for sorting products:
class LeftSidenavComponent {
#Input() productsReceived;
#Output() productsSorted = new EventEmitter();
categories = []
constructor(product_list_service: ProductListService) {
this._product_list_service = product_list_service;
}
ngOnInit() {
this.productsReceived.subscribe((products) => {
this.categories = products.map((elem) => {
return elem.category
})
});
}
}
So when all is drawn, categories array in LeftSidenavComponent is empty.
I think that productUpdated event emitter fires earlier than LeftSidenavComponent subscribes to it, but don't know how to handle that.
I would recommend moving the EventEmitter's to the service you have injected like
#Injectable()
export class DrinksService {
drinkSelected = new EventEmitter<any>();
drinkChanged = new EventEmitter<any>();
drinksToggle = new EventEmitter<any>();
}
The above code is an example from one of my projects, but just change the variable names.
This way rather then relying on the HTML template to modify productsReceived, you simply subscribe to the eventEmitters in ngOnInit.
Currently, your code is using databinding to an event emitter productsUpdated but you could simply databind [productsReceived]="productsUpdated" where productsUpdated is an empty list. Once productsUpdated is populated with values, it will be reflected in the DOM. You have to populate productsUpdated by subscribing to an event emitter like...
this.myEmitter
.subscribe(
(data)=>this.productsUpdated = data
);
Does this help? The main thing is to databind to a list, and not an event emitter.

Categories

Resources