Reading through this article covering OnPush Change Detection and it features the following service that uses a BehaviorSubject to load a user:
const ANONYMOUS_USER: User = {
firstName: '',
lastName: ''
};
#Injectable()
export class UserService {
private subject = new BehaviorSubject<User>(ANONYMOUS_USER);
user$: Observable<User> = this.subject.asObservable();
loadUser(user:User) {
this.subject.next(user);
}
}
Suppose the subject reference is never updated with a new user. In other words it sticks with the ANONYMOUS_USER state that it is initialized with.
Will all components that are injected with UserService and call loadUser receive an instance of the ANONYMOUS_USER. IIUC that is the purpose of BehaviorSubject. It remembers the current state so that loadUser can be called multiple times by different components and they will always receive the ANONYMOUS_USER?
Not quite.
All components that subscribe to user$ will receive the ANONYMOUS_USER value from the subject.
So for example, if a component A subscribes, it will immediately get the ANONYMOUS_USER value. If the user navigates to another component B, the original component A is destroyed. If the user navigates back to component A and it resubscribes, it will get the ANONYMOUS_User value again.
This code:
loadUser(user:User) {
this.subject.next(user);
}
Is what broadcasts a new value into the observable stream. So if any components call this method, they will broadcast a new value ... whatever they passed in to the method.
I have a complete example of using BehaviorSubject here: https://github.com/DeborahK/MovieHunter-communication/tree/master/MH-Take3
And code that accomplishes the same task using getters/setters instead of BehaviorSubject here: https://github.com/DeborahK/MovieHunter-communication/tree/master/MH-Take4
EDIT:
The main purpose of a Subject is to broadcast events to subscribers. It allows components to call a method (loadUser in this example) and pass a value. That value is then broadcast to all subscribers.
If you are using data binding, in most cases a subject is not needed. That is because Angular's change detection will provide the notification automatically.
A BehaviorSubject is a special type of Subject that has an initial value and provides that initial value to the subscriber when they subscribe. It is often used instead of a Subject if you need the subject to "remember" its last value.
Related
EDIT 2: This appears to be my general problem, and solution (using setTimeout so Angular's lifecycle can happen). I'll either close this or post an answer to my own question when I can.
See EDIT for a simpler repro that doesn't involve Subjects/Observables but is essentially the same problem.
I have a parent component that's responsible for fetching data from a service.
export class ParentComponent implements OnInit {
public mySubject: Subject<Foo[]> = new Subject<Foo[]>();
public buttonClicked = false;
private currentValues: Foo[] = null;
constructor(private SomeService myService) { }
this.myService.get().subscribe(values => {
this.mySubject.next(values); // Does NOT work when a child component is hidden, as expected.
this.currentValues = values; // Keep value so we can manually fire later.
});
private buttonClickHandler() {
this.buttonClicked = true;
this.mySubject.next(this.currentValues);
}
}
This data is subscribed to in the HTML by a child component. This component is hidden by default via *ngIf, and only becomes visible on a button click:
<app-child-component [values]="mySubject.asObservable()" *ngif="buttonClicked" />
In the parent component above you see I'm trying to pass the current available data to the child by invoking next() when the component is made visible in some way:
this.mySubject.next(this.currentValues);
This does not work when initially un-hiding the component via *ngIf. If I click the button a second time, which then calls next() again, then it works as expected. But when Angular is in the current context of un-hiding something, observables aren't getting their data. (This also happens when things are unhidden by other means, but the result is the same: If in the same method, the subject/data passing does not work; the component has to already be visible as of the method call.)
I'm guessing the binding to the observable is not happening until after *ngIf shows the child component, after the method call resolves. Is there some place I can hook into that I can then pass child data down?
EDIT for clarification: I don't believe this is an issue of Subject vs. BehaviorSubject. I'm not having issue passing the data. The issue is that the data-passing (confirmed via console.log()) is not occurring at all in the first place. It's not that the child component is receiving a null value. The subscription just isn't firing to the child.
I found I can reproduce this in a simpler fashion too: Trying to select an element in the DOM of *ngIf HTML reveals undefined if I make *ngIf's value true within the same Angular method.
<div *ngIf="buttonClicked">
<div id="someElement">Foo</div>
</div>
public someMethod(): void {
this.buttonClicked = true;
const container = document.getElementById('someElement'); // DOES NOT WORK if this.buttonClicked was false at the start of this method!
}
You going to need to use a BehaviourSubject instead of Subject, which emits the previously set value initially.
What is the difference between Subject and BehaviorSubject?
I am using this helper to display a datepicker component
<%= react_component "MyDatePicker", startDate: '', endDate: ''%>
I want to pass javascript values to startDate and endDate props. Any possible way to do this?
I don't really know what you're trying to do here exactly but if you just want to get values of your props from the component to your rails controller, do the following.
You can set state of your props in your react component and send an ajax request whenever the user selects a date.
Fixed this by using Pubsub. What I did is publish the user selected values in the javascript first. Then on react parent component lifecycle method subscribed to the previously published event and use setState to change the state based on that.
Now am on mobile but i will publish the code for clarity once i got access to a pc.
update
Using pubsub is easy. First we need to publish the required from anywhere using javascript
dates={some_value:value, another_value: value }
PubSub.publish(OFFLINE_BOOKING_DURATION, dates)
Here I just pass a dates object. You can pass anything.
Then in react's componentDidMount state I subscribe to this
componentDidMount: function () {
this.token = PubSub.subscribe(OFFLINE_BOOKING_DURATION, this.subscriber)
},
Here the first is object we are expecting and the callback
so here is the call back function here you can do anything like ajax call, set state, or anything
subscriber: function (msg, data) {
#this method gets called on receiving data
#we can access the data we passed like this
data.some_value},
And finally unsubscribe when component unmounts
componentWillUnmount: function () {
PubSub.unsubscribe(this.token)
}
This is how I implemented it. I no longer have the code but I hope you got the point.
In my current project, I have a very simple service which sets a string when a request comes from first page and show it in the second page using the same service. Setting the text is working perfectly. But when I called the get function, it returns undefined.
This is my service
import { Injectable } from '#angular/core';
#Injectable()
export class TsService {
constructor() { }
ts: string;
getTs() : string {
return this.ts;
}
setTs(ts) : void {
this.ts = ts;
}
}
In my first component I imported the Service
import { TsService } from './ts.service';
and added it to the providers
providers: [TsService]
and initialized in the contructor
private tsService: TsService
and to the button click, I set a string as well
this.tService.setTs(form.value.member)
In my second component, followed the same steps mentioned above except in the constructor I assigned as follows
this.ts = tsService.getTs();
but it gives me undefined. Is there anything that I missed
As i can make out from your code. You have registered your service as a provider in your component. like
providers: [TsService]
What this line of code will do. Is that it will fetch a new Instance of your service as soon as your component comes into play. So from first component lets say ComponentA you set the service variable as
this.tService.setTs(form.value.member)
But here ComponentA is having suppose Instnace1 of the service. So you have set the value to Instance1 of the Service. Now you navigate to second component say ComponentB As soon as ComponentB comes into play it angular creates a new Instance of the service and same is made available to ComponentB. Now there are two Instances of your service one with ComponentA and one with ComponentB. but you have set the variable using ComponentA so it wont be available to ComponentB hence
this.ts = this.tsService.getTs();
this returns undefined.
In order to check whether you variable is set or not you can try
this.tService.setTs(form.value.member);
console.log(this.tsService.getTs());
in your ComponentA it will log the value set.
The solution for this problem of your is to share the same Instance and that can be achieved by registering the service as provider in the AppModule.
As official docs say
On the one hand, a provider in an NgModule is registered in the root
injector. That means that every provider registered within an NgModule
will be accessible in the entire application.
For more please refer :-
Dependency Injection
NgModule Injectors
Hope it helps :)
Depending on the order in which stuff is executed it may well be that
this.tService.setTs(form.value.member)
is being executed after
this.ts = tsService.getTs();
In which case the behaviour is expected.
As for how to deal with this problem. One way is to add a way for components to subscribe to the service and get notified when ts changes so that they can react by executing some code. Look into RxJS' Subject.
A different reason may be that you are not providing the service correctly.
For example if you provide the service to a parent and a child component (direct or not). In that case the second provider may be shadowing the first due to Angular's hierarchical dependency injection. Which means that one component is setting the value in one instance of the service and the other component is getting it from a different one. Unless you specifically need that kind of behaviour a service should only be provided at the root of the component tree where it's going to be used.
If your components are not related through the parent-child hierarchy then you should be providing the service only once. At the module level.
Without knowing more about your component structure it's not possible to tell what exactly is going on.
You may have use two instances of data object.
use one data service for set and get data.Then you can access to same data object within different components.
And if you set that service as a provider for your components individually those gonna work as different instances. If you need only one instance for your whole app you can set that service as a provider in app.module.ts file.
In my app.component.html I create a custom component (contact-table component) that should get a new value (Account) to present after I update the in the store the connected user to be someone else.
app.component.html:
<contact-table [contacts]="connectedAccount$ |async"></contact-table>
app.component.ts:
export class AppComponent implements OnInit
{
private connectedAccount$: Observable<Account>;
constructor(private store:Store<any>) {
this.connectedAccount$ = this.store
.select(state=> state.connectedAccountReducer.connectedAccount);
this.connectedAccount$
.subscribe(account1=> {
//the app prints any new account that comes up. It's working here.
console.log(account1);
});
}
public ngOnInit() {
this.store.dispatch({
type: updateConnectedAccount,
payload: {
connectedAccount: ACCOUNTS[0]
}
});
}
}
The subscribtion in AppComponent class works great and fires any update that comes up.
The problem is that the async pipe in app.component.html does not send the new account to the contact-table component. I think he doesn't get notified for any updates.
I tried to see what is being sent to the contact-table component,
and I saw he only get one value in the first creation of the contact-table component and its undefined. Thats not possibole becouse my reducer function creates an empty Account object for the initial state of my app.
Is there anything you notice that I missed?
Check and make sure you are using Object.assign to create the changes to your state. It shouldn't return a mutated state. Redux/ngrx and the async pipe detect changes when the object hash changes (or at least this is my understanding). I've run into this problem when I wasn't creating a brand new object but accidentally mutating the existing state and returning it.
To quote the redux site -> http://redux.js.org/docs/basics/Reducers.html
We don’t mutate the state. We create a copy with Object.assign(). Object.assign(state, { visibilityFilter: action.filter }) is also wrong: it will mutate the first argument. You must supply an empty object as the first parameter. You can also enable the object spread operator proposal to write { >...state, ...newState } instead.
I also had a async issue with RC2 so if you aren't using RC3 or above I'd recommend upgrading.
Not saying this is it but it is the most likely candidate from my experience. Hope this helps.
Try using the json pipe just to see if the view is getting anything from that store select.
<span>{{connectedAccount$ |async | json}}</span>
You can include ngrx-store-freeze - the problem with mutable state is easy to address.
Another way of how I often debug the dispatching of Actions is to introduce an Effect for logging purpose. This helps to identify problems as well.
I am kind of confused here on where to place my global functions. In a lot of examples a main.js file points to an app component and this is placed somewhere within the html. This workflow would be fine for me If I were to simply contain all my logic within this app component. But I am combining components with Laravel functionality so this does not work for me.
Currently my main.js file contains a bunch of methods that I need to have access from anywhere in my app. These methods don't contain any broadcasting events so they can effectively be placed anywhere as long as they get a vue-resource instance.
My main.js file:
https://github.com/stephan-v/BeerQuest/blob/develop/resources/assets/js/main.js
Hopefully somebody can tell me where I could place my friendship methods if I were to use vuex or in general since this does not seem like best practice at all.
Thank you.
Vuex manages all of the data in your application. It's a "single source of truth" for data on your front-end. Therefore, anything that changes the state of your application, such as adding a friend, or denying a friend, needs to flow through Vuex. This happens through three main function types, getters, actions, and mutations.
Check out: https://github.com/vuejs/vuex/tree/master/examples/shopping-cart/vuex
Getters are used to fetch data from storage in Vuex. They are reactive to changes, meaning if Vuex data changes, the information in your component is updated as well. You can put these in something like getters.js so that you can import them in any module you need them in.
Actions are functions that you call directly, ie. acceptFriendRequest when a user clicks the button. They interact with your database, and then dispatch mutations. In this app, all of the actions are in actions.js.
So you'd call this.acceptFriendRequest(recipient) in your component. This would tell your database to update the friend status, then you get a confirmation back that this happened. That's when you dispatch a mutation that updates the current users' list of friends within Vuex.
A mutation updates the data in Vuex to reflect the new state. When this happens, any data you are retrieving in a getter is updated as well. Here is an example of the entire flow:
import {addFriend} from './actions.js';
import {friends} from './getters.js';
new Vue({
vuex:{
getters:{
friends
}
},
methods:{
addFriend
}
}
store.js:
export default {
state:{
friends: []
},
mutations:{
ADD_FRIEND(state, friend) {
state.friends.push(friend);
}
}
}
actions.js:
export default {
addFriend(friend){
Vue.http.post('/users/1/friends',friend)
.then((response)=>{
dispatch("ADD_FRIEND", response) //response is the new friend
})
}
}
getters.js
export default {
friends(state) {
return state.friends;
}
}
So all of these are organized into their own files, and you can import them in any component you need. You can call this.addFriend(friend) from any component, and then your getter which is accessed from this.friends will automatically update with the new friend when the mutation happens. You can always use the same data in any view in your app and know that it is current with your database.
Some misc stuff:
getters automatically receive state as a variable, so you can always reference the state of your Vuex store
mutations should never be asynchronous. Do fetching/updating in actions and then dispatch mutations just to update your data
creating services (or resources) using Vue Resource will make fetching/updating/deleting resources even easier. you can put these in separate files and import them in your actions.js to keep the database retrieval logic separated. Then you'd be writing something like FriendService.get({id: 1}) instead of Vue.http.get('/users/1'). see https://github.com/vuejs/vue-resource/blob/master/docs/resource.md
Vuex works with vue devtools for "time-traveling". You can see a list of every mutation that has taken place and rewind them/redo them. It's great for debugging and seeing where data is being changed.