I'm new with NgRx.
I made some code, but the result is not exactly what intended.
I would like to intercept every action sent with the dispatch() function to the store.
My goals are the following:
intercept every action sent to the store in a custom Effect, and print the text (Intercepting action...) to the console without modifying the action. (The Effect, that handles this action, should work normally.)
if the action (that was previously sent to the store) made its job (i.e. the corresponding Reducer made modifications on the store), I also want to "react" on this, and print another message (Got state from store.) on the console
I have 2 problems in my code:
the texts appear in the wrong order (i.e. first "Got state from store", then "Intercepting action...", then "Got state from store" again).
to intercept every action, I make an array of all of the actions, defined in the application, and call ofType() with this array. If a new action is implemented, I have to manually update this array. Is there a way to retrieve the registered actions directly from the store ?
Below is the code:
app.module.ts
//to register the Effect to intercept the actions
imports: [
EffectsModule.forRoot([InterceptorEffects]),
]
app.component.ts
//here I react to the changes of the store
_interceptorSubscription: Subscription;
this._interceptorSubscription = _store.pipe(select((state, props) => console.log("Got state from store.")) );
interceptor.effect.ts
//the Effect that should intercept every action
#Injectable({ providedIn: "root" })
export class InterceptorEffects {
private readonly _interceptAllAction$ = createEffect(() =>
this._actions.pipe(
ofType(...[JobActions.downloadResultAction, JobActions.deleteJobAction]),
tap(() => console.log("Intercepting action..."))
)
, { dispatch: false } );
constructor(private _actions: Actions) {}
}
Thank you for any advice.
May I ask why you need this?
To answer the question:
AFAIK you can't do this. An action first reaches all reducers, and then it's handled by effects. If you want to register actions BEFORE they reach a reducer, take a look at meta reducers.
To react to all actions in an effect, don't use the ofType operator, and simply subscribe to the Actions stream.
Related
I created a simple test case in Symfony.
So one client which should listen for an event which will be dispatched during an request.
But nothing happen because the request have an own scope or I dont know why Im not able to access the dispatcher in it.
$this->client = static::createClient();
self::$container = $this->client->getContainer();
$dispatcher = self::$container->get('event_dispatcher');
$dispatcher->addListener('example', function ($event) {
// Never executed
});
$this->client->request('POST', $endpoint, $this->getNextRequestParameters($i), [$file], $this->requestHeaders);
$this->client->getResponse();
The listener is never called.
When I debug it a bit I find out that the object hash via spl_object_hash($dispatcher) is different on the highest level than on within the request level.
So it seems that the request has an own world and ignores everything outside.
But then is the question how I can put my listener to this "world"?
I think part of the problem is the mixing of testing styles. You have a WebTestCase which is intended for a very high level of testing (requests & responses). It should not really care about internals, i.e. which services or listeners are called. It only cares that given input x (your request) you will get output y (your response). This allows to ensure the basic functionality as perceived by your users is always met, without caring how it is done. Making these tests very flexible.
By looking into the container and the services you are going into a lower level of testing, which tests interconnected services. This is usually only done within the same process for the reasons you already found out. The higher level test has 2 separate lifecycles, one for the test itself and one for the simulated web request to your application, hence the different object ids.
The solution is either to emit something to the higher level, e.g. by setting headers or changing the output, so you can inspect the response body. You could also write into some log file and check the logs before/after the request for that message.
A different option would be to move the whole test into a lower level where you do not need the requests and instead only work with the services. For this you can use the KernelTestCase (instead of the WebTestCase) and instead of calling createClient() you call bootKernel. This will give you access to your container where you can modify the EventDispatcher. Rather than sending a request you can then either call the code directly, e.g. dispatch an event if you only want to test the listeners, or you can make your controller accessible as service and then manually create a request, call the action and then either check the response or whatever else you want to assert on. This could look roughly like this:
public function testActionFiresEvent()
{
$kernel = static::bootKernel();
$eventDispatcher = $kernel->getContainer()->get('event_dispatcher');
// ...
$request = Request::create();
// This might not work when the controller
// You can create a service configuration only used by tests,
// e.g. "config/services_test.yaml" and provide the controller service there
$controller = $kernel->getContainer()->get(MyController::class);
$response = $controller->endpointAction($request);
// ...Do assertions...
}
Using version 2.17. I have an Ember component inside an /edit route with a controller:
// edit.hbs
{{ingredient-table recipe=model ingredients=model.ingredients}}
Inside my component, I am using a didRecieveAttrs hook to loop through ingredients on render, create proxy objects based off of each, and then build an ingredient table using those proxy objects.
// ingredient-table.js
didReceiveAttrs() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
this.set('recipeIngredients', Object.values(uniqueIngredients));
}
I also have a delete action, which I invoke when a user wishes to delete a row in the ingredient table. My delete action looks like this:
// ingredient-table.js
deleteIngredient(ingredient) {
ingredient.deleteRecord();
ingredient.save().then(() => {
// yay! deleted!
})
}
Everything mentioned above is working fine. The problem is that the deleted ingredient row remains in the table until the page refreshes. It should disappear immediately after the user deletes it, without page refresh. I need to trigger the didReceiveAttrs hook again. If I manually call that hook, all my problems are solved. But I don't think I should be manually calling it.
Based on the docs, it is my understanding that this hook will fire again on page load, and on re-renders (not initiated internally). I'm having some trouble figuring out what this means, I guess. Here's what I've tried:
1) calling ingredients.reload() in the promise handler of my save in ingredient-table.js (I also tried recipe.reload() here).
2) creating a controller function that calls model.ingredients.reload(), and passing that through to my component, then calling it in the promise handler. (I also tried model.reload() here).
Neither worked. Am I even using the right hook?
I suppose recipeIngredients is the items listed in the table. If that is the case; please remove the code within didReceiveAttrs hook and make recipeIngredients a computed property within the component. Let the code talk:
// ingredient-table.js
recipeIngredients: Ember.computed('ingredients.[]', function() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
return Object.values(uniqueIngredients)
})
My guess is didReceiveAttrs hook is not triggered again; because the array ingredients passed to the component is not changed; so attrs are not changed. By the way; do your best to rely on Ember's computed properties whenever possible; they are in the hearth of Ember design.
I am a bit confused. Components, controllers, routes, helpers and whatsoever. I simply want to grab a value from a JSON file and calculate it with a value on Ember.Helper. Which way should i use, i cannot know anymore, brain burned. Would someone please help me to grab the "sell" part of the "market_name" which equals to "BTC_USDT" on "https://stocks.exchange/api2/prices" and put that into helper?
Edited:
In fact i try to do something like that.
import Ember from 'ember';
export function formatBTC(value) {
var url = 'https://stocks.exchange/api2/prices';
var btc_price = Ember.$.getJSON(url).then(function(data) {
for (var i=0; i <= data.length-1; i += 1)
{
if (data[i].market_name == "BTC_USDT")
{
return data[i].sell;
console.log(data[i].sell+' - i got the value properly');
}
}
});
console.log(btc_price+' - shows nothing, i cannot pass the var btc_price to here, why not');
calculation = value * btc_price; //some syntax may apply, maybe Number(value) or whatsoever, but i cannot have my variable btc_price returns here.
return calculation.toFixed(8);
}
export default Ember.Helper.helper(formatBTC);
And from the index.hbs
{{format-btc 0.001}}
Still couldnt find a proper solution. I get the data[i].sell as btc_price, but couldnt pass it through to return part... what am i missing? or what am i doing wrong?
The issue you're encountering is because the ajax request executes. Execution of the function continues and returns the value before the promise returns.
While technically, you could fix this and use async/await in your helper function, you'll run into another issue - Every time your helper is called, you'll make a new ajax request that will fetch the current price and calulate the value.
My recommendation is that instead of a helper, you use a combination of a model and a controller. Because you're currently overwhelmed with the framework, I'll actually make a second suggestion of using a service + component
I recommend a service or a model because you want to persist the data that you've fetched from the pricing source. If you don't, every instance of the helper/component will make a new request to fetch data.
Service
A service is kind of a session collection in ember. It only gets instantiated once, after that data will persist.
ember g service pricing
In the init block, set your default values and make your ajax request.
# services/pricing.js
btcPrice:null,
init() {
this._super(...arguments);
Ember.$.getJSON(...).then(val=>{
# get correct value from response
# The way you were getting the value in your example was incorrect - you're dealing with an array.
# filter through the array and get the correct value first
this.set('btcPrice',val.btcPrice);
})
}
Component
You can then inject the service into the component and use a consistent price.
ember g component format-btc
Modify the controller for the component to inject the service and calculate the new value.
#components/format-btc.js
pricing: Ember.inject.service('pricing')
convertedPrice: Ember.computed('pricing',function(){
return pricing.btcPrice*this.get('bitcoins')
})
The template for the component will simple return the converted price.
#templates/components/format-btc.js
{{convertedPrice}}
And you'll call the component, passing in bitcoins as an argument
{{format-btc bitcoints='1234'}}
All of this is pseudo-code, and is probably not functional. However, you should still be able to take the guidance and piece the information together to get the results you want.
tl;dr: Data can be sent in and out of a component, but I only know how to send actions out. Is there a way to send actions in?
In my Ember application, I have something like the following UI from Google Maps:
The background map corresponds to a PinsRoute/PinsView/PinsController, and it shows many pins. When you click one, you enter the PinRoute, which renders the overlay to {{outlet}}. Both the big map and the thumbnail (in the Google Maps image, the picture that says "Street View") are components: FullscreenMapComponent and ThumbnailMapComponent, respectively.
In Google maps, when you click "Street view", it pans and zooms the main map to the selected point. This is essentially what I'm trying to figure out how to wire up.
When the user clicks "streeth view" on my ThumbnailMapComponent, I can send out an action, which the PinsRoute can handle. The question is, how can I then reach down to my FullscreenMapComponent and invoke the appropriate method (.panToSelected(), in this case)?
Here's a good solution. Have the component register itself to whatever controller it's being rendered in, and that way you can access the component in action handlers.
In my case, I added a method to my component
App.FullscreenMapComponent = Ember.Component.extend({
...
_register: function() {
this.set('register-as', this);
}.on('init')
});
and a property to my controller:
App.SensorsController = Ember.ArrayController.extend({
fullscreenMap: null,
...
});
In my template, I bind the two with my new register-as property:
// sensors.hbs
{{fullscreen-map data=mapData
selectedItem=currentSensor
action='selectSensor'
deselect='deselectSensor'
register-as=fullscreenMap }}
And now, let's say I click the thumbnail above and it sends an action that bubbles up to my ApplicationRoute, I can now do this:
// ApplicationRoute.js
actions: {
panTo: function(latLong, zoom) {
this.controllerFor('sensors').get('fullscreenMap').panTo(latLong, zoom);
}
}
Presto! Components responding to actions.
Update (10/6/2016)
Whew, I've learned a lot in two years.
I would no longer recommend this solution. In general, you should strive for your components to be declarative, and have their output and behavior depend solely on their state. Instead, my original answer here solved the problem using an imperative method, which is brittle, harder to understand, and not very easy to extend (what if we wanted to tie the the map's position to the URL?).
If I was refactoring this specific component, I'd probably make {{fullscreen-map}} accept latLong and zoom params. If someone from the outside sets those, the map would respond and update itself. You can use didReceiveAttrs from within the component to respond to param changes, and then from there call the imperative panTo method from Google's API.
The takeaway is that, even if you have to interact with imperative methods (maybe because of a third-party lib), strive to make your own components' APIs as declarative as possible. What first seems like "sending an action down" can usually be expressed as some function of state, and that state is what you want to identify and make explicit.
This is working example but I am not 100% sure that this approach is best
Here what you can do:
When calling action pass your component as parameter:
App.PinController = Ember.Controller.extend({
actions: {
actionThatComponentCalls: function(){
// different component will be called
new App.MyOtherComponent().send('differentAction'):
}
}
});
App.FullScreenMapComponent = Ember.Component.extend({
click: function(){
this.sendAction();
}
});
App.MyOtherComponent = Ember.Component.extend({
actions: {
differentAction: function(){
console.log('different action called');
}
}
});
Hope this helps
Curious about the proper procedure, or at least common procedure for using sproutcore-routing.
In the read me there it shows this basic example for routing:
SC.routes.add(':controller/:action/:id', MyApp, MyApp.route);
I'm assuming that in most cases MyApp.route would call the supplied action on the supplied controller. My question is more about beyond this step how you handle the setup/teardown stuff for an application where you have lots of primary views.
Are people instantiating new controllers when the controller changes as to always start with a clean slate of data and views? Or is it more common/advisable to instantiate all the controllers and such at load and simply use the routing to show/hide primary views?
I suppose the same question goes when bouncing between actions within a controller. Is it proper to do some teardown, especially on bindings/listeners, and then re-establishing them if the action is recalled?
My question may be a little fuzzy, but I'm basically wondering how people handle lots of primary views, and deal with cleanup so stuff doesn't get stale or chew up lots of resources.
I wrote a blog post that describes a method for this: http://codebrief.com/2012/02/anatomy-of-a-complex-ember-js-app-part-i-states-and-routes/
In most Ember and Sproutcore apps and examples I have seen, controllers are instantiated at app initialization. Routes drives state changes in statecharts, where controllers are updated and views are created/destroyed as needed.
I have the following setup.
in my Ember.Application.create() I have the following code:
MyApp.routes = Em.Object.create({
currentRoute: null,
gotoRoute: function(routeParams) {
console.log('MyApp.routes gotoRoute. type: ' + routeParams.type + ' action: ' + routeParams.action + " id: " + routeParams.id);
if (routeParams.type === 'expectedType' && routeParams.action === 'expectedAction' && routeParams.id) {
//find item with ID and load in controller
MyApp.MyController.findItemWithId(routeParams.id);
//Navigate to the correct state
MyApp.stateManager.goToState('stateName');
}
}
})
SC.routes.add(":action/:type/:id", MyApp.routes, 'gotoRoute');
Then, when I click on things that should cause the URL to change I do:
SC.routes.set("location", "show/item/ID-123-123");
Your app should now be listening to changes in the URL and cause the correct action to happen based on the URL-part.
You could probably move the MyApp.MyController.findItemWithId(routeParams.id); to the enter() function of the statechart (if you are using them), but you do need to store that ID somewhere in some controller.