Angular 9 call function when two components loaded - javascript

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');
}
}

Related

How to pass data from One component to other

I am new in angular working on a project.My problem is that i want to transfer data from one component to other. Actually i want to show data in text field from database and then have to update it. I have one component name ricerca.component.ts in which data in table is showing. now when i click on specific line(row) then data for that specific record i have to show in my other component name as generatecontract.comonent.ts. I don't know how to perform this.
I made a model name ContractDblist assign all these value to that model but unfortunatelly not solved the problem in other component
this is my ricercacomponnet code
if(tag === 'Item1'){
this.router.navigate(['/workflow/rigester' ]);
}
}
public lstContractRecordDbValue :any[];
getContractRecordbyParameter(selecteditem: any, index: number) { this.workFlowService.getContractRecordbyParameter(selecteditem).subscribe(data => {
this.lstContractRecordDbValue = data;
this.contractdblist.cnt_num=this.lstContractRecordDbValue[0].CNT_NUM;
this.contractdblist.contract=this.lstContractRecordDbValue[0].CONTRACT; this.contractdblist.contacttype=this.lstContractRecordDbValue[0].CONTRACT_TYPE; this.contractdblist.contractno=this.lstContractRecordDbValue[0].CONTRACT_NO;
this.loading = false;
}, error => {
console.error('getAllTickets', error);
this.loading = false;
})
}
You can use Subject to do that
import { Injectable } from "#angular/core";
import { Subject } from "rxjs";
#Injectable()
export class MessageService {
private subject = new Subject<any>();
constructor() {}
sendMessage(message: any) {
this.subject.next(message);
}
getData() {
return this.subject.asObservable();
}
}
So you can call MessageService class method sendMessage() to send data
I defined 2 method here. The first method using next() to send message to the next subcriber. So in your component you just need to simply subscribe like this to get the data
private subscription$: Subscription;
public ngOnInit(): void {
this.subscription$ = this.messageervice
.getData()
.subscribe(data => { console.log(data); })
}
public ngOnDestroy(): void {
this.subscription$.unsubscribe();
}
There are many ways to do so some of them are
using input decorator when passing data from parent to child component.
using output decorator with event emitter when passing data from child to parent component.
using subjects for components not related to each other.
using a service to set and get data to be passed.
you can refer their official website for all the above ways.

Angular Injectable service that is dependant on DOM element

I am just grasping Angular (6) framework, and it's quirks and features.
And there is this one thing, that I would need, but as it seems, it goes against Angular paradigms - a service, that needs a reference to DOM node.
More specifically - Esri Map component.
In it's constructor, it requires a DOM node - a place where the map will live.
Since the Map is going to be the center thing in the application, it would be used by multiple components throughout the app. And for that, I would want it in a service. Question is - how would I do that? I made solution to this, but I would like to validate it with more experienced Angular devs.
So I have a MapComponent that would be like a singleton component - included only once in the App.
export class MapComponent implements OnInit {
#ViewChild('map') mapElement:ElementRef;
constructor(
private renderer2: Renderer2,
private mapService: MapService) { }
ngOnInit() {
// This is where I pass my DOM element to the service, to initialize it
this.mapService.init(this.mapElement.nativeElement);
}
}
And my mapService, that I would reference throughout other services
#Injectable()
export class MapService {
private isInit:boolean = false;
private map: __esri.Map = null;
private mapView: __esri.MapView = null;
init(domElement: ElementRef) {
if (this.isInit) return Promise.resolve(); // A guard?
loadModules(["esri/Map", "esri/views/MapView", "esri/widgets/ScaleBar"])
.then(([Map, MapView, ScaleBar]) => {
this.map = new Map();
this.mapView = new MapView({
map: this.map,
container: domElement // This is where I need DOM element
});
})
.catch(err => {
// handle any errors
console.error(err);
})
.then(() => {
this.isInit = true;
});
}
}
This does work, I just wonder, would this be a correct way to do it.
I require these map objects to be accessible through other components, to add/remove/change map layers, draw/render geometries and other map things.

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.

Transactional model update in Angular 2

I previously worked with React/Mobx with the action concept. This allows to change some model properties in one transaction without firing multiple events to update UI state (only one event will be triggered after an action method will be executed).
Is there any approach or may be patterns to achieve the same behavior in Angular 2?
I'm using a service like this to control UI:
#Injectable()
export class UIService {
private buffer: any = {};
private dispatcher: Subject<any> = new Subject();
constructor() {
this.dispatcher
.asObservable()
.map(state => this.buffer = { ...this.buffer, ...state })
.debounceTime(50)
.subscribe(() => { /* do something */));
}
set(key: string, value?: any) {
this.dispatcher.next({ [key]: value });
}
}
and in different components in ngOnInit() I set different options:
this.uiService.set('footer', false); // in base component
this.uiService.set('footer', true); // in extended component
this.uiService.set('sidebar', true); // in other component
this.uiService.set('title', 'My Page'); // elsewhere...
This way I have only one object that reflects my current UI state...
Note that MobX can be used with Angular 2 as well: https://github.com/500tech/ng2-mobx

Categories

Resources