Reputation: 3134
In my Ember 2.8 application, I'm establishing a Websocket connection in a service. The connection URL changes when a user is logged in (it then includes the user auth token as a query parameter).
The current user service is simple:
CurrentUserService = Ember.Service.extend(
name: "current-user"
user: null
load: ->
// Do some stuff
@set("user", user)
)
It works exactly as expected, and I use it to display the current users username on the page (among other things).
In the Websocket service, all I do is create a computed property, depending on currentUser.user, that sets up the connection (depending on whether a user is logged in):
ActionCableService = Ember.Service.extend(
name: "action-cable"
cable: service()
currentUser: service()
testObs: Ember.observer("currentUser", ->
console.log "currentUser changed, #{ @get("currentUser.user") }"
)
consumer: Ember.computed("currentUser.user", ->
consumerUrl = "ws://localhost:10000/cable"
if @get("currentUser").user?
consumerUrl += "?token=#{ @get("currentUser.user.authToken") }"
console.log(consumerUrl)
return @get("cable").createConsumer(consumerUrl)
)
)
Problem is, the consumer property never gets updated. It's set once, on page load, and when the user property of the currentUser service changes, consumer
is not updated, and neither does my test observer.
When I refresh the page, sometimes the logged in consumerUrl is used, and sometimes it's not.
I'm guessing sometimes the session restoration happens first, and sometimes the action cable service happens first.
What I expected to happen when the action cable service gets loaded first is:
currentUser.user
(this happens, I can see the username on my page) consumer
computed property notices the currentUser.user
change and connects to the private consumerUrl (does not happen)I can very easily solve this problem in a way that does not depend on computed properties, but I would like to know what went wrong here.
Upvotes: 2
Views: 1360
Reputation: 11
Another way would be to emit an event in the service and then subscribe to the event in the init method and set the value of the dependent key of the computed property to force it to be recomputed/updated.
Upvotes: 1
Reputation: 7431
Unless you are invoking or calling that computed property, it will not execute your intended code.
Observers
, on the other hand, react without invocation, when the property they are watching, changes. But they are often overused, and can easily introduce bugs due to their synchronous nature.
You could refactor your observers and computed properties into helper functions that are called directly. This makes them easier to unit test as well.
In your controller, you can handle the initial action of logging in, like this:
currentUser: Ember.inject.service(),
actions: {
login() {
this.auth({ username: 'Mary' });
},
},
auth(data) {
// Send data to server for authentication...
// ...upon response, handle the following within the promise's `then`
// method, failures caught within `catch`, etc. But for purposes of
// demonstration, just mocking this for now...
const response = { username: 'Mary', authToken: 'xyz', };
this.get('currentUser').setConsumer(response);
},
The current-user service could then set it’s properties, and call a helper function on the action-cable
service:
actionCable: Ember.inject.service(),
authToken: null,
username: null,
setConsumer(response) {
this.set('authToken', response.authToken);
this.set('username', response.username);
this.get('actionCable').setConsumer();
},
The action-cable service reads properties from currentService
, sets the consumerUrl
, and calls the cable service to create the consumer:
cable: Ember.inject.service(),
currentUser: Ember.inject.service(),
setConsumer() {
var consumerUrl = "ws://localhost:10000/cable";
if (this.get("currentUser.username") !== null) {
consumerUrl += "?token=" + (this.get("currentUser.authToken"));
}
console.log("ACTION CABLE SERVICE, Consumer URL: ", consumerUrl);
this.get("cable").createConsumer(consumerUrl);
}
I’ve created an Ember Twiddle to demonstrate.
Upvotes: 3