Broadcast data to multiple unrelated components in Angular2 through single service - javascript

I have two unrelated components which uses a single #injectable service.
When specific service method is called. It emits an even. Second components has subscribed that event.
my code is as follow:
Component Bot2:
import { Component, OnInit, NgZone, EventEmitter} from '#angular/core';
import { SpeechEngine} from '../../services/common/Speechengine';
#Component({
selector: 'bot2',
template: '<h3>bot2</h3> <input type="button" value="Emmit" (click)="testEmmiter()"/>',
providers: [],
})
export class Bot2 {
_speechEngine: any
constructor(private zone: NgZone) {
this._speechEngine = new SpeechEngine(this.zone);
this._speechEngine.BotActivation$.subscribe(obj => { this.AutoBotActivation(obj.speechActive) })
}
testEmmiter() {
this._speechEngine.testEmiter();
}
AutoBotActivation(speechActive) {
alert(" Bot2 subscribed function called.")
}
}
#Component({
selector: 'bot3',
template: '<h3>bot3</h3> <input type="button" value="Emmit" (click)="testEmmiter()"/>',
providers: [],
})
export class Bot3 {
_speechEngine: any
constructor(private zone: NgZone) {
this._speechEngine = new SpeechEngine(this.zone);
this._speechEngine.BotActivation$.subscribe(obj => { this.AutoBotActivation(obj.speechActive) })
}
testEmmiter() {
this._speechEngine.testEmiter();
}
AutoBotActivation(speechActive) {
alert(" Bot3 subscribed function called.")
}
}
Service:
#Injectable()
export class SpeechEngine{
BotActivation$: EventEmitter<any> = new EventEmitter<any>()
constructor(private zone: NgZone) { }
testEmiter() {
this.BotActivation$.next({
speechActive: false
});
}
}
my service is added at appModule Providers.
providers: [SpeechEngine]
Problem is when i invoke testEmmiter() from bot2, service emits function and subscription in bot2 catches it. but bot3 is not catching. and voice versa with bot3.
if i am using emitter wrongly, Can someone suggest how to get this functionality. my components are not in any (parent/child) relation. one can be sibling of grand grand grand parent of other.

First create a global publish subscribe Service
import { Injectable, EventEmitter } from '#angular/core';
#Injectable()
export class PubSubService {
emitter: EventEmitter<any> = new EventEmitter();
constructor( ) { }
subscribe(callback) {
return this.emitter.subscribe(callback);
}
publish(event, data?) {
let payload = {
type: event,
data: data
};
this.emitter.emit(payload);
}
}
Subscribe in your component(any component)
constructor( private pubSubService: PubSubService) { }
ngOnInit() {
this.pubSubService.subscribe(event => {
if (event.type === 'event_1') {
// do something
} else if (event.type === 'event_2') {
// do something
}
});
}
Publish event from service/component
this.pubSubService.publish('event_1', "what ever message");

Related

In Angular 9, how do I update a component's data field to show in the DOM without re-instantiating it?

I'm fairly new to Angular 9. I have a program where a user enters in a name - which, upon submitting - a POST HTTP request is sent and the name is stored. I then have an unrelated component for a sub-header that lists the names that have been stored using a GET HTTP request using ngOnInit(). However, I need the sub-header to update that list of names dynamically each time a new list is entered rather than just whenever the component instantiates.
I'm unsure how to proceed. I'm sure I could simply add a button that fetches and updates said list, but trying for something more dynamic. Thanks in advance!
//SERVICE.TS...
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { NewList } from './new-list.model';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class ListService {
createdLists: NewList[] = [];
constructor(private http: HttpClient) { }
createList(postData) {
return this.http
.post(
'API_KEY',
postData
);
}
getLists() {
return this.http
.get<NewList>(
'API_KEY'
).pipe(map(responseData => {
const responseArray: NewList[] = [];
for (const key in responseData) {
responseArray.push(responseData[key])
}
return responseArray;
})
);
}
}
// NEW-LIST-MENU.TS (USER ENTERS A NAME)...
import { Component, OnInit } from '#angular/core';
import { NgForm } from '#angular/forms';
import { Router } from '#angular/router';
import { ListService } from 'src/app/shared/list.service';
import { NewList } from 'src/app/shared/new-list.model';
import { UIService } from 'src/app/shared/ui.service';
#Component({
selector: 'app-new-list-menu',
templateUrl: './new-list-menu.component.html',
styleUrls: ['./new-list-menu.component.css']
})
export class NewListMenuComponent implements OnInit {
constructor(private listService: ListService,
private uiService: UIService,
private router: Router) { }
ngOnInit(): void {
}
onSubmit(form: NgForm) {
const listName = form.value.listname;
const newListObj = new NewList(listName, []);
this.listService.createList(newListObj)
.subscribe(() => {
this.router.navigate(['']);
});
const lists = this.listService.updateLists(newListObj);
form.reset();
}
onCancel() {
this.router.navigate(['']);
}
}
// SUB-HEADER.TS...
import { Component, OnInit, Output } from '#angular/core';
import { Router } from '#angular/router';
import { ListService } from 'src/app/shared/list.service';
import { NewList } from 'src/app/shared/new-list.model';
import { faWindowClose } from '#fortawesome/free-solid-svg-icons';
import { faPlusCircle } from '#fortawesome/free-solid-svg-icons';
import { faList } from '#fortawesome/free-solid-svg-icons';
import { faSignOutAlt } from '#fortawesome/free-solid-svg-icons';
import { Subject } from 'rxjs';
#Component({
selector: 'app-sub-header',
templateUrl: './sub-header.component.html',
styleUrls: ['./sub-header.component.css']
})
export class SubHeaderComponent implements OnInit {
createdLists: NewList[];
faWindowClose = faWindowClose;
faPlusCircle = faPlusCircle;
faList = faList;
faSignOutAlt = faSignOutAlt;
#Output() closeSub = new Subject();
constructor(private listService: ListService,
private router: Router) { }
ngOnInit(): void {
this.listService.getLists().subscribe((responseData) => {
this.createdLists = responseData;
});
}
onCloseSelect() {
this.closeSub.next();
}
onNewListSelect() {
this.onCloseSelect();
this.router.navigate(['new-list-menu']);
}
onLogOutSelect() {
}
}```
You can accomplish this in many ways, as these components are not related to each other, you can introduce a state service and use observables. see below possible solution
Create a new state service ListStateService
export class ListStateService {
private listData = new BehaviorSubject<NewList >({} as NewList);
listData$ = this.listData .asObservable();
}
Inject ListStateService into NewListMenuComponent
In the onSubmit, after you update,
const lists = this.listService.updateLists(newListObj);
this.listData .next(lists );
Inject ListStateService into SubHeaderComponent
In the ngOnInit(), subscribe to the ListStateService.listData$ and here you will get the value on changes
In your service, use an event emitter (very useful):
import { EventEmitter } from "#angular/core";
#Output() myEvent: EventEmitter<any> = new EventEmitter();
then emit new data to your sub header component through your service like so:
emitEvent (newData: Array<string>) {
this.myEvent.emit({
data: newData,
});
}
Subscribe to new data in your sub header component ngOnInit and use it:
this.myService.myEvent.subscribe((newData: Array<string>) => {
console.log(JSON.stringify(newData.data));
});
Note: Subscriptions will cause memory leaks if constantly re-subscribed in the component, so you can save the subscription and call unsubscribe() on it in the ngOnDestroy callback.
It's a little unclear what you are trying to do, but if you are trying to pass data from a parent component to a child component, you can do this either with Input fields or a ViewChild
to use Input fields your parent might looks like this:
<app-sub-header [names]="names"></app-sub-header>
then use an "Input" field in the child. Updating names in the parent should update the same named variable in the child in real time.

Angular - communication from child-component to parent

I don't get i, how to communicate between components and services.. :(
I have read and tried a lot about even if some examples somehow work, I do not understand why (?)
what I want to achieve:
I have one parent and two child-components:
dashboard
toolbar
graph
in the toolbar-component I have a searchfield, which gets it's result from a external source (works via service).. when the result arrives, I need to trigger the updateGraph()-Method in the graph-component
toolbar.component.ts
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
import { FormControl } from '#angular/forms';
import { WebsocketsService } from '../../../services/websockets/websockets.service';
import { DataService } from '../../../services/data/data.service';
#Component({
selector: 'toolbar',
templateUrl: './toolbar.component.html',
styleUrls: ['./toolbar.component.scss'],
providers: [WebsocketsService, DataService]
})
export class ToolbarComponent implements OnInit {
#Output() newGraphData: EventEmitter<boolean> = new EventEmitter();
searchField: FormControl;
search: string;
private isNewGraph = false;
constructor(private _websocketsService: WebsocketsService, private _dataService: DataService) {
}
ngOnInit() {
this.searchField = new FormControl();
this.searchField.valueChanges
.subscribe(term => {
this.search = term;
});
}
private applySearch() {
const res = this._websocketsService.sendQuery(this.search);
this._dataService.setGraphData(res);
this.newGraphData.emit(true);
this.search = '';
this.searchField.reset();
}
}
graph-component.ts
import { Component, OnInit} from '#angular/core';
import { HttpService } from '../../../services/http/http.service';
import { DataService } from '../../../services/data/data.service';
#Component({
selector: 'graph',
templateUrl: './graph.component.html',
styleUrls: ['./graph.component.scss'],
providers: [HttpService, DataService]
})
export class GraphComponent implements OnInit, AfterViewInit {
constructor( private _httpService: HttpService, private _dataService: DataService ) {
}
ngOnInit() {
}
public renderResult() {
console.log( this._dataService.getGraphData() );
}
}
data.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class DataService {
private graphData: Subject<string> = new Subject<string>();
public setGraphData(data) {
this.graphData.next( data );
}
public getGraphData() {
return this.graphData;
}
constructor() { }
}
I simply want ´renderResult()´to be executed after the searchresult has been written to ´graphData´. please help i am confused.
If I understand, you want communication between components and service.
A[component] (make a information) -----(notification)-----> B[service] ----(send)----> C[component] (consume the information)
It's correct? Let's go.
You need create a subscription of graphData(data.service.ts) in GraphComponent.
import { Subscription } from 'rxjs/Subscription';
export class GraphComponent implements OnInit, AfterViewInit {
constructor( private _httpService: HttpService, private _dataService: DataService ) {
}
private subscription: Subscription;
ngOnInit() {
this.subscription = this._dataService.getGraphData().asObservable().subscribe((data) => {
console.log(data);
});
}
}
Look here to help you.
http://jasonwatmore.com/post/2016/12/01/angular-2-communicating-between-components-with-observable-subject
Short answer, I think you need to subscribe to the getGraphData subject, something like this (NOT RECOMMENDED):
public renderResult() {
this._dataService.getGraphData().subscribe(d => {
console.log(d)
});
}
It is not recommended as per the lead of RxJS says: https://medium.com/#benlesh/on-the-subject-of-subjects-in-rxjs-2b08b7198b93
Better answer, create an observable in your service and subscribe to that instead.
data.service.ts
graphObservable = this.graphData.asObservable();
graph-component.ts
public renderResult() {
this._dataService.graphObservable().subscribe(d => {
console.log(d)
});
}

Cannot access variable outside the subscription

I have an component in angular. I get data from the API and I instantiate a new object to create a map. But I can't access a variable outside the subscribe function. I also can't access my method.
maps.service.ts
This part, get data form api
import { Injectable } from '#angular/core';
import {HttpClient} from '#angular/common/http';
#Injectable()
export class MapsService {
constructor(private http: HttpClient) { }
getMap(params) {
console.log('Service', params);
return this.http.get('/api/posts/' + params.id);
}
}
map.component.ts
Here, where I build the map with google in the future
import { Component, OnInit } from '#angular/core';
import {MapsService} from '../maps.service';
import {ActivatedRoute} from '#angular/router';
import {MapPath} from '../map-path';
#Component({
selector: 'app-maps',
templateUrl: './maps.component.html',
styleUrls: ['./maps.component.css']
})
export class MapsComponent implements OnInit {
results: any;
params: {};
test: any;
constructor(
private mapsService: MapsService,
private route: ActivatedRoute,
) { }
ngOnInit() {
this.route.params.subscribe( params => {
this.params = params;
});
this.mapsService.getMap(this.params).subscribe(
data => {
this.results = data;
this.test = new MapPath(data, 'test');
},
err => {
console.log('Error occured', err);
});
console.log('this.test', this.test.getX());
console.log('this.results', this.results);
}
}
map-path.ts
Here get the different properties from geoJSON
export class MapPath {
test: string;
constructor(path: any, test: string) {
this.test = test;
console.log('Path', path);
console.log('test', test);
}
getX() {
return this.test;
}
}
Thanks.
Your problem is that Observables are async functions, meaning your subscription callback will happen later than the console.log calls following your this.mapsService.getMap call.
This is guaranteed by async nature of Observables.
You can either move your console logs inside of the subscribe function or create another subscription.
Hope this helps.

(Angular2) JSON data (http.get()) is undefined, and data is not updated in the component

My http-data.service accepts json for output in the component template. Initially, the console shows that the first few calls are given undefined, and the following calls are already taking json, but also if you check the component, then the component shows that the method that outputs the data to the component is called only once and since the data has not yet arrived it writes undefined , But not updated after the arrival of json. Help please understand why? Thank you
My http-data.service:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
import {Response} from '#angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class HttpService{
constructor(private http: Http) {}
getDataOrganizations(): Observable<any[]>{
return this.http.get('http://localhost:3010/data')
.map((resp:Response)=>{
let dataOrganizations = resp.json().organization;
return dataOrganizations;
});
}
getDataModules(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataModules = resp.json().modules;
return dataModules;
});
}
getDataPresets(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataPresets = resp.json().presets;
return dataPresets;
});
}
getDataModuleItems(): Observable<any[]> {
return this.http.get('http://localhost:3010/data')
.map((resp: Response)=> {
let dataModuleItems = resp.json().module_items;
return dataModuleItems;
});
}
}
My data-all.service
import { Injectable, EventEmitter } from '#angular/core';
import {Response} from '#angular/http';
import { ModuleModel } from './model-module';
import { ModuleItemsModel } from './model-module-items';
import data from '../data/data-all';
import { PriceService } from './price.service';
import { HttpService } from './http-data.service';
#Injectable()
export class ModuleDataService {
constructor(private priceService: PriceService, private httpService: HttpService){
this.dataMinMaxSum = {minSum: 0, maxSum: 0}
}
private currentPopupView: EventEmitter<any> = new EventEmitter<any>();
private dataModules: ModuleModel[] = this.getDataModules();
private dataMinMaxSum: {};
private dataCalculateVariationOrg: any[];
private dataChangeExecutor: any[];
subscribe(generatorOrNext?: any, error?: any, complete?: any) {
this.currentPopupView.subscribe(generatorOrNext, error, complete);
}
calculte(){
return this.priceService.getDataPrice();
}
getDataModules(){
this.httpService.getDataModules().subscribe(((modules)=>{this.dataModules = modules; console.log(this.dataModules);}));
console.log('dataModules');
console.log(this.dataModules);
return this.dataModules;
}
---------------------------------------------------------------------------
}
My left-block.component
import { Component, OnInit} from '#angular/core';
import { ModuleDataService } from '../../service/data-all.service';
import { ModuleModel } from '../../service/model-module';
#Component({
moduleId: module.id,
selector: 'modules-left-block',
templateUrl: './modules-left-block.html',
styleUrls: ['modules-left-block.css']
})
export class ModuleLeft implements OnInit{
modules: ModuleModel[];
constructor(private modulesAll: ModuleDataService){}
ngOnInit(){
this.modules = this.modulesAll.getDataModules();
console.log("view");
console.log(this.modulesAll.getDataModules());
}
onToggle(module: any){
this.modulesAll.toggleModules(module);
}
}
My left-block.component.html
<div class="modules-all">
<div class="modules-all-title">Все модули</div>
<div class="module-item" *ngFor="let module of modules" [ngClass]="{ 'active': module.completed }" (click)="onToggle(module)">{{module?.title}}</div>
</div>
In the component this.modulesAll.getDataModules () method is why it is executed only once without updating (write in console => undefined), if there are any thoughts, write, thanks.
This behaviour is due to the .subscribe() method does not wait for the data to arrive and I'm guessing you already know this. The problem you're facing is because, you have .subscribe to the getDataModules() service in the wron place. You shouldn't subscribe to a service in another service (at leat in this case). Move the subscribe method to the left-block.component and it should work.
getDataModules() {
this.httpService.getDataModules().subscribe(((modules) => {
this.dataModules = modules;
console.log(this.dataModules);
}));
console.log('dataModules');
console.log(this.dataModules);
return this.dataModules;
}
It should look somethig like this:
#Component({
moduleId: module.id,
selector: 'modules-left-block',
templateUrl: './modules-left-block.html',
styleUrls: ['modules-left-block.css']
})
export class ModuleLeft implements OnInit {
modules: ModuleModel[] = new ModuleModel();
constructor(private modulesAll: ModuleDataService, private httpService: HttpService) {}
ngOnInit() {
this.getDataModles();
//this.modules = this.modulesAll.getDataModules();
console.log("view");
//console.log(this.modulesAll.getDataModules());
}
onToggle(module: any) {
this.modulesAll.toggleModules(module);
}
getDataModules(): void {
this.httpService.getDataModules().subscribe(((modules) => {
this.modules = modules;
console.log(this.dataModules);
}));
}
}

Angular2: Send data from one component to other and act with the data

I'm learning Angular2. In order to that, I have 2 components, on the click of one of the components, the other component should be notified and act with that.
This is my code so far:
export class JsonTextInput {
#Output() renderNewJson: EventEmitter<Object> = new EventEmitter()
json: string = '';
process () {
this.renderNewJson.next(this.json)
}
}
The process function is being called on the click on the first component.
On the second component I have this code:
export class JsonRendered {
#Input() jsonObject: Object
ngOnChanges () {
console.log(1)
console.log(this.jsonObject)
}
}
The ngOnChanges is never runned, I dont get how to pass the info from one component to other
EDIT
There is an app component which is parent of those 2 components. None of both is parent of the other
This is how my clasess look now:
export class JsonRendered {
private jsonObject: Object
constructor (private jsonChangeService: JsonChangeService) {
this.jsonChangeService = jsonChangeService
this.jsonObject = jsonChangeService.jsonObject
jsonChangeService.stateChange.subscribe(json => { this.jsonObject = json; console.log('Change made!') })
}
}
export class JsonTextInput {
json: string = '';
constructor (private jsonChangeService: JsonChangeService) {
this.jsonChangeService = jsonChangeService
}
process () {
this.jsonChangeService.jsonChange(this.json)
}
}
And the service
import {Injectable, EventEmitter} from '#angular/core';
#Injectable()
export default class JsonChangeService {
public jsonObject: Object;
stateChange: EventEmitter<Object> = new EventEmitter<Object>();
constructor(){
this.jsonObject = {};
}
jsonChange(obj) {
console.log('sending', obj)
this.jsonObject = obj
this.stateChange.next(this.jsonObject)
}
}
Create a service like so...
import {Injectable, EventEmitter} from 'angular2/core';
#Injectable()
export class MyService {
private searchParams: string[];
stateChange: EventEmitter<any> = new EventEmitter<any>();
constructor(){
this.searchParams = [{}];
}
change(value) {
this.searchParams = value;
this.stateChange.next(this.searchParams);
}
}
Then in your component...
import {Component} from 'angular2/core';
import {MyService} from './myService';
#Component({
selector: 'my-directive',
pipes: [keyValueFilterPipe],
templateUrl: "./src/someTemplate.html",
providers: [MyService]
})
export class MyDirective {
public searchParams: string[];
constructor(private myService: MyService) {
this.myService = myService;
myService.stateChange.subscribe(value => { this.searchParams = value; console.log('Change made!') })
}
change(){
this.myService.change(this.searchParams);
}
}
You have to subscribe to the eventemitter, then update your variable. The change event in the service would get fired of from something like...
(click)="change()"

Categories

Resources