I'm trying to use NgRx/effects for my material autocompleted in angular 8.
I have created store, action, effects and reducers but I'm not getting the state after successfully calling the api. The api is returning the correct value.
Action file
import { Action } from '#ngrx/store';
import {IProviderSearchObject} from '../../models/provider.type';
export enum ProviderSearchActionTypes {
SearchProvidersRequest = '[SEARCH_PROVIDER] REQUEST',
SearchProvidersSuccess = '[SEARCH_PROVIDER] SUCCESS',
SearchProvidersFail = '[SEARCH_PROVIDER] FAILED'
}
export class ProviderSearchAction implements Action {
type: string;
payload: {
isRequesting: boolean,
providers: Array<IProviderSearchObject>,
error: boolean,
searchPhrase: string
};
}
export class SearchProvidersRequest implements Action {
readonly type = ProviderSearchActionTypes.SearchProvidersRequest;
constructor(readonly payload: {isRequesting: boolean, searchPhrase: string}) {}
}
export class SearchProvidersSuccess implements Action {
readonly type = ProviderSearchActionTypes.SearchProvidersSuccess;
constructor(readonly payload: {isRequesting: boolean, providers: Array<IProviderSearchObject>}) {}
}
export class SearchProvidersFail implements Action {
readonly type = ProviderSearchActionTypes.SearchProvidersFail;
constructor(readonly payload: {error: boolean}) {}
}
export type ProviderSearchActions = SearchProvidersRequest | SearchProvidersSuccess | SearchProvidersFail;
reducer file
import {IProviderSearchObject} from '../../models/provider.type';
import {ProviderSearchAction, ProviderSearchActionTypes} from '../actions/provider-search.action';
export interface IProviderSearchState {
isRequesting: boolean;
providers: Array<IProviderSearchObject> | null;
error: boolean;
}
const initialProviderSearchState: IProviderSearchState = {
isRequesting: false,
providers: null,
error: false
};
export function providerSearchReducer(state: IProviderSearchState = initialProviderSearchState, action: ProviderSearchAction): IProviderSearchState {
console.log(action, state);
switch (action.type) {
case ProviderSearchActionTypes.SearchProvidersRequest:
return {
isRequesting: true,
providers: null,
error: false
};
case ProviderSearchActionTypes.SearchProvidersSuccess:
return {
isRequesting: false,
providers: action.payload.providers,
error: false
};
case ProviderSearchActionTypes.SearchProvidersFail:
return {
isRequesting: false,
providers: null,
error: true
}
default:
return state;
}
}
import {ActionReducerMap, MetaReducer} from '#ngrx/store';
import {IProviderSearchState, providerSearchReducer} from './provider-search.reducer';
export interface IAppState {
providerSearch: IProviderSearchState;
}
export const reducers: ActionReducerMap<IAppState> = {
providerSearch: providerSearchReducer
};
export const selectProviderSearch = (state: IAppState) => state.providerSearch.providers;
export const metaReducers: MetaReducer<any>[] = [];
Effects file
import {Actions, Effect, ofType} from '#ngrx/effects';
import {IAppState} from '../reducers';
import {ProviderSearchService} from '../../modules/provider-search/services/provider-search.service';
import {Store} from '#ngrx/store';
import {ProviderSearchActionTypes, SearchProvidersSuccess, SearchProvidersFail} from '../actions/provider-search.action';
import {catchError, map, switchMap} from 'rxjs/operators';
import { of } from 'rxjs';
import {IProviderSearchObject} from '../../models/provider.type';
import {Injectable} from '#angular/core';
#Injectable()
export class ProviderSearchEffects {
constructor(private actions$: Actions,
private store: Store<IAppState>,
private providerSearchService: ProviderSearchService) {}
#Effect()
searchProvider$ = this.actions$
.pipe(
ofType<any>(ProviderSearchActionTypes.SearchProvidersRequest),
map(action => action.payload),
switchMap((action) => {
return this.providerSearchService.getProviderByPhrase(action.searchPhrase).pipe(
map((data: Array<IProviderSearchObject>) => new SearchProvidersSuccess({isRequesting: false, providers: data})),
catchError(error => of(new SearchProvidersFail({error: true})))
);
})
);
}
import { ProviderSearchEffects } from './provider-search.effects';
export const effects: Array<any> = [ProviderSearchEffects];
Service file
import { Injectable } from '#angular/core';
import {HttpClient} from '#angular/common/http';
import {environment} from './../../../../environments/environment';
import { Store } from '#ngrx/store';
import * as ProviderSearchAction from '../../../store/actions/provider-search.action';
import {IAppState} from '../../../store/reducers';
import {Observable} from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ProviderSearchService {
constructor(
private http: HttpClient,
private store: Store<IAppState>
) { }
public getProviderByPhrase = (searchPhrase: string): Observable<any> => {
return this.http.get('https://mydummyapi.com?q=' + searchPhrase);
}
public searchProviderByTermSearch = (searchPhrase: string): any => {
return this.store.dispatch(new ProviderSearchAction.SearchProvidersRequest({isRequesting: true, searchPhrase}));
}
}
Component file
ngOnInit() {
this.providerSearchControl.valueChanges
.pipe(
debounceTime(500),
tap(() => {
this.isLoading = true;
}),
switchMap((value: string) => this.providerSearchService.searchProviderByTermSearch(value))
.pipe(
finalize(() => {
this.isLoading = false;
})
)
)
)
.subscribe((data: Array<IProviderSearchObject>) => {
console.log(data);
if (data && data.length > 0) {
this.providerSearchResult = data;
}
});
}
When the user start typing the autocomplete field then searchProviderByTermSearch method is invoked inside the service file and that dispatches the action.
But after [SEARCH_PROVIDER] SUCCESS call is made nothing is happening.
A store.dispatch is a void, it does not return a value.
The data from the subscribe code, is not an Array<IProviderSearchObject>.
Your flow should be:
dispatch fetch
call service in effects
dispatch fetch success/failure
update state via reducer
read data with selectors
update component
https://ngrx.io/guide/store#diagram
Related
I am new to angular 7 and didn't find any proper answer for similar questions posted.
I am getting Property 'subscribe' does not exist on type 'void' in angular-cli. I tried importing subscribe from rxjs but didn't find that library.
The problem is in the UpdateRecord Function!
product.component.ts code:
the code bellow is exist in compoent.ts of product
import { Component, OnInit } from '#angular/core';
import { ProductService } from 'src/app/shared/product.service';
import { NgForm } from '#angular/forms';
import { ToastrService } from 'ngx-toastr';
import { filter, map } from 'rxjs/operators';
#Component({
selector: 'app-product',
templateUrl: './product.component.html',
styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
constructor(private service : ProductService, private toastr : ToastrService) { }
ngOnInit() {
this.resetForm();
}
resetForm(form?: NgForm) {
if (form != null)
form.resetForm();
this.service.formData = {
ProductID: null,
ProductName: '',
ProductDescription: '',
Price: 0.00,
Image: '',
Qte: null
}
}
onSubmit(form: NgForm) {
if (form.value.ProductID == null)
this.insertRecord(form);
else
this.updateRecord(form);
}
insertRecord(form: NgForm) {
this.service.postProduct(form.value).subscribe(res => {
this.toastr.success('Inserted successfully', 'Product. Register');
this.resetForm(form);
this.service.refreshList();
});
}
updateRecord(form: NgForm) {
this.service.putProduct(form.value).subscribe(res => {
this.toastr.success('Updated successfully', 'Product. Update');
this.resetForm(form);
this.service.refreshList();
});
}
}
product.service.ts code :
the code bellow is exist in service file related to product
import { Injectable } from '#angular/core';
import { Product } from './product.model';
import { HttpClient } from "#angular/common/http";
#Injectable({
providedIn: 'root'
})
export class ProductService {
formData : Product
list : Product[]
readonly rootURL= 'http://localhost:50369/api'
constructor(private http : HttpClient) { }
postProduct(formData : Product){
return this.http.post(this.rootURL+'/Product', formData);
}
refreshList(){
return this.http.get(this.rootURL+'/Product')
.toPromise().then(res => this.list = res as Product[]);
}
putProduct(formData : Product){
this.http.put(this.rootURL+'/Product/'+formData.ProductID,FormData);
}
}
Thanks in advance,
I missed return :
So in putProduct function in product.service.ts is updated to be :
putProduct(formData : Product){
return this.http.put(this.rootURL+'/Product/'+formData.ProductID,FormData);
}
And it's working now!
Your HttpClient.put function seems to be incorrectly used (you are passing the class as parameter when you should be passing the object).
Look for the function updateHero() in this StackBlitz example.
/** PUT: update the hero on the server. Returns the updated hero upon success. */
updateHero (hero: Hero): Observable<Hero> {
httpOptions.headers =
httpOptions.headers.set('Authorization', 'my-new-auth-token');
return this.http.put<Hero>(this.heroesUrl, hero, httpOptions)
.pipe(
catchError(this.handleError('updateHero', hero))
);
}
Taking again the tutorial of the site Angular, I created in winamp a database with a table including a field {"id": id, "name": name} and I make 2 queries on this table with Symfony4:
1) A request to list heroes.
2) A request to create hero.
Executed from Angular 7, the query 1) works perfectly (route / listerHeroes).
Executed from Angular 7, query 2) does not work, it returns error 405 (route / ajouterHero). However launched from Postman, this query works.
I can not find any documentation to explain to me this bug on which I stumble for several days. A track please
Below copy of both classes: heroes.service.ts and component3.component.ts
// heroes.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { HttpErrorHandler, HandleError } from './http-error-handler.service';
import { Hero } from '../assets/Structure';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
#Injectable({ providedIn: 'root' })
export class HeroesService {
heroesUrl = 'http://heroes/';
private handleError: HandleError;
constructor(private http: HttpClient, httpErrorHandler: HttpErrorHandler) {
this.handleError = httpErrorHandler.createHandleError('HeroesService');
}
getHeroes$(): Observable<Hero[]> {
return this.http.get<Hero[]>(`${this.heroesUrl}listerHeroes`, httpOptions);
}
addHero(hero: Hero): Observable<Hero> {
return this.http
.post<Hero>(`${this.heroesUrl}ajouterHero`, hero, httpOptions)
.pipe(catchError(this.handleError('addHero', hero)));
}
}
// component3.component.ts
import { Component, OnInit } from '#angular/core';
import { HeroesService } from '../heroes.service';
import { Hero } from '../../assets/Structure';
#Component({
selector: 'app-component3',
templateUrl: './component3.component.html',
styleUrls: ['./component3.component.css']
})
export class Component3Component implements OnInit {
heroes: Hero[];
editHero: Hero;
constructor(private heroesService: HeroesService) {}
ngOnInit() {
this.heroesService.getHeroes$().subscribe(res => (this.heroes = res));
}
addHero(name: string): void {
name = name.trim();
console.log('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF C3A name =', name);
if (!name) {
return;
}
const newHero: Hero = { 'id': 0, 'name': name } as Hero;
this.heroesService.addHero(newHero).subscribe(hero => {
console.log('GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG C3B hero= ', hero);
this.heroes.push(hero);
});
}
}
I found the solution:
The blocking was listed backend (Symfony4) which refused the pre-query OPTIONS. It was necessary to install and configure the bundle nelmio (https://github.com/nelmio/NelmioApiDocBundle) which allows the smooth running of the request.
I have a snackbar component which I have created like this!
import {Component, ViewEncapsulation, OnInit, OnDestroy} from '#angular/core';
import {
MatSnackBar,
MatSnackBarConfig,
MatSnackBarHorizontalPosition,
MatSnackBarVerticalPosition,
} from '#angular/material';
import { Subscription } from 'rxjs';
import { AuthenticationService } from "../../services/authentication.service";
#Component({
selector: 'snack-message',
templateUrl: './messages.component.html',
styleUrls: [ './mesaages.component.scss' ],
encapsulation: ViewEncapsulation.None
})
export class SnackBarMessages implements OnInit, OnDestroy {
action: boolean = true;
setAutoHide: boolean = true;
autoHide: number = 2000;
horizontalPosition: MatSnackBarHorizontalPosition = 'center';
verticalPosition: MatSnackBarVerticalPosition = 'bottom';
private showMessageSub: Subscription;
messageData: object;
addExtraClass: boolean = false;
constructor(public snackBar: MatSnackBar, public authenticationService: AuthenticationService) {
}
ngOnInit() {
this.messageData = this.authenticationService.getMessageData();
this.showMessageSub = this.authenticationService.getMessageListener()
.subscribe(data => {
this.messageData = data;
});
this.openMessageSnackBar(this.messageData);
}
openMessageSnackBar(data) {
let config = new MatSnackBarConfig();
config.verticalPosition = this.verticalPosition;
config.horizontalPosition = this.horizontalPosition;
config.duration = this.setAutoHide ? this.autoHide : 0;
this.snackBar.open(data.message, data.action, config);
}
ngOnDestroy() {
if (this.showMessageSub) {
this.showMessageSub.unsubscribe();
}
}
}
I have added subject and i have created a subscription which listens to the messageListener in my Authentication Service. Then i call next with my subjectListener with the data I want to pass. I am not getting any call on my component. I don't understand why ? This is my Service!
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Subject } from 'rxjs';
import { Router, ActivatedRoute } from '../../../node_modules/#angular/router';
#Injectable({
providedIn: 'root'
})
export class AuthenticationService {
private mesageListener = new Subject<object>();
private messageData: object;
getMessageListener() {
return this.mesageListener.asObservable();
}
getMessageData() {
return this.messageData;
}
createUser(email: string, password: string) {
const userData: AuthModel = {
email: email,
password: password
}
const requestPath = this.getModes()[`${this.navigatedFrom}`];
this.http.post(`http://localhost:3000/api/${requestPath}/signup`,userData)
.subscribe(response => {
this.messageData = {
message: 'Sign Up Successful. Please Login now!',
action: 'Ok! Got it.'
}
this.mesageListener.next(this.messageData);
});
}
}
Try to use new BehaviorSubject<object>(null); for mesageListener .
you can see more details on Subject and BehavoiurSubject here : angular2-difference-between-a-behavior-subject-and-an-observable/
In Component subscribe to the value of Observable. It should work.
I know that i can make a component and a service in Angular 2(Typescript) and can parse the response in component, but i want my architecture to be like below as for any changes in response i will have to change only my parser:
Service : Do API call and fetch response. Has a get response method.
Parser : Gets Response and parses response and returns response in a usable form to the component.
Component : gets Response from the parser and display data to view.
For doing it i am using an observable from service and subscribing it in parser. And then using an observable from parser and subscribing it in component.
But getting a lot of errors while doing so. Do i miss any steps or is it the right approach to the problem.
Adding Code :
Component:::
import { AuthenticateParser } from './../../parsers/authenticate.parser';
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'my-component',
moduleId: module.id,
templateUrl: './app.component.html',
providers : [AuthenticateParser]
})
export class AppComponent implements OnInit {
// Constructor Function
constructor(private _authenticationService: AuthenticateParser) {
}
ngOnInit(): void {
this._authenticationService.setAuthenticationToken();
}
}
Service :::
import { Observable } from 'rxjs/Observable';
import { IRequest } from './api.interaction.interface';
import { Http, Headers, RequestOptions, Response } from '#angular/http';
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
#Injectable()
export class ApiInteractionService {
// Constructor Function
constructor(private _http: Http) {
}
getApiResponse(p_attr: IRequest): Observable<Response> {
// Set request url
let _authenticationUrl: string = p_attr.url;
// Set request body
let _body: any = p_attr.body;
// Set content type to JSON
let _headers = new Headers(p_attr.header);
// Set a request option
let _options = new RequestOptions({ headers: _headers });
return this._http[p_attr.method](_authenticationUrl, _body, _options)
.map((response: Response) => response.json()) ///... change response to JSON format
.do((data:any) => console.log('All : ' + JSON.stringify(data))) //... console response
.catch((error: any) => Observable.throw(error.json().error || 'Server error')); //...errors if any
}}
PARSER :::
import { Injectable, Component } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { IRequest } from './../services/api.interaction.interface';
import { ApiInteractionService } from './../services/api.interaction.service';
#Injectable()
export class AuthenticateParser {
private _authenticationToken: string = '';
// Constructor Function
constructor(private _authenticationService: ApiInteractionService) {
}
// Fetches Authentication Token
setAuthenticationToken(): void {
let p_url: string = 'http://10.5.214.82:8443/auth/authenticate';
let p_body: Object = {
"userId": "admin",
"password": "admin"
};
let p_header: Object = {
"Content-Type": "application/json"
};
let p_attrs: IRequest = {
'method': 'post',
'url': p_url,
'body': p_body,
'header': p_header
};
this._authenticationService.getApiResponse(p_attrs)
.subscribe(
response => this.parseSuccessResponse(response),
error => this.parseErrorResponse(error)
);
}
// Parses Token response and sets it.
parseSuccessResponse(p_response: any): void {
this._authenticationToken = p_response.data;
}
// Parses Error response and sets it.
parseErrorResponse(p_error: any): void {
this._authenticationToken = p_error.data;
}
// returns authentication token
getAuthenticationToken():string {
return this._authenticationToken;
}
}
Sounds like you want to make a parser as a service. Would have helped if you gave example code.
In your parser, you can subscribe to the http.get response, and then parse the response, and then publish the result to a subject. Then in your component you subscribe to the subject from parser.
Your parser will be something like:
import { Injectable, OnInit} from '#angular/core';
import { Subject } from 'rxjs/Subject';
import {YourService} from '....''
#Injectable()
export class ParseService implements OnInit {
public parseSubject: Subject<any> = new Subject <any>();
constructor(private service: YourService) {}
ngOnInit(){
this.service.yourGetFunction()
.map(res=> res.json())
.subscribe(
response => {
parse logic that stores result in parsedObj
parseSubject.next(parsedObj);
},
error => { this.errorMessage = <any>error});
}
}
Now in your component, you can import ParseService and subscribe to its parseSubject to get the parsed output
I want to access the property 'Status' from the controller and simply do some operations but I am unable to get this property and do any further operation. I am sharing my code below:
TasksController:
[HttpGet]
public ActionResult GetTasks()
{
var q = (from a in db.Tsk
join b in db.TType on a.TaskTypeID equals b.TaskTypeID
join c in db.Prior on a.PriorityID equals c.PriorityID
join d in db.Usr on a.AssignedTo equals d.Employees.EmpName
select new
{
a.TaskID,
a.TaskCode,
a.AssignedTo,
a.Date,
a.DueDate,
a.Status,
a.Reply,
a.PriorityID,
a.TaskTypeID,
b.TaskType,
c.Priorities,
d.Login
}).ToList().Skip(1).AsEnumerable();
db.Configuration.ProxyCreationEnabled = false;
return Json(q, JsonRequestBehavior.AllowGet);
}
AppService:
import { Injectable } from '#angular/core';
import { Http, Headers, Response } from '#angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
import { Observable } from 'rxjs/Observable'
#Injectable()
export class AppService {
constructor(private _http: Http) { }
//Task Register
getFTRs(c: string) {
return this._http.get('Tasks/GetTasks').map(res => res.json().filter(a => a.Login === c));
}
}
HomeComponent:
import { Component, OnInit, Input } from '#angular/core';
import { AuthenticationService } from '../_services/index';
import { AppService } from '../app.service';
import { LoginComponent } from '../login/index';
import { User, TaskRegisters } from '../contract';
import { Message } from '../message';
#Component({
moduleId: module.id,
selector: 'home',
templateUrl: 'home.component.html',
providers: [LoginComponent]
})
export class HomeComponent implements OnInit {
users: User[];
tasks: string;
msgs: Message[] = [];
curr: any;
constructor(private userService: AuthenticationService,
private Tsk: AppService,
private Log: LoginComponent) { }
ngOnInit() {
debugger;
this.curr = localStorage.getItem('currentUser').slice(1);
this.Tsk.getFTRs(this.curr).subscribe(
res => {
this.tasks = res.Status,
error => console.log(error)
});
if (this.tasks) {
this.msgs.push({ severity: 'error', summary: 'Task Assigned', detail: 'A new task has been assigned to you' })
}
}
}
Status is a boolean and if boolean is true, I want to push the message in msgs array. I am unable to get the value of Status and store it in tasks variable of home component. Whenever I run the program it shows this.tasks as undefined thus making and comparison impossible. Any help will be appreciated.
Change:
this.Tsk.getFTRs(this.curr).subscribe(
res => {
this.tasks = res.Status,
error => console.log(error)
});
if (this.tasks) {
this.msgs.push({ severity: 'error', summary: 'Task Assigned', detail: 'A new task has been assigned to you' })
}
to
this.Tsk.getFTRs(this.curr).subscribe(
(res) => {
console.log(res); //What does this print?
this.tasks = res.Status;
console.log(this.tasks); //What does this print?
if (this.tasks) {
this.msgs.push({ severity: 'error', summary: 'Task Assigned', detail: 'A new task has been assigned to you' })
}
},
(error) => {console.log(error);}
);
Since you're assingning this.tasks inside getFRSs' callback which is async, by the time you are using it below in the if statement it is undefined.
Since this.tasks is available to me now after the edit of #echonax I did this to make it work!
ngOnInit() {
debugger;
this.curr = localStorage.getItem('currentUser').slice(1);
this.Tsk.getFTRs(this.curr).subscribe(
res => {
this.tasks = res;
for (let i = 0; i < this.tasks.length; i++){
if (this.tasks[i].Status) {
this.msgs.push({ severity: 'error', summary: 'Task Assigned', detail: 'A new task has been assigned to you' })
}
}
},
error => console.log(error)
)}