Angular 8 bind ngrx state to component - javascript

I am new to Angular and I want to bind some Classnames in my HTML to some values in my states. At first I had only one state, which was only some boolean value and everything worked fine. Now I changed the state to an object, so that I can save more information in it. Although I am using more or less the same approach as before but the classname doesn't change. The state does change. Can someone please tell me what my mistake is and if there is another more appropriate way to do the things I am trying to do please tell me that also.
The angular version is 8.2.14.
This is my code:
reducer.ts:
export type ShoppinCartState = {
CartIsOpen: boolean;
};
export type HamburgerState = {
MenuIsOpen: boolean;
};
export function hamburgerReducer(
state: HamburgerState = { MenuIsOpen: false },
action: Action
) {
switch (action.type) {
case "HAMBURGER.TOGGLE":
console.log("HAMBURGER.Toggle called " + state.MenuIsOpen);
return {
...state,
MenuIsOpen: !state.MenuIsOpen
};
case "HAMBURGER.CLOSE":
return {
...state,
MenuIsOpen: false
};
case "HAMBURGER.OPEN":
return {
...state,
MenuIsOpen: true
};
default:
return state;
}
}
export function shoppingCartReducer(
state: ShoppinCartState = { CartIsOpen: false },
action: Action
) {
switch (action.type) {
case "CART.TOGGLE":
console.log("Tooggle cart called " + state.CartIsOpen);
return {
...state,
CartIsOpen: !state.CartIsOpen
};
default:
return state;
}
}
This is my Component. When the user clicks on the Hamburger Icon one action is dispatched which changes the state. There is also another part which is binded to the state. When the value is true, the classname should be "visible".
<app-hamburger-icon (click)="onHamburgerIconClick()"></app-hamburger-icon>
<div id="Logo"><a (click)="onLinkClick()" routerLink="/">E99-EsAns</a></div>
<ul [ngClass]="{ visible: hamburgerClicked$ | async }">
And that's the component.ts file
import { Store } from "#ngrx/store";
import { Observable } from "rxjs";
import { HamburgerState } from "src/app/reducer";
#Component({
selector: "app-navigationbar",
templateUrl: "./navigationbar.component.html",
styleUrls: ["./navigationbar.component.css"]
})
export class NavigationbarComponent implements OnInit {
hamburgerClicked$: Observable<boolean>;
constructor(private store: Store<HamburgerState>) {
this.hamburgerClicked$ = this.store.select("MenuIsOpen");
}
ngOnInit() {}
onHamburgerIconClick() {
console.log(this.hamburgerClicked$);
this.store.dispatch({ type: "HAMBURGER.TOGGLE" });
}
onLinkClick() {
this.store.dispatch({ type: "HAMBURGER.CLOSE" });
}
onShoppingCartIconClicked() {
this.store.dispatch({ type: "CART.TOGGLE" });
}
}
Some snippet of my app.module.ts
import { hamburgerReducer, shoppingCartReducer } from "./reducer";
...
...
...
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
HttpClientModule,
StoreModule.forRoot({
hamburgerReducer: hamburgerReducer,
shoppingCartReducer: shoppingCartReducer
})
],

When you inject the Store into a component you operate on the structure of the global state. So the select has to 'select' into this structure to access the correct object.
Only the reducer functions get handed a slice of this global state. We can see that most likely in app.module.ts where you define your store:
StoreModule.forRoot({
hamburgerReducer: hamburgerReducer,
shoppingCartReducer: shoppingCartReducer
})
Best practice is to create a interface State that describes your store structure:
export interface State {
hamburgerReducer: HamburgerState;
shoppingCartReducer: ShoppingCartState;
}
and then use this interface when injecting the store:
constructor(private store: Store<State>)
then you can select the hamburger menu-state via:
this.store.select(state => state.hamburgerReducer.MenuIsOpen)

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

NgRX Entity : ids are undefined in the State

I've been trying #ngrx/entity in a dummy "Todo" project, with a single AppModule, a single reducer and a single component. However, I am having issues trying it out.
My actions are pretty straight forward, just some CRUD operations :
import { Action } from '#ngrx/store';
import { Todo } from '../../models/todo';
export const CREATE = '[Todo] Create'
export const UPDATE = '[Todo] Update'
export const DELETE = '[Todo] Delete'
export class Create implements Action {
readonly type = CREATE;
constructor(public todo: Todo) { }
}
export class Update implements Action {
readonly type = UPDATE;
constructor(
public id: string,
public changes: Partial<Todo>,
) { }
}
export class Delete implements Action {
readonly type = DELETE;
constructor(public id: string) { }
}
export type TodoActions
= Create
| Update
| Delete;
Then my reducer file contains everything I need to handle my entity :
import * as actions from './todo.actions';
import { EntityState, createEntityAdapter } from '#ngrx/entity';
import { createFeatureSelector } from '#ngrx/store';
import { Todo } from '../../models/todo';
export interface TodosState extends EntityState<Todo> {}
export const todoAdapter = createEntityAdapter<Todo>();
export const initialState: TodosState = todoAdapter.getInitialState();
export function todoReducer(state: TodosState = initialState, action: actions.TodoActions) {
console.log("Got new action", action);
switch(action.type) {
case actions.CREATE:
return todoAdapter.addOne(action.todo, state);
case actions.UPDATE:
return todoAdapter.updateOne({
id: action.id,
changes: action.changes
}, state);
case actions.DELETE:
return todoAdapter.removeOne(action.id, state);
default:
return state;
}
}
export const {
selectIds,
selectEntities,
selectAll,
selectTotal
} = todoAdapter.getSelectors();
In my app.module.ts file, I am doing the following :
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { StoreModule } from '#ngrx/store';
import { StoreDevtoolsModule } from '#ngrx/store-devtools';
import { AppComponent } from './app.component';
import { todoReducer } from './reducers/todo.reducer';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
StoreModule.forRoot({
todo: todoReducer
}),
StoreDevtoolsModule.instrument({maxAge: 25}),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Finally, in my app.component.ts, I am simply trying to create two TODOs :
import { Component, OnInit } from '#angular/core';
import { Store } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
import * as fromTodo from './reducers/todo.reducer';
import { Todo } from '../models/todo';
import { Create } from './reducers/todo.actions';
#Component({
selector: 'app-root',
template: `
`,
styles: []
})
export class AppComponent implements OnInit {
public todos: Observable<Todo[]>;
constructor(private store: Store<fromTodo.TodosState>) {
this.store.dispatch(new Create({
title: "Test todo",
content: "This is a test todo",
date: new Date()
}))
this.store.dispatch(new Create({
title: "Test todo 2",
content: "This is another todo",
date: new Date()
}))
}
ngOnInit() {
this.todos = this.store.select(fromTodo.selectAll);
}
}
However, after running this, I inspected the Redux DevTools. I saw that it only creates the first TODO, and its id is "undefined".
My console.log in my reducer displays #ngrx/store/init, as well as both [TODO] Create actions
Moreover, if I try to ngFor | async through my todos in my component, I get various errors depending on what I try ("Cannot read 'map' property of undefined" mainly).
After some research, I noticed that #ngrx/entity uses the id property of the model you use.
In my case, my Todo model did not have any id property, so #ngrx/entity could not handle my entities.
I thought it generated ids internally, but apparently it doesn't.
So the fix to this issue is to add an id property to the model, and auto-generate it each time you add an item to the state.
There is a Angular2 UUID module for example.
In my case, I am using ngrx with AngularFire2, which has a createId() method : const id = this.afs.createId(). Then I can add it to the item I want to add, and then store it in my Firestore database.

Jasmine error while testing redux dispatch

I'm throwing a coin in the ocean after spending a couple hours trying to fix an ngRedux testing error.
Here is the component itself, as you can see, it's calling the ModuleActions:
#Component
class TestedClass {
constructor(private actions: ModuleActions) {}
restoreDefault() {
this.action.reloadPageModule(true);
}
}
Here is the module action class, I'm using an if to dispatch the action or not, because I also call it from an epic:
#Injectable()
export class ModuleActions {
constructor(private ngRedux: NgRedux<PageModules>) {}
...
reloadPageModule(dispatch?: boolean) {
if(dispatch) {
this.ngRedux.dispatch({
type: ModuleActions.RELOAD_PAGE_MODULES,
meta: { status: 'success' }
});
} else {
return {
type: ModuleActions.RELOAD_PAGE_MODULES,
meta: { status: 'success' }
};
}
}
}
And this is my testedClass component test:
it('should fire the reload page module action', () => {
const executeActionSpy = spyOn(component['action'], 'loadSucceeded');
component.restoreDefault();
expect(executeActionSpy).toHaveBeenCalled()
})
...
const compileAndCreate = () => {
TestBed.configureTestingModule({
declarations: [
...
],
providers: [
ModuleActions,
NgRedux
]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(EditorComponent);
component = fixture.componentInstance;
})
...
This is giving me a this.ngRedux.dispatch() is not a function error when called from the
In order to fix it, I modified my test to use an import of NgReduxTestingModule instead of a providers of NgRedux:
import { NgReduxTestingModule } from '#angular-redux/store/testing';
TestBed.configureTestingModule({
imports: [NgReduxTestingModule, ...],
providers: [
...

How can I effectively reset a state using #ngrx/store?

I seem to have gotten stuck on this matter for the last couple of days.
We're working on an Angular 2 application, and I need to create a wizard for users to fill out a form.
I've successfully managed to make the data flow through each step of the wizard, and save it in order to freely move back and forth. However, I can't seem to be able to reset it once the form is submitted.
I should add that each component is behind a wall. Maybe a better solution would be a singleton service injected directly at the AppModule. But I can't seem to make it work.
Here's my code so far:
Step 1
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { EventOption } from '../../../events/shared/event-option.model';
import { Store } from '#ngrx/store';
import { NewEventService } from '../shared/new-event.service';
import { Event } from '../../../events/shared/event.model';
import { FriendService } from '../../../friends/shared/friend.service';
#Component({
selector: 'app-upload-images',
templateUrl: './upload-images.component.html',
styleUrls: ['../../../events/new-event/new-event.component.css']
})
export class UploadImagesComponent implements OnInit {
form: FormGroup;
private event;
private images = [];
constructor(
private _store: Store<any>,
private formBuilder: FormBuilder,
private router: Router,
private newEventService: NewEventService,
private friendService: FriendService
) {
_store.select('newEvent').subscribe(newEvent => {
this.event = newEvent;
})
}
ngOnInit() {
this.initForm(this.event);
if (this.event.counter === 0) {
let friends = this.friendService.getFriends('58aaf6304fabf427e0acc08d');
for (let friend in friends) {
this.event.userIds.push(friends[friend]['id']);
}
}
}
initForm(event: Event) {
this.images.push({ imageUrl: 'test0', voteCount: 0 });
this.images.push({ imageUrl: 'test1', voteCount: 0 });
this.images.push({ imageUrl: 'test2', voteCount: 0 });
this.images.push({ imageUrl: 'test3', voteCount: 0 });
this.form = this.formBuilder.group({
firstImage: [this.event.length > 0 ? this.event.eventOption[0].imageUrl : null],
secondImage: [this.event.length > 0 ? this.event.eventOption[1].imageUrl : null],
thirdImage: [this.event.length > 0 ? this.event.eventOption[2].imageUrl : null],
fourthImage: [this.event.length > 0 ? this.event.eventOption[3].imageUrl : null],
})
}
next() {
this.event.eventOptions = this.images;
this.newEventService.updateEvent(this.event);
this.router.navigate(['events/new-event/choose-friends']);
}
}
Step 2
import { Component, OnInit, Input } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { EventOption } from '../../../events/shared/event-option.model';
import { Store } from '#ngrx/store';
import { Event } from '../../shared/event.model';
import { NewEventService } from '../shared/new-event.service';
import { FriendService } from '../../../friends/shared/friend.service';
import { SearchPipe } from '../../../core/search.pipe';
#Component({
selector: 'app-choose-friends',
templateUrl: './choose-friends.component.html',
styleUrls: ['../../../events/new-event/new-event.component.css', './choose-friends.component.css']
})
export class ChooseFriendsComponent implements OnInit {
private searchTerm = '';
private event;
private friends = [];
private friendsError = false;
constructor(
private _store: Store<any>,
private formBuilder: FormBuilder,
private router: Router,
private newEventService: NewEventService,
private friendService: FriendService
) {
_store.select('newEvent').subscribe(newEvent => {
this.event = newEvent;
})
}
ngOnInit() {
this.friends = this.friendService.getFriends('58aaf6304fabf427e0acc08d');
}
selectedFriend(friendId: string) {
return this.friendService.selectedFriend(friendId, this.event.userIds);
}
toggleFriend(friendId: string) {
return this.friendService.toggleFriend(friendId, this.event.userIds);
}
toggleAllFriends() {
return this.friendService.toggleAllFriends(this.friends, this.event.userIds);
}
submit() {
if (this.event.userIds.length > 0) {
this.newEventService.resetEvent();
this.router.navigate(['events/vote-events']);
} else {
this.friendsError = true;
}
}
back() {
this.newEventService.updateEvent(this.event);
this.router.navigate(['events/new-event/upload-images']);
}
}
Event Service
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Store, Action } from '#ngrx/store';
import { Event } from '../../../events/shared/event.model';
import { EventOption } from '../../../events/shared/event-option.model';
import { newEvent, newEventModel } from './new-event.reducer';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/find';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class NewEventService {
public newEvent$: Observable<newEventModel>;
constructor(private store: Store<newEventModel>) {
this.newEvent$ = this.store.select('newEvent');
}
getEvent(event) {
return this.store.dispatch({
type: 'GET_EVENT',
payload: event
})
}
updateEvent(event) {
return this.store.dispatch({
type: 'UPDATE_EVENT',
payload: event
})
}
resetEvent() {
return this.store.dispatch({
type: 'RESET_EVENT',
})
}
}
Event Reducer
import { EventOption } from '../../shared/event-option.model';
import { EventType } from '../../shared/event-type.model';
import { ActionReducer, Action } from '#ngrx/store';
import { Event } from '../../shared/event.model';
import { FriendService } from '../../../friends/shared/friend.service';
export interface newEventModel {
eventOptions: EventOption[];
eventTypeId: number,
duration: number,
comment: string,
privacyId: number,
isGlobal: boolean,
id: string,
userIds: string[],
counter: number
}
let blankState: newEventModel = {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
}
let initialState: newEventModel = {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
}
export const newEvent: ActionReducer<newEventModel> = (state: newEventModel = initialState, action: Action) => {
// return new state
switch (action.type) {
case 'GET_EVENT':
return state;
case 'UPDATE_EVENT':
action.payload.counter = action.payload.counter + 1;
return action.payload;
case 'RESET_EVENT':
return Object.assign({}, state, {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
});
default:
return state;
}
}
I could provide a working plunkr if needed, but I need to create it first.
TLDR: How can I reset the state on #ngrx/store?
Thanks for any help provided!
Noy Levi had the right thinking in her answer to this question, which assigns initialState back into state, however, there is a way to assign initialState for each reducer automatically.
The key concept to understand is that if the value of 'state' passed into a reducer is 'undefined' (not 'null', it needs to be 'undefined') then the reducer will automatically assign into 'state' the initialState provided to the reducer when it was created. Because of this default behavior, you can create a 'metareducer' that recognizes an action, say 'logout', and then passes a state of 'undefined' into all the subsequent reducers called.
This behavior is described well in this article about redux, this article about NgRx, and also in this answer about NgRx.
The relevant code would look like this:
export function logoutClearState(reducer) {
return function (state, action) {
if (action.type === ActionTypes.LOGOUT) {
state = undefined;
}
return reducer(state, action);
};
}
#NgModule({
imports: [
StoreModule.forRoot(reducers, { metaReducers: [logoutClearState] }),
],
declarations: [],
providers: [],
})
You can reset the state to initialState in your reducer by using Object.assign to copy all properties of initialState to a new object.
export const newEvent: ActionReducer<newEventModel> = (state: newEventModel = initialState, action: Action) => {
// return new state
switch (action.type) {
// ...
case 'RESET_EVENT':
return Object.assign({}, initialState);
// ...
}
}
A note on the reducer
The reducer should be a pure function, so should not modify the arguments. Your UPDATE_EVENT requires a little tweak:
case 'UPDATE_EVENT':
let counter = { counter: action.payload.counter + 1 };
return Object.assign({}, action.payload, counter);
The pattern to follow is Object.assign({}, source1, source2, ...) where source1, source2 etc contain properties to be assigned. Properties in source1 are overwritten by duplicate properties in source2 etc.
there is much easier way, you just need to set the initialState instead of state:
const reducer = createReducer(initialState,
on(proofActions.cleanAdditionalInsuredState, (state, action) => ({
...initialState
})),
I'm assuming your RESET_EVENT is suppose to return a fresh object. Though you are filling in the object with your state data and another object:
case 'RESET_EVENT':
return Object.assign({}, state, {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
});
The syntax for Object.assign is Object.assign(target, ...sources) and your providing two items as sources: state and the object containing eventOptions, eventTypeId, etc.
Instead you'll want to return Object.assign({}, initialState);
sorry, I took a day off in order to study for some exams. I ended up "solving" it by doing the following:
....
case 'RESET_EVENT':
action.payload.eventOptions = blankState.eventOptions;
action.payload.eventTypeId = blankState.eventTypeId;
action.payload.duration = blankState.duration;
action.payload.comment = blankState.comment;
action.payload.privacyId = blankState.privacyId;
....
return action.payload;
....
It might not be the prettiest or best solution, but at least it works. Thanks for all the help #iblamefish and everyone.

Subscribe to EventEmitter in Angular2 not working

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])

Categories

Resources