Subscribe to EventEmitter in Angular2 not working - javascript

I'm learning Angular 2. I'm trying to send data from a component to other on the click of the first one.
Both components are siblings.
This is my code so far:
First Component:
#Component({
selector: 'jsonTextInput',
templateUrl: '../templates/jsonTextInput.html',
directives: [Card, CardTitle, CardDescription, Icon],
providers: [JsonChangeService]
})
export class JsonTextInput {
json: string = '';
constructor (private jsonChangeService: JsonChangeService) {
this.jsonChangeService = jsonChangeService
}
process () {
this.jsonChangeService.jsonChange(this.json)
}
}
This is the service:
import {Injectable, EventEmitter} from '#angular/core';
#Injectable()
export default class JsonChangeService {
public jsonObject: Object;
stateChange: EventEmitter<any> = new EventEmitter<any>();
constructor (){
this.jsonObject = {};
}
jsonChange (obj) {
console.log('sending', obj)
this.jsonObject = obj
this.stateChange.emit(this.jsonObject)
}
}
The call from the first component to the service is working, since the sending is being printed.
This is the second Component
#Component({
selector: 'jsonRendered',
templateUrl: '../templates/jsonrendered.html',
directives: [Card, CardTitle],
providers: [JsonChangeService]
})
export class JsonRendered {
private jsonObject: Object
constructor (private jsonChangeService: JsonChangeService) {
this.jsonChangeService = jsonChangeService
this.jsonObject = jsonChangeService.jsonObject
this.jsonChangeService.stateChange.subscribe(json => { this.jsonObject = json; console.log('Change made!') })
}
ngOnInit () {
console.log(1)
}
ngOnChanges () {
console.log(2)
}
renderJson () {
console.log(3)
}
}
The function inside the subscribe to stateChange never runs. What am I missing?
EDIT
This is the content of my stateChange EventEmitter:
_isAsync: true
_isScalar: false
destination: undefined
dispatching: false
hasCompleted: false
hasErrored: false
isStopped: false
isUnsubscribed: false
observers: Array[0]
source:undefined

You have two different instances of JsonChangeService. That's why you don't receive message between components. You need to have one instance service, i.e. on parent component or on top level like this:
bootstrap(AppComponent, [JsonChangeService])

Related

Child component's EventEmitter "loses" parent component's observer

I have a parent component which observes child component's Output event emitter (topicsChanged).
Parent component:
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnInit,
Output
} from "#angular/core";
import { Language } from "../language";
import { TemplateTopic } from "../template-topic";
#Component({
selector: "at-main-topics",
templateUrl: "./main-topics.component.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MainTopicsComponent implements OnInit {
#Input() templates: string[] = [];
#Input() templateTopics: TemplateTopic[] = [];
#Input() languages: Language[] = [];
#Output() templateTopicChanged = new EventEmitter<TemplateTopic>();
constructor() {}
ngOnInit(): void {}
get availableTemplateTopics(): TemplateTopic[] {
return this.templates
.map(x => +x)
.map(template => {
const existingTopic = this.templateTopics.find(
x => x.template === template
);
return (
existingTopic ||
{ //observer will disappear for this empty created object.
template: template,
topics: []
}
);
});
}
onTopicsChanged(templateTopic: TemplateTopic) {
// This will not be triggered for 3rd template which is created in availableTemplateTopics getter, because it doesn't exist in initial data (templateTopics)
this.templateTopicChanged.emit(templateTopic);
}
}
<at-template-topic *ngFor="let templateTopic of availableTemplateTopics"
[templateTopic]="templateTopic"
[languages]="languages"
(topicsChanged)="onTopicsChanged($event)">
</at-template-topic>
In one strange case, this event emitter loses it's parent component's observer. That is - in child component I am opening a dialog. Before dialog is opened, if I inspect the emitter, the observer is there, but once the dialog is closed, observer is gone.
Child component:
import { Component, EventEmitter, Input, OnInit, Output } from '#angular/core';
import { MatDialog } from '#angular/material/dialog';
import { Language } from '../language';
import { TemplateTopic } from '../template-topic';
import { Topic } from '../topic';
import { TranslationDialogModel } from '../translation-dialog.model';
import { TranslationDialogComponent } from '../translation-dialog/translation-dialog.component';
#Component({
selector: 'at-template-topic',
templateUrl: './template-topic.component.html'
})
export class TemplateTopicComponent implements OnInit {
#Input() templateTopic: TemplateTopic;
#Input() languages: Language[] = [];
#Output() topicsChanged = new EventEmitter<TemplateTopic>();
private dialogTitle: string = 'lorem ipsum'
constructor(
private dialog: MatDialog
) { }
ngOnInit(): void {
}
onCreateTopic(): void {
this.openDialog();
}
onEditTopic(topic: Topic): void {
this.openDialog(topic);
}
private openDialog(topic?: Topic): void {
// this.topicsChanged always has observer at this point
const dialogRef = this.dialog.open(TranslationDialogComponent, {
data: {
pageTitle: this.dialogTitle,
availableLanguages: this.languages,
translations: topic?.translations
} as TranslationDialogModel
});
dialogRef
.beforeClosed()
.subscribe(translations => {
if (translations) {
if (topic) {
topic.translations = translations;
topic.title = translations[0].title;
} else {
this.templateTopic.topics.push({ translations, title: translations[0].title })
}
// When called via onCreateTopic method for a category which was created as an empty placeholder, this.topicsChanged has lost it's observer. However if category had initial data, then observer is still there.
this.topicsChanged.emit(this.templateTopic);
}
})
}
}
There is nothing shady going in the dialog, it simply returns some data and that's it. This is somehow connected to the getter get availableTemplateTopics in parent component from which list of child components are created. In getter there is a list of templates representing each child component which is either populated from already existing data or an empty placeholder is created. And the issue is with the empty placeholder objects.
Snippet:
get availableTemplateTopics(): TemplateTopic[] {
return this.templates
.map(x => +x)
.map(template => {
const existingTopic = this.templateTopics.find(
x => x.template === template
);
return (
existingTopic ||
{ //observer will disappear for this empty created object.
template: template,
topics: []
}
);
});
}
I found that I can solve all of this simply by moving the getter logic one level up, but I would still like to understand this weird behavior. How can observer disappear just like that and how is it connected to the getter?
Stackblitz for full code: https://stackblitz.com/edit/angular-kjewu7?file=src/app/main-topics/main-topics.component.ts

declare variable in angular in a way that all functions have access to it [duplicate]

This question already has answers here:
Angular 4/5/6 Global Variables
(6 answers)
Closed 3 years ago.
im creating the window variable over and over again how can i declare it only once ?
i tried adding it to the constructor but that didn't work.
import { Component } from '#angular/core';
import { ElectronService } from 'ngx-electron';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass']
})
export class AppComponent {
title = 'ae-test';
constructor(
private _ES: ElectronService,
) {}
minWindow() {
const window = this._ES.remote.getCurrentWindow();
window.minimize();
}
fullscreenWindow() {
const window = this._ES.remote.getCurrentWindow()
if (window.isFullScreen() == true) {
window.setFullScreen(false);
} else {
window.setFullScreen(true);
}
}
closeWindow() {
const window = this._ES.remote.getCurrentWindow();
window.minimize();
}
}
Just create a shared singleton service
#Injectable()
export class GlobalService {
private _data = {value:0};
getData(){
return this._data; // get ref of the data object 👀
}
}
notice that every time you ask for data you got the same object so
there is need to create a property in the component body unless you
want to display the object in the template
shared or singleton service is just a service add to AppModule or root module providers list
#NgModule({
...
providers: [GlobalService]
})
export class AppModule { }
if you want to render any data from the data object you need to create a property in the component body a,b to hold a reference of the object.
export class AComponent implements OnInit {
data;
constructor(public _g:GlobalService) { }
ngOnInit() {
this.data = this._g.getData()
}
}
in case you just want to change the data c component
export class CComponent {
data;
constructor(public _g:GlobalService) { }
reset() {
const data = this._g.getData(); // 🌟
data.value = 0;
}
inc(){
const data = this._g.getData(); // 🌟
data.value +=10;
}
}
in the global service getData return a reference to _data object not a new object every time
stackblitz demo
Define a new property in your component and assign it once in your constructor (or even better ngOnInit if you implement the OnInit lifecycle hook):
private window: any;
constructor(private _ES: ElectronService) {
this.window = this._ES.remote.getCurrentWindow();
}
add window variable to component nad set it in the ngOnInit hook:
this.window = this._ES.remote.getCurrentWindow();
just use a global variable
import { Component } from '#angular/core';
import { ElectronService } from 'ngx-electron';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass']
})
export class AppComponent {
title = 'ae-test';
window = null;
constructor(
private _ES: ElectronService,
) {
this.window = this._ES.remote.getCurrentWindow();
}
minWindow() {
this.window.minimize();
}
fullscreenWindow() {
if (this.window.isFullScreen() == true) {
this.window.setFullScreen(false);
} else {
this.window.setFullScreen(true);
}
}
closeWindow() {
this.window.minimize();
}
}
you can initialise window in the ngOnInit function too
You can solve your problem by this answer. So it's a possible repeated question:
Angular Globals variables

How do I call a function in a class from another class in Angular 6?

Here's my code:
import { Component, OnInit, ViewChild } from '#angular/core';
import { AuthService } from '../core/auth.service';
import { MatRadioButton, MatPaginator, MatSort, MatTableDataSource } from '#angular/material';
import { SelectionModel } from '#angular/cdk/collections';
import { OrdersService } from '../orders.service';
export interface DataTableItem {
ordersn: string;
order_status: string;
update_time: number;
}
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
radioValue: number;
dataSource = new UserDataSource(this.orderService);
selection = new SelectionModel<any>(true, []);
// Sorting and pagination
#ViewChild(MatSort) sort: MatSort;
#ViewChild(MatPaginator) paginator: MatPaginator;
// Columns displayed in the table. Columns IDs can be added, removed, or reordered.
displayedColumns = ['ordersn', 'order_status', 'update_time'];
// Filter
applyFilter(filterValue: string) {
this.dataSource.filter = filterValue.trim().toLowerCase();
}
// Whether the number of selected elements matches the total number of rows.
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
// Selects all rows if they are not all selected; otherwise clear selection.
masterToggle() {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
constructor(public auth: AuthService, private orderService: OrdersService) {
}
onSelectionChange(radioSelection: MatRadioButton) {
this.radioValue = radioSelection.value;
console.log(this.radioValue);
}
ngOnInit() {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
}
}
export class UserDataSource extends MatTableDataSource<any> {
constructor(private orderService: OrdersService) {
super();
this.orderService.GetOrdersList().subscribe(d => {
this.data = d.orders;
});
}
radioFilter() {
const array = [];
this.orderService.GetOrdersList().subscribe(d => {
for (const entry of d.orders) {
if (entry.order_status === 'READY_TO_SHIP') {
array.push(entry);
}
}
this.data = array;
console.log(array);
});
}
}
I'm trying to call radioFilter() from HomeComponent. What I've tried:
Implementing #ViewChild in HomeComponent but I would get this error: Class 'UserDataSource' used before its declaration.
Importing UserDataSource and then added to the constructor in HomeComponent. I would get this error: Getting Uncaught Error: Can't resolve all parameters for HomeComponent
I'm kind of out of anymore idea, thus any suggestion is much appreciated. Thanks!
Getting Uncaught Error: Can't resolve all parameters for HomeComponent
First of all your dataSource is not registered in a ngModule as injectable.
So it's not possible to inject it to the constructor in HomeComponent.
I also don't think you want to do that because ngMaterial-dataSources are stateful and injectables shouldn't be stateful.
Class 'UserDataSource' used before its declaration
Your dataSource is not a ViewChild in your component's template. It's just an object (without a html-template). The error you get is that annotations in typeScript are processed on compile/transpile time. But the UserDataSource class is declared below the HomeComponent. You are using it before it's declared. You could just put it above the HomeComponent but better put it in a new file and import it. But that's not the solution.
Possible solution
I don't get why you cannot just call the radioFilter method.
It's a public method of your UserDataSource and there is an instantiated object in HomeComponent called dataSource. Just make sure to not call it in the constructor. Member variables are processed after the constructor is called. But imho you can just call dataSource.radioFilter()

Broadcast data to multiple unrelated components in Angular2 through single service

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");

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