I'm trying to use an angular service to store and communicate data through observable.
Here is my service
public _currentLegal = new BehaviorSubject({
name: 'init',
_id: 'init',
country: 'init',
city: 'init',
});
readonly currentLegal$ = this._currentLegal.asObservable();
constructor(private http: HttpClient) {
}
setCurrentLegal(legal) {
const legaltest = {
name: 'test',
_id: 'test',
country: 'test',
city: 'test',
}
console.log('emitting next value from', legal.name)
this._currentLegal.next({ ...legaltest });
}
I got a component that call setCurrentLegal, the console.log is triggered and correct. I then navigate to another component which subscribe to the currentLegal$ observable and the value is still the same (init) !
I tried accessing the value directly by setting public type and using getValue(), same.
I tried duplicating the object as to not pass a reference, did not change anything.
Here is my subscripting component ngOnInit :
ngOnInit(): void {
this.legalToUpdate = this.legalService.currentLegal$.subscribe((legal) => {
console.log(legal)
})
}
What's wrong here ?
Thanks
You have this behavior because you use it in different modules and it is created a different instances.
Put your service to be provide in root:
#Injectable({ providedIn: 'root' })
export class LegalService implements HttpInterceptor { }
Related
I'm fairly new to Angular and I'm trying to get only certain values from the Http response object.
In my service, I'm doing a get request to fetch weather data for a given city like so:
export class CityService {
private baseUrl = 'https://api.openweathermap.org';
constructor(private http: HttpClient) { }
getCity(name: string){
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`);
}
}
Now, in the component, I'm logging out the response like so:
ngOnInit(): void {
this.cityService.getCity('lucija').subscribe(data => {
console.log(data)
});
}
The response itself it's just one object with many fields (also nested ones), which most of them I do not need.
I've also set up an interface where I would like to "save" in those certain response fields:
export interface City {
name: string;
description: string;
icon: string;
main: object;
search: string;
}
How can I do that? Cheers!
EDIT:
Based on the answer below I got 2 errors, which I resolved like so:
getCity(name: string): Observable<City>{
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`)
.pipe(map((res: any) => <City>{
name: res.name,
description: res.weather[0].description,
icon: `http://openweathermap.org/img/w/${res.weather[0].icon}.png`,
main: res.main,
search: name
}));
One solution could be to map the object in your service. Then your service will return the City Object.
export class CityService {
private baseUrl = 'https://api.openweathermap.org';
constructor(private http: HttpClient) { }
getCity(name: string): Observable<City>{
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`)
.pipe(map((res) => { return { name: res.cityName }; }); // only added name as example. In the final code map all the values to the correct City field.
}
}
If you do not want your service to always return the City object you can do this mapping in your component.
I have my service as PortAllocationService below
#Injectable({
providedIn: 'root'
})
export class PortAllocationService {
businessSwitchName: any;businessSwitchIp: any;businessSwitchportName: any;
businessSwitchNodeId: any;routerName: any;routerIp: any;routerDetailsportName: any;
routernodeID: any;aggSwitchName: any;aggSwitchPort: any;aggNodeIP: any;
aggNodeId: any;serviceName: any;convertorDetails: any;handoffPort: any;qosLoopingPort: any;
constructor(private http:HttpClient) { }
portService(da){
return this.http.post(url.portAllocationUrl , da).
subscribe ((response:any) => {
//Port Allocation response is mapped here
console.log(response);
// businessSwitchDetails
this.businessSwitchName = response.businessSwitchDetails.nodeName;
this.businessSwitchIp = response.businessSwitchDetails.nodeIP;
});
}
and my component as below
export class WimaxFormComponent {
data : any = {};
btsIp :any; vCategory:any;nVlan:any;
businessSwitchName:any;businessSwitchIp:any;businessSwitchportName:any;
routerName:any;routerIp:any;aggSwitchName:any;aggSwitchPort:any;
routerDetailsportName:any;routernodeID:any;aggNodeIP: any;aggNodeId: any;
businessSwitchNodeId: any;serviceName: any;convertorDetails: any;
handoffPort: any;qosLoopingPort: any;
serviceId: any;serviceType: any;customerName: any;
vReservedValue:boolean;cVlan: string;sVlan: any;
path: string;
constructor(
private service: PortAllocationService){
}
onChange(){
let portAllocationData = {
"serviceAttribute": {
"serviceType": this.serviceType,
"category": "category",
"name": this.serviceId
},
"btsIp": this.btsIp
}
console.log(portAllocationData);
this.service.portService(portAllocationData);
}
When i call the onChange function the call is made to service and we get the response from server . But i want to access all variable values from my service to Component like for example i have tried in constructor and onChange both as
this.businessSwitchName = service.businessSwitchName // this is coming as undefined
Could you please let me know how to access the variable values into component.
Rather than subscribing to the API call in the service itself, I would simply return the observable made by http.post(). And then move the subscribe to the component itself:
Service:
portService(da){ return this.http.post(url.portAllocationUrl , da) });
Component:
this.service.portService(portAllocationData).subscribe(response => {
// Do stuff with the response here
});
However, if you really want to keep the variables in the service, I would put the API call in the constructor of the service, change the fields in the service to start with an underscore (or some other local/private identifier) and then have get methods for each variable you want to be able to access.
Service:
get businessSwitchName() {
return this._businessSwitchName;
}
Component:
this.service.businessSwitchName
In an angular 5 app, there is a route guard that check from an API if an object exists:
//guard.ts excerpt
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
return this.clientService.get(next.params.id).switchMap( data => {
return Observable.of(true);
})
.catch( err => Observable.of(false))
}
//route.ts excerpt
{ path: ':id', canActivate: [ ClientDetailGuard ], component: ClientDetail }
this works perfect, but I am wondering if is there a way to pass the data retrieved from my service to next the route/component (ClientDetail), so I won't need to call the service again this again.
I tried to add
next.data.client = data;
before the return of Observable(true) but in the component, the ActivatedRoute's data does not have this value set.
Or should I use something like Resolve?
I know I can achieve this using some state container or a shared service to store/retrieve data, but I wouldn't like to do this at this time, as long as the app is not complex.
I could do this using a Resolver instead of a guard
//route.ts
{ path: ':id', resolve: { client: ClientDetailResolver }, component: ClientDetail }
//resolver.ts
#Injectable()
export class ClientDetailResolver implements Resolve {
constructor(private clientService: ClientService, private router: Router, public location: Location) {
}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any>|Promise<any>|any {
return this.clientService.get(route.params.id)
.catch( err => {
//handle error
const path = this.location.path();
this.router.navigate(["error", err.status], { skipLocationChange: true })
.then( () => {
this.location.replaceState(path);
});
return Observable.empty();
})
}
}
You seem to be under-estimating the power of services. Services are the best way to save/store data or states between components. You can set the data from any component, pull the data from any component. You don't have to worry about putting data in for the next route, instead you go to the next route and subscribe to your data on ngOnInit and boom, got everything you need. Here is an example of just how simple it really is.
Example of service
import { Injectable } from '#angular/core';
import { BehaviorSubject, Subscription } from 'rxjs';
#Injectable()
export class AppService {
alertData = new BehaviorSubject({
type: '',
message: '',
timer: 0,
showing: false
});
constructor() {}
setAlertData(data: AlertModel) {
this.alertData.next(data);
}
}
Example of using service
this.subscription$.push(this._as.alertData.subscribe((data: AlertModel) => {
this.alertData = data;
if (data.showing) {
this.alertIsShowing = true;
}
else {
this.alertIsShowing = false;
}
}));
I'm working on a simple project using nodejs and angular 2.
In my client project I have a component which has a form and a submit event. When I throw this event all data from the form is properly sent to my node application.
But I have another component which has a table where I list all registers located on my database.
Everything works fine. But I should make my table load in the same time after I've sent my submit.
I have no idea how I can do this.
I tried implement something like this:
export class EmployeeListComponent implements OnInit {
public employees: string[];
constructor(private employeeService : EmployeeService) {
this.employeeService.employeeRequest().subscribe(employees => {
this.employees = employees;
})
}
ngOnInit() {
}
}
And in my register component I did this method:
onRegister(){
const Employee = {
name : this.name,
familyName: this.familyName,
participation: this.participation
}
if (!this.validateService.validateRegister(Employee)){
this.alertMsg = JSON.stringify({'msg': 'All fields must be filled'});
console.log(this.alertMsg);
return false;
} else {
this.registerService.employeeRegister(Employee).subscribe(data => {
this.alertMsg = JSON.stringify({'msg': 'Employee registered successfully'});
console.log(this.alertMsg);
return this.router.navigate(['/list']);
});
};
};
My tests show that my code works fine, but my table just load properly at first submit. After that I have to refresh manually the browser to load my table again.
Is there someone who know what I'm doing wrong or can tell me some way to code that?
Try to change your constructor to a function, and than you call it on ngOninit, so every time your list component initialize your list of employees will load again, something like this:
export class EmployeeListComponent implements OnInit {
public employees: string[];
constructor(private employeeService : EmployeeService) { }
ngOnInit() {
this.loadAllEmployees();
}
loadAllEmployees() {
this.employeeService.employeeRequest().subscribe(data => {
this.employees = data;
});
}
}
Also see Angular life cycle hooks documentation to understand more about ngOnInit and others:
https://angular.io/guide/lifecycle-hooks
Well. After I got the suggestions I tried another strategies. First I've simplify my two components as a single component. Then I moved my logic from the constructor to a method called dashCall() which I call inside my constructor and in the final of onRegister() method.
The final code looks like this:
export class EmployeeRegisterComponent {
public name: string;
public familyName: string;
public participation: number;
public alertMsg: any;
public employees: string[];
constructor(private validateService : ValidateService,
private registerService : EmployeeService,
private employeeService : EmployeeService) {
this.dashCall();
}
onRegister(){
const Employee = {
name : this.name,
familyName: this.familyName,
participation: this.participation
}
if (!this.validateService.validateRegister(Employee)){
this.alertMsg = JSON.stringify({'msg': 'All fields must be filled'});
return false;
} else {
this.registerService.employeeRegister(Employee).subscribe(data => {
this.alertMsg = JSON.stringify({'msg': 'Employee registered successfully'});
this.dashCall();
});
};
};
dashCall(){
this.employeeService.employeeRequest().subscribe(employees => {
this.employees = employees;
});
}
}
I know it's not the best way to implement this. But in the end I got what I needed.
i have a trouble.
here i let my code.
https://gist.github.com/anonymous/b651408a8419f13a949d719e6b87d8ea
in my app i connect to the firebase cloud message service, in the appComponent i listen the messages that firebise send and emit the data content whit the DataInterchage.service, in the chatComponent i suscribe to the event emited and i process the data.
the problem is the next. when I receved the data, i set the this.messeges variable the data content but the view dont update.
what do you believe that be?
when you set this.messages your code might be running outside the angular because that code is written in service callback. that is why when you assign values to variable it doesn't update the view.
try running code inside the angular NgZone. after that your view will be updated successfully.
for your code snippet will be
import {NgZone,ChangeDetectorRef} from "#angular/core";
export class ChatComponent implements OnInit{
constructor(
private zone: NgZone,
private cd: ChangeDetectorRef,
) {}
ngOnInit() {
this.user = JSON.parse(appStorage.getString("user_info"));
this.me = {
id: this.user.id,
name: this.user.full_name,
pictureUrl: this.user.icon
};
this.other = {
id: "",
name: "",
pictureUrl: "",
coverUrl: ""
};
this.emitter.msgRecived$
.subscribe(data => {
data = JSON.parse(data);
this.http.get(`${ env['api_route'] }/api/users/${ data.user }`)
.subscribe((res: Response) => {
let user = res.json().data;
this.other = {
id: user.id,
name: user.full_name,
pictureUrl: user.icon,
};
this.zone.run(()=>{
this.messages.push({
sender: this.other,
content: data.message,
date: data.date
});
});
console.dump(this.other);
console.dump(this.messages)
}, (err: Response) => {
this.oauth.isLogged(err);
});
});
}
}