What is the proper way of emitting an event through a service. I have read that declaring the EventEmmiter in the service it's not suitable.
I want to achieve the following.
I have 2 components inside the root component, when I click in the first component, I want to know that the first component was clicked in the second component.
There are four possible scenarios in which you can share your data but it depends upon your requirements
Parent to Child: Sharing Data via Input
Child to Parent: Sharing Data via ViewChild
Child to Parent: Sharing Data via Output() and EventEmitter
Unrelated Components: Sharing Data with a Service
When passing data between components that lack a direct connection, such as siblings, grandchildren, etc, you should you a shared service. When you have data that should aways been in sync, I find the RxJS BehaviorSubject very useful in this situation.
You can also use a regular RxJS Subject for sharing data via the service, but here’s why I prefer a BehaviorSubject.
It will always return the current value on subscription - there is no need to call onnext
It has a getValue() function to extract the last value as raw data.
It ensures that the component always receives the most recent data.
In the service, we create a private BehaviorSubject that will hold the current value of the message. We define a currentMessage variable handle this data stream as an observable that will be used by the components. Lastly, we create function that calls next on the BehaviorSubject to change its value.
The parent, child, and sibling components all receive the same treatment. We inject the DataService in the constructor, then subscribe to the currentMessage observable and set its value equal to the message variable.
Now if we create a function in any one of these components that changes the value of the message. when this function is executed the new data it’s automatically broadcast to all other components.
Here its a code snippet.
data.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class DataService {
private messageSource = new BehaviorSubject('default message');
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
parent.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-parent',
template: `
{{message}}
`,
styleUrls: ['./sibling.component.css']
})
export class ParentComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
}
second.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-sibling',
template: `
{{message}}
<button (click)="newMessage()">New Message</button>
`,
styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
newMessage() {
this.data.changeMessage("Hello from Sibling")
}
}
I used Replaysubject to notify about changes in data that needed to be updated to the gui in gui-component when the data provided by the service changes.
A service makes a ReplaySubject which holds the data that may change.
All the gui-components / other services subscribe to this object in their init function and get later notified about changes in data. => Do what they need to do.
In my case the service has a polling interval and the data held by it may change without any user actions.
Related
This app isn't complicated. I'm trying to create a simple store (not keen to use Vuex for something this light) which should coordinate server requests and make sure there's a single source of truth across the app.
store.js
import Vue from "vue"
import axios from "axios"
class Store {
items = []
constructor() {
this.fetchData()
}
fetchData() {
axios
.get("/api/items")
.then(response => this.fillFieldsFromServer(response.data))
}
fillFieldsFromServer(data) {
// NONE OF THESE WORK
// 1: this.items = data
// 2: this.items = this.items.concat(data)
// 3: Array.prototype.push.apply(this.items, data)
}
}
export const itemStore = Vue.observable(new Store())
component.vue
<template>
<ul>
<li v-for="item in items">{{ item }}</li>
</ul>
</template>
<script>
import { itemStore } from "../../stores/item-store.js"
export default {
computed: {
items() {
return itemStore.items
},
},
}
</script>
Obviously I'm fundamentally misunderstanding something here.
What I thought would happen:
The store singleton is created
A server request is fired off
Vue makes the store singleton reactive
The component renders with an empty list
The component watches store.items
The server request returns
The store updates items
The component sees that changes
The component re-renders with the server data
But what's actually happening is that step (8) doesn't occur. The server request returns fine, but the component doesn't see the change so it doesn't re-render.
Obviously I'm doing something wrong. But what?
Vue.observable makes an object reactive by recursively replacing existing properties with get/set accessors, this allows to detect when they are changed. As for arrays, Array.prototype methods that mutate existing array are also replaced to track their calls.
This isn't supposed to work because Array.prototype.push.apply !== store.items.push:
Array.prototype.push.apply(this.items, data)
It should be either:
fillFieldsFromServer(data) {
this.items = data;
}
Or:
fillFieldsFromServer(data) {
this.items.push(...data);
}
Here is a demo.
Hello I'm trying to find a way to send some boolean value from component A to component B without been nested between each other, without props, just send data, one-way binding.
export default {
data: function {
return {
value1: false,
value2:true
}
}
}
Introducing global state with Vuex is probably the best way to go about this.
Without introducing something new into the system, you can handle this with an event bus. Introducing side channel stuff like this does add complexity to your app but is sometimes necessary.
Then in your components you use them like this
// eventBus.js
import Vue from 'vue';
export const EventBus = new Vue();
// To setup your component to listen and update based on new value
import { EventBus } from './eventBus';
mounted() {
EventBus.$on('newValue', (val) => this.something = val);
}
// To send out a new value
EventBus.$emit('newValue', 5);
I have a property selectedOption on the state of my Aurelia Store, which can be changed via actions. I want to observe/subscribe to any changes to this property on the state. My problem is the subscription within the BindingEngine doesn't work because every time you change the state, you create a new copy of the state, therefore the subscription no longer works.
Here is my example:
import { Disposable, BindingEngine, autoinject } from "aurelia-framework";
import { connectTo, dispatchify } from "aurelia-store";
#autoinject()
#connectTo()
export class Holiday {
subscription: Disposable;
state: any;
constructor(private bindingEngine: BindingEngine) {
}
async updateState()
{
await dispatchify(changeSelectedOption)();
}
attached() {
this.subscription = this.bindingEngine
.propertyObserver(this.state, 'selectedOption')
.subscribe((newValue, oldValue) => {
console.log("something has changed!")
});
}
}
export class State {
selectedOption: number = 0;
}
export const changeSelectedOption = (state: State) => {
let updatedState = { ...state };
updatedState.selectedOption++;
return updatedState;
}
store.registerAction("changeSelectedOption", changeSelectedOption);
The first time, my subscription will work and the console will log "something has changed!" as the state is the same object, but it won't work after.
Another solution I could use would be to have a computed property like so:
#computedFrom("state.selectedOption")
get selectedOptionChanged()
{
return console.log("something has changed!");
}
This is a hack, and this computed won't ever be triggered as it is not bound to anything in the HTML.
For context, I want to trigger a server call every time the selectedOption property changes.
What can I do to receive all updates from the property on the state?
The thing here is that the state observable exposed by the Store is a RxJS stream. So with the advent of the new "multi-selector" feature for connectTo you could create two bindings. By implementing a hook called selectorKey Changed, in your sample selectedOptionChanged it would get called on every change of said property.
#connectTo({
selector: {
state: (store) => store.state, // the complete state if you need
selectedOption: (store) => store.state.pluck("selectedOption")
}
})
class MyVM {
...
selectedOptionChanged(newState, oldState) {
// notification about new state
}
}
Instead of store.state.pluck("selectedOption") you can also experiment with additional conditions when to notify about changes like adding distinctUntilChanged etc.
Read more about multi-selectors in the updated docs.
Alternatively if you don't want to use the connectTo decorator, simply use the state property and create another subscription
When using MobX with React, I have 2 components. From the parent I send a prop to the child component like this:
import { computed } from 'mobx'
import { observer } from 'mobx-react'
#observer
class Parent extends React.Component {
#computed get user() {
const { gamer } = this.props;
}
render () {
return <div><Child user={this.user} /></div>
}
}
Child component:
import { observable } from 'mobx'
import { observer } from 'mobx-react'
#observer
class Child extends React.Component {
#observable owner = this.props.user;
render () {
return <div>{this.owner.name}</div>
}
}
The first time I run this with userX passed, the child shows the correct userX owner name, accessed via the #observable owner. The issue is the second time I run this with a different user passed userY, the child still shows userX even though the prop passed to it is correctly userY when I log it.
So the passed prop is different per user (as it should be), but the observable stays "locked" on the first user that was passed. Any idea why the observable isn't updating its value to the passed this.props.user?
Update:
So I tried #computed like this:
#computed get owner() {
return this.props.user;
}
but still the same issue. The only way I can seem to access the correct user, only in the render statement and directly from the passed prop as opposed to having mobx assign the prop value and read it from mobx observable/computed:
render() {
console.log(this.owner.name); // shows old data (even w/ observable or computed returning the passed prop)
console.log(this.props.user.name); // shows new data correctly without mobx
I just don't understand why the #observable or #computed don't return the correct new data. Is there anyway to have mobx correctly return the latest passed prop so the first console log works?
I think that we you do #observable owner = this.props.user, you do not create a reference to the original observable, but rather you create a new observable whose initial value will be the same as of the orginal one.
The solution (as it seems you already found) is to use the prop value directly in the Child component:
#observer
class Child extends React.Component {
render () {
return <div>{this.props.user.name}</div>
}
}
If you don't wanna do this, perhaps you can take a look at cloning* the observable using createViewModel from the mobx-utils package:
import { computed } from 'mobx'
import { observer } from 'mobx-react'
import { createViewModel } from 'mobx-utils'
#observer
class Child extends React.Component {
owner = createViewModel(this.props.user);
render () {
return <div>{this.owner.name}</div>
}
}
* Note: well, it is not exactly cloning, but the changes to user will get reflected in the owner object as well. Check the docs for more info.
So I'm new to OOP and trying out ionic 2 with angular 2 with typescript.
Say I have an input in the home.html file that is coupled to username in home.ts like this:
export class HomePage {
public username: string;
public newusername: string;
...
constructor(public users: UserService) {
...
Now I want a service (userService.ts) to take username from the input or home.ts and modify it and store the result as newusername.
Do I have to make a constructor in the service/provider like in Homepage which instantiates a new object of home though I already made an object of this in home.ts?
I imported HomePage in userServices but I cant access newusername because I didnt make an object of it.
I don't know what exactly you want but take a look at this if its good for you. (I'd use newusername in service itself)
NOTE: its has nothing to do with Ionic2 as I don't know Ionic2.
Working demo: https://plnkr.co/edit/03oXUTZYwRRo8PTfDFlA?p=preview
import { Component } from '#angular/core';
import {service} from './service';
#Component({
selector: 'my-app',
template: `
New User : {{s.newusername}}<br>
<input type="text" [(ngModel)]="username">
<button (click)="click(username)">Add User</button>
`
})
export class AppComponent {
constructor(private s:service){
}
click(username){
this.s.addUserName(username);
console.log(this.s.newusername)
}
}
service.ts
import {Injectable} from '#angular/core';
#Injectable()
export class service{
newusername:string;
addUserName(str){
this.newusername=str;
}
}
You would need to call a service from code in the component, or pass the component to the service.
constructor(public users: UserService) {}
#Input() public username: string;
// called when an input was updated
ngOnChanges() {
this.newusername = this.users.convertUserName(this.username);
}
or
constructor(public users: UserService) {
users.component = this;
}
but this would still need some way to notify the service about changes to the input like in the above example.