How to emit one event after finishing previous one using NgRx? - redux

I am new to this technology and I am working on a project where blow's the scenario.
table schema:
tbl.Product
id
product name
brand_id [FK]
tbl.Brand
id
BrandName
in this, while submitting I am adding BrandName first and taking the newly added id of brand record and insert full record in product table with brand_id.
Submit(){
this.saveBrand.emit(brand);
product.brand_id = brands.id;
this.saveProduct.emit(product);
}
for this, I am using NgRx Store.
Brand.effects.ts
addBrand$: Observable<Action> = this.actions$
.pipe(ofType<Add>(BrandActionTypes.Add),
switchMap(action => this.BrandService.addBrand(action.payload)),
map((Brand: any) => new AddSuccess(Brand)),
catchError(err => {
toastr.error(`Could not add Brand.`);
console.error(err);
return of(new AddFail(err));
})
);
Product.effects.ts
addProduct$: Observable<Action> = this.actions$
.pipe(ofType<Add>(ProductActionTypes.Add),
switchMap(action => this.ProductService.addProduct(action.payload)),
map((Product: any) => new AddSuccess(Product)),
catchError(err => {
toastr.error(`Could not add Product.`);
console.error(err);
return of(new AddFail(err));
})
);
while executing this both event emitting together (both are not async). so product is not getting brand_id.
i am expecting output like.
Submit(){
this.saveBrand.emit(brand);
product.brand_id = brands.id;
this.saveProduct.emit(product); \\hold until brand get added
}

Dispatching an action is an async process, that's why the brand id isn't "populated".
To solve your problem you can do a few things:
your products effects can listen on the AddSuccess action
use one effect to handle both service calls
make on service call?
A great article about some of the patterns NgRx: Patterns and Techniques

Related

ngrx effect - waiting on external data

I have an effect which runs when a user is downloaded and stored in state:
getMetadata$ = createEffect(() => {
return this.actions$.pipe(
ofType(fromUserSelectors.getUserSuccess),
concatLatestFrom(() => this.store.select(fromProductSelectors.selectProduct)),
exhaustMap(([user, product]) =>
// do something with user and product
)
);
});
This effect is triggered when getUserSuccess is dispatched.
However, you can see on the 4th line, I also need the product which is stored in a different store and is downloaded asynchronously to the user being downloaded. So it isn't necessarily populated until later.
How can I tell this effect to wait for the product to be populated before resuming? I do not want to retrigger the effect. I thought that was the purpose of concatLatestFrom introduced in ngrx v12, but maybe I am misunderstanding it.
I am aware I could have multiple actions being listened to in my ofType call and listen to fromProductSelectors.getProductSuccess, but I only want this effect to run when getUserSuccess is called, not fromProductSelectors.getProductSuccess too, but need the selectProduct to not be undefined.
You can use the filter operator to wait until it becomes available.
getMetadata$ = createEffect(() => {
return this.actions$.pipe(
ofType(fromUserSelectors.getUserSuccess),
concatLatestFrom(() => this.store.select(fromProductSelectors.selectProduct).pipe(filter(x => !!x)),
exhaustMap(([user, product]) =>
// do something with user and product
)
);
});

Dispatch action in effect before return statement

I need to dispatch/trigger another ui-related action inside an effect before calling the service to fetch data without injecting the store
I managed to fix it by injecting the store in the constructor and dispatch this extra action in effect right before calling the service for fetching the data(this.store.dispatch(UIActions.startLoading()) but I am not sure if injecting the store in the effect is a good practice
recipes$ = createEffect(() => this.actions$.pipe(
ofType(RecipeActions.FETCH_RECIPES),
switchMap(() => this.recipeService.getRecipes().pipe(
switchMap(recipes => [
RecipeActions.setRecipes({ recipes }),
UIActions.stopLoading()
]),
catchError(() => EMPTY)
))
));
I wonder if there is a way to do this like using
tap(() => of(UIActions.startLoading())) inside the first switchMap
You're correct in the use of tap operator for your desired functionality.
However, you need to do store.dispatch instead of simply returning an of observable.
Also, instead of multiple switchMap, you can use the from observable to return array of actions.
recipes$ = createEffect(() => this.actions$.pipe(
ofType(RecipeActions.FETCH_RECIPES),
tap(()=> this.store.dispatch(UIActions.startLoading())),
switchMap(() => this.recipeService.getRecipes().pipe(
map(recipes => from([
RecipeActions.setRecipes({ recipes }),
UIActions.stopLoading()
]),
catchError(() => EMPTY)
))
));

Laravel - multiple forms/tables - carry over variable in URL to fill table column

I am quite new to coding and to Laravel (using 5.7). I understood how to create a basic one-page form to populate a table in a database, including relashionship and authentication but I have difficulties reaching the next level.
The app I want to design will have multiple forms and tables and I can't figure out how to link info collected from the first form to the following one.
Let's consider a Database with:
- a Clients table (populated thru form page A) ex: one field is a client_id field.
- a Products table (populated thru form page B)
- (ultimately they will be more)
When the user (e.g. an employee of a compagny analyzing clients behavior) is done filling the form page A (URL: /clients, GET method: Clientcontroller#create, view clients.create.blade.php), he/she click Next.
My idea is that the Next button should:
- submit information to the Client table (POST method: Clientscontroller#store)
- and redirect the user to the page B of the form, carrying over the client_id in the URL (URL: /products/{client_id}/create, GET method: Productscontroller#create, view products.create.blade.php).
In the Product table, I have a client_id column and I have a one to many relashionship between the Clients and Products model.
Upon submission of form B, I would like to retrieve the {client_id} from URL to fill the client_id column of the Product table but I am stuck here. I would appreciate pieces of guidance and advices. For simplification during the learning process, I consider that Clients only buy one product.
THE MAIN QUESTION IS:
- How to retrieve the {client_id} parameter from the URL of the products.create.blade.php view to inject it onto the client view (already tried a lot of thing from answer to similar questions in stackoverflow)
ALSO:
- Am I using the right approach? Any suggestions/advices?
EXTRA QUESTION a bit out of the scope:
- Any hint on how implement add/remove fields for products?
WEB ROUTES FILE:
> <?php
> Route::get('/', 'PagesController#welcome')->name('welcome');
> Auth::routes();
> //ROUTES FOR CLIENT
> Route::resource('clients','ClientsController');
> //ROUTES FOR PRODUCT (please note the {client_id} parameter)
> Route::get('/products/{client_id}/create', 'ProductsController#create')->name('products.create');
> Route::post('/products/{client_id}', 'ProductsController#store')->name('products.store');
> //not sure if it should be Route::post('/products', 'ProductsController#store')->name('products.store');
CLIENTS CONTROLLER:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Client; //THIS IS THE CLIENT MODEL
use App\User; ////THIS IS THE USER MODEL
class ClientsController extends Controller
{
AUTH
public function __construct()
{
$this->middleware('auth');
}
//INDEX (CLIENT)
public function index()
{
//my code here
}
//CREATE (CLIENT)
public function create()
{
$datas=[
'controllermethod' => 'ClientsController#store',
'client_id_field' => [
'input_type_l' => 'textinput',
'input_name_l' => 'client_id', //this what i am carrying over in URL
'input_label_l' => 'Client ID :',
'input_placeholder_l' => 'XXXXX',
'input_default_l' => ''
],
'client_info_1_field' => [
'input_type_l' => 'textinput',
'input_name_l' => 'client_info_1'
'input_label_l' => 'Client Info 1 :',
'input_placeholder_l' => 'XXXXX',
'input_default_l' => ''
],
'client_info_2_field' => [
'input_type_l' => 'textinput',
'input_name_l' => 'client_info_2'
'input_label_l' => 'Client Info 2 :',
'input_placeholder_l' => 'XXXXX',
'input_default_l' => ''
]
],
return view ('clients.create')->with($datas);
}
//STORE (CLIENT)
public function store(Request $request)
{
$this->validate($request, [
'client_id' => 'required',
]);
$client = new Client;
$client->client_id = $request->input('client_id');
$client->client_info_1 = $request->input('client_info_1');
$client->client_info_2 = $request->input('client_info_2');
$client->user_id = auth()->user()->id; //one to many relashionship betw user/client models
$client->save();
//
//this is how I inject the {client_id} parameter onto the URL
//this works, the products.create view is displayed, the URL contain the client_id from formA entered by the user
$client_id = $request->client_id;
return redirect("/products/$client_id/create")->with('client_id', $client_id)
->with('success','New client record created');
//SHOW/DESTROY/EDIT/UPDATE FUNCTIONS....all this work
PRODUCTS CONTROLLER = THAT's WERE I AM STUCK + NOT SURE IF IT's THE RIGHT APPROACH
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
//note sure if I need that
//use Illuminate\Routing\Route;
use App\Product;
use App\Client;
use App\User;
class ProductsController extends Controller
{
//AUTH
public function __construct()
{
$this->middleware('auth');
}
//INDEX (PRODUCT)
public function index()
{
//no code so far
}
//INDEX (PRODUCT)
public function create()
{
$datas=[
'controllermethod' => 'ProductsController#store',
//planning of having dynamic add/remove products fields but let's keep it simple (1 client -> 1 product)
'product_id_field' => [
'input_type_l' => 'textinput',
'input_name_l' => 'product_id',
'input_label_l' => 'Date of cancer diagnosis :',
'input_default_l' => ''
],
'product_info_1_field' => [
'input_type_l' => 'textinput',
'input_name_l' => 'product_info_1'
'input_label_l' => 'Product Info 1 :',
'input_placeholder_l' => 'XXXXX',
'input_default_l' => ''
],
'product_info_2_field' => [
'input_type_l' => 'textinput',
'input_name_l' => 'product_info_2'
'input_label_l' => 'Product Info 2 :',
'input_placeholder_l' => 'XXXXX',
'input_default_l' => ''
]
],
//Below, I am not sure I should do that
return view ('products.create')->with($datas);
}
// STORE (PRODUCT) = THAT's WHERE I AM STUCK
// everything works except that the client_id column in the products table stays empty or filled with crap
public function store(Request $request)
{
//NOT SURE WHAT TO DO HERE TO RETRIEVE THE {client_id} from the "/products/$client_id/create" ...
//TO FURTHER INJECT IT AS A VALUE IN THE client_id COLUMN OF THE PRODUCT TABLE
//I KNOW IT's CRAP, but I TRIED THINGS ALONG THOSE LINES:
//return $client_id;
//$client_id = request()->route()->paremeters('client_id');
//$client_id = request()->route('client_id');
//$client_id = $request->client_id;
//$client_id = url('client_id');
$product = new Diagnosis;
$product->product_id = $request->input('product_id');
$product->product_info_1 = $request->input('product_info_1');
$product->product_info_2 = $request->input('product_info_2');
$product->user_id = auth()->user()->id;
//I KNOW IT's CRAP, but I TRIED THINGS ALONG THOSE LINES:
//$diagnosis->client_id = $client_id; //NB: if I write $diagnosis->client_id = 'whatever'; it works
$diagnosis->save();
//redirect to client.index view
return redirect('/clients')->with('success','New patient diagnosis (-es) created');
}
our solution will start by looking to this route :
Route::get('/products/{client_id}/create','ProductsController#create')->name('products.create');
this is the route that contains your client_id that we want to retrieve, and since this value is on the link, we can use the Request object to access to it!
like i said, the $request object already contains the client_id value, so what we need is to retrieve it and send it as a parameter to the view using the with function, so basically your ProductsController#create will be like this :
public function create(Request $request)
{
// all your code ...
return view ('products.create')->with("data",$datas)->with("client_id",$request->client_id);
}
so now, we access this value from our view right ? the idea is to add this value as a hidden input on the product form! ( something like this )
<form>
<!-- other inputs .... -->
<input type="hidden" value="{{$client_id}}" name="client_id" />
</form>
after submitting the form, this route will be called :
Route::post('/products/{client_id}', 'ProductsController#store')->name('products.store');
since the client_id will be sent through the form and not using the link, it would be better if you change your route to
Route::post('/products', 'ProductsController#store')->name('products.store');
now, we can have access to our value on the store function just by using
$client_id = $request->input('client_id')
Here is an alternative solution which works too:
ROUTES:
Route::get('/products/{client_id}/create', 'ProductsController#create')-name('products.create');
Route::post('/products', 'ProductsController#store')->name('products.store');
PRODUCTS CONTROLLER CREATE FUNCTION:
public function create() //keep as in the original question
{
// all your code ...
return view ('products.create')->with("data",$datas)->with("client_id",$request->client_id);
}
IN THE VIEW:
<form>
<!-- other inputs .... -->
<input type="hidden" value="{{Request::segment(2)}}" name="client_id" /> //access the URL segmment 2 which correspond to the client_id
</form>
IN THE PRODUCTS CONTROLLER STORE FUNCTION:
$client_id = $request->input('client_id')
It would have be nice to use the Request::segment(2) directly in the store function like this but this triggers a "Non-static method Illuminate\Http\Request::segment() should not be called statically" error that I have not been able to resolve. I don't need an answer to that, however an hint would be appreciated anyway. Maybe I'll post a new question if this has not been resolved already.

Is it possible to both dispatch an array of actions and also navigate from an ngrx effect?

I have an issue with one of my application's ngrx effects. I am basically trying to execute multiple actions using concatMap() AND navigate using the router store's go().
Here is the effect:
#Effect()
loadPersonalInfoAndSignin$: Observable<Action> = this.actions$
.ofType(session.ActionTypes.LOAD_PERSONAL_INFO)
.map((action: LoadPersonalInfoAction) => action.payload)
.do(sessionToken => {
localStorage.setItem('authenticated', 'true');
localStorage.setItem('sessionToken', sessionToken);
})
.switchMap(() => this.userAccountService
.retrieveCurrentUserAccount()
.concatMap(currentUserAccount => [
new LoadUserAccountAction(currentUserAccount),
new SigninAction(),
new LoadMessagesAction({})
])
)
.mapTo(go(['/dashboard']));
If I remove the .mapTo(go(['/dashboard'])), then all three actions in the concatMap array are successfully dispatched to their corresponding effects.
I am therefore wondering why my mapTo(go(... is causing the last two actions in the array (i.e. SigninAction & LoadMessagesAction) not to be dispatched to their corresponding effects..
Can someone please help?
edit: Changing mapTo to do as follows:
.do(go(['/dashboard']));
results in the following error:
ERROR in /Users/julien/Documents/projects/bignibou/bignibou-client/src/app/core/store/session/session.effects.ts (55,9): Argument of type 'Action' is not assignable to parameter of type 'PartialObserver<SigninAction>'.
Type 'Action' is not assignable to type 'CompletionObserver<SigninAction>'.
Property 'complete' is missing in type 'Action'.
Using do for the go call will not see the route changed. go is an action creator and the action that it creates needs to be emitted from the effect for #ngrx/router-store to receive the action and effect the route change.
Also, the mapTo operator will ignore what it receives and will emit the value you've specified, so it's not appropriate, either.
Instead, you should include the action created by the go call in your concatMap array:
#Effect()
loadPersonalInfoAndSignin$: Observable<Action> = this.actions$
.ofType(session.ActionTypes.LOAD_PERSONAL_INFO)
.map((action: LoadPersonalInfoAction) => action.payload)
.do(sessionToken => {
localStorage.setItem('authenticated', 'true');
localStorage.setItem('sessionToken', sessionToken);
})
.switchMap(() => this.userAccountService
.retrieveCurrentUserAccount()
.concatMap(currentUserAccount => [
new LoadUserAccountAction(currentUserAccount),
new SigninAction(),
new LoadMessagesAction({}),
go(['/dashboard'])
])
);

Use VBO in Drupal to update custom user field

I want to perform bulk update of users with a Approved users, the table
field_user_status_value
-----------------------
entity_type, entity_id, field_user_status_value
The entity_id is the user id which does not exist in the table, below is the custom module I wrote to update the table:
function bulkapprove_action_info() {
return array(
'bulkapprove_action_callback_name' => array(
'type' => 'user', // Can be file, term, user, etc.
'label' => t('Approve User'),
'configurable' => FALSE, // Doesn't need config form
'behavior' => array('view_property'), // Uses view access rights ,
'pass rows' => TRUE,
'triggers' => array('any'), // Works always
),
);
}
function bulkapprove_action_callback_name($entity, $context)
{
db_update('field_data_field_user_status')->fields(array('field_user_status_value' => 'Approved'))->condition('entity_id', $context->entity_id)->execute();
}
But it is not inserting the values in this table
In Drupal you do not want to update the database fields directly unless you created the table. Drupal's internal APIs provide a collection of tools to ensure you update the values correctly and that all supporting modules get notified of changes as needed through the hook system.
In this case the callback gets the actual entity to run your action against (in this case the user object). You want to take action on that entity and then save the entity.
function bulkapprove_action_callback_name($entity, $context)
{
$entity->status = 1;
entity_save('user', $entity);
}

Resources