Post/Get data from service doesn't work like it should - javascript

To pass data around few components I created a service:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class Services {
private messageSource = new BehaviorSubject<string>("lol");
currentMessage = this.messageSource.asObservable();
constructor() {
}
changeMessage(message: string) {
this.messageSource.next(message);
console.log(message);
}
}
I create string messagesource for contain the passing data, and current message for Observable. And after that I create function to change this data.
So now, In ngOnInit() in one of component, I write:
this.data.currentMessage.subscribe(message => this.message = message);
and define in component message:string save there message.
In function I make:
selectNews(news) {
this.data.changeMessage("lol3"); // string for test
}
And after that, I want to get this string in other component
So again in ngOnInit, I copy the same line and define the message. But the consolelog here show me the string before function make action....
So were is the problem?
Edit
Fiddle with all whole code :
https://jsfiddle.net/fywdzz51/

you are providing the service separately for each component. this creates different instances in each component. you should provide it in one place for the whole module

Related

How to pass data between sibling components in Angular, by 'sending the message/calling the function' from ngOnInit()?

I want to send message from ComponentA to ComponentB. And I am using service for that. Here is the code-
My problem is if I call sendInfoToB() from componentA's html file (event hooked to a button click), then I'm recieving the message in componentB /getting
console.log(this.check); and console.log(message); 's values in the console.. but when I call this.sendInfoToB(true); from componentA's ngOnInit() then I get no message in console for componentB. I want the message when I call from componentA's ngOnInit. How can I achieve that? Please help. Thank you in advance.
ComponentA.ts
constructor(private siblingService: SiblingService,) {}
public dataX: boolean = false;
someFunc(){
//some calculation returning true
this.dataX = true;
}
ngOnInit() {
this.someFunc();
this.sendInfoToB();
}
sendInfoToB() {
this.siblingService.communicateMessage(dataX);
}
message.service.ts
export class SiblingService {
sendMessage = new Subject();
constructor() {}
communicateMessage(msg) {
this.sendMessage.next(msg);
}
}
ComponentB.ts
constructor(private sibService: SiblingService,) {}
ngOnInit() {
this.sibService.sendMessage.subscribe((message) => {
this.check = message;
console.log(this.check);
console.log(message);
});
}
More than likely this has to do with order of operations. Component A is created and sends the message, then Component B gets created and subscribes to new messages. Since you are using a Subject it only gives you messages sent after you subscribe to it.
The easiest strategy to this is to use a BehaviorSubject.
export class SiblingService {
sendMessage = new BehaviorSubject();
constructor() {}
communicateMessage(msg) {
this.sendMessage.next(msg);
}
}
The other way is to send the message after the init happens, like on a click event.

Javascript Variable Name as Json Object Name

First of all the question title doesn't make sense. Im not sure how can I ask the question in a better way.
Im facing an issue in an Angular 8.1 project. There is a josn file Im importing that in the settings Class (its a service).
When the environment.ts file have a variable as app_company. the same name a json file also available. So if the client app_company matches the json file. all the config should be loaded from that file. This is what I need to achieve
What I tried so far is .
import { Injectable } from '#angular/core';
import { environment } from '#env/environment';
import * as client1 from './client1.json';
#Injectable({
providedIn: 'root'
})
export class SettingsService {
newInstance:object;
constructor() { }
config(key : string){
// const newInstance = Object.create(window[environment.app_company].prototype);
// newInstance.constructor.apply(newInstance);
// return newInstance.key;
// const newInstance: any = new (<any>window)[environment.app_company];
// console.log(Object.create(window[environment.app_company]));
return environment.app_company.key;
}
}
my json file will look like below
{
"opNumberLabel" : "OP No"
}
So in the return section if I call client1.opNumberLabel
It works like my expectation , but I try to make that dynamic like environment.app_company.key it not working .
As you can see my tried are commented those are not worked so far :(.
Any hint will highly appreciated .
Thanks in advance,
Finally I resolved.
export class SettingsService {
clients = { client1 , client2 };
app_company :string;
default_client = 'client1';
constructor() { }
config(label : string){
this.app_company = environment.app_company;
if(this.clients[this.app_company].default.hasOwnProperty(label)){
return this.clients[this.app_company].default[label];
}else{
return this.clients[this.default_client].default[label];
}
}
}
Thanks to #Titus for the hint.
Would you like to achieve something like that?
import { Injectable } from '#angular/core';
import * as settings from "./environment.json";
interface Settings {
[key: string]: string
}
type Environment = {
[key: string]: Partial<Settings>
};
#Injectable({
providedIn: 'root'
})
export class SettingsService {
private environment: Environment = {
app_company: { }
};
constructor() {
this.environment.app_company = settings;
console.log(this.config("opNumberLabel")); // OP No
}
config(key : string){
return this.environment.app_company[key];
}
}
{
"opNumberLabel" : "OP No",
"settings1": "testSettings1",
"settings2": "testSettings2"
}
If you would like to have app_company as a dynamic value too, then I would suggest to extend the example:
by creating an init function which could also accept the name of
the client.
by passing an object to the SettingsService
constructor, with InjectionToken, so you can define what is the
current client you are trying to configure.
You might also want to have different environment setup for different clients, so I would suggest to create separate environment json files for each client.
You could create an object which would hold every <client>.environment.json with <client> key.
After that you have an object, like that:
const clientEnvironments: Environment = {
client1: { ... },
client2: { ... }
};
So by having the name of your current client, you can easily select which environment you want to use.

How to pass data from One component to other

I am new in angular working on a project.My problem is that i want to transfer data from one component to other. Actually i want to show data in text field from database and then have to update it. I have one component name ricerca.component.ts in which data in table is showing. now when i click on specific line(row) then data for that specific record i have to show in my other component name as generatecontract.comonent.ts. I don't know how to perform this.
I made a model name ContractDblist assign all these value to that model but unfortunatelly not solved the problem in other component
this is my ricercacomponnet code
if(tag === 'Item1'){
this.router.navigate(['/workflow/rigester' ]);
}
}
public lstContractRecordDbValue :any[];
getContractRecordbyParameter(selecteditem: any, index: number) { this.workFlowService.getContractRecordbyParameter(selecteditem).subscribe(data => {
this.lstContractRecordDbValue = data;
this.contractdblist.cnt_num=this.lstContractRecordDbValue[0].CNT_NUM;
this.contractdblist.contract=this.lstContractRecordDbValue[0].CONTRACT; this.contractdblist.contacttype=this.lstContractRecordDbValue[0].CONTRACT_TYPE; this.contractdblist.contractno=this.lstContractRecordDbValue[0].CONTRACT_NO;
this.loading = false;
}, error => {
console.error('getAllTickets', error);
this.loading = false;
})
}
You can use Subject to do that
import { Injectable } from "#angular/core";
import { Subject } from "rxjs";
#Injectable()
export class MessageService {
private subject = new Subject<any>();
constructor() {}
sendMessage(message: any) {
this.subject.next(message);
}
getData() {
return this.subject.asObservable();
}
}
So you can call MessageService class method sendMessage() to send data
I defined 2 method here. The first method using next() to send message to the next subcriber. So in your component you just need to simply subscribe like this to get the data
private subscription$: Subscription;
public ngOnInit(): void {
this.subscription$ = this.messageervice
.getData()
.subscribe(data => { console.log(data); })
}
public ngOnDestroy(): void {
this.subscription$.unsubscribe();
}
There are many ways to do so some of them are
using input decorator when passing data from parent to child component.
using output decorator with event emitter when passing data from child to parent component.
using subjects for components not related to each other.
using a service to set and get data to be passed.
you can refer their official website for all the above ways.

add data to the end of a behavior object array Angular 5

I have some data that I want to be shared with my entire app so I have created a service like so..
user.service
userDataSource = BehaviorSubject<Array<any>>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
then in my component Im getting some data from an api and then sending that data to userDataSource like so..
constructor(
private: userService: UserService,
private: api: Api
){
}
ngOnInit() {
this.api.getData()
.subscribe((data) => {
this.userService.updateUserData(data);
})
}
now that all works but.. I want to be able to add data to the end of the array inside the userDataSource so basically the equivalent of a .push am I able to just call the updateUserData() function and add more data or will doing that overwrite what is currently in there?
Any help would be appreciated
You can add a new method to your service like addData in which you can combine your previous data with new data like.
import {Injectable} from '#angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class UserService {
userDataSource: BehaviorSubject<Array<any>> = new BehaviorSubject([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(data);
}
addData(dataObj) {
const currentValue = this.userDataSource.value;
const updatedValue = [...currentValue, dataObj];
this.userDataSource.next(updatedValue);
}
}
For someone that may come accross this issue with a BehaviorSubject<YourObject[]>.
I found in this article a way to properly add the new array of YourObject
import { Observable, BehaviorSubject } from 'rxjs';
import { YourObject} from './location';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ObjService {
private theObjData: BehaviorSubject<YourObject[]> = new BehaviorSubject<YourObject[]>(null);
constructor() {
}
public SetObjData(newValue: YourObject[]): void {
this.theObjData.next(Object.assign([], newValue));
}
}
How to update data:
// inside some component
this.api.userData().subscribe((results:YourObject) =>
this.objService.SetObjData(results);
)
How to observe changes on other component
// inside another component
ngOnInit() {
this.objService.GetAccountStatements().subscribe((results) =>
...
)
}
Normally Observables and Subjects are meant to be streams of data, not an assignment of data. BehaviorSubjects are different because they hold their last emitted value.
Normally Subjects or BehaviorSubjects inside of a contained class (like a Service) do not want to expose themselves publicly to any other classes, so it's best practice to access their properties with getters or methods. This keeps the data stream cold to all subscribers.
However, since the BehaviorSubject holds the last emitted value, there's a few options here. If all subscribers need a concatenated stream of data from every emission, you could access the last emitted value and append to it:
userDataSource = BehaviorSubject<any[]>([]);
userData = this.userDataSource.asObservable();
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.push(data));
}
...or, in what might be considered better practice, Subscribers to this Subject could do their own transformation on the stream:
this.api.userData()
.scan((prev, current) => prev.push(current). [])
.subscribe((data) => {
this.concatenatedUserData = data;
});
Use concat to add object
userDataSource = BehaviorSubject<Array<any>>([]);
updateUserData(data) {
this.userDataSource.next(this.userDataSource.value.concat(data));
}
Use filter to remove object
removeUserData(data) {
this.userDataSource.next(this.userDataSource.value.filter(obj => obj !== data));
}

Change route params without reloading in Angular 2

I'm making a real estate website using Angular 2, Google Maps, etc. and when a user changes the center of the map I perform a search to the API indicating the current position of the map as well as the radius. The thing is, I want to reflect those values in the url without reloading the entire page. Is that possible? I've found some solutions using AngularJS 1.x but nothing about Angular 2.
As of RC6 you can do the following to change URL without change state and thereby keeping your route history
import {OnInit} from '#angular/core';
import {Location} from '#angular/common';
// If you dont import this angular will import the wrong "Location"
#Component({
selector: 'example-component',
templateUrl: 'xxx.html'
})
export class ExampleComponent implements OnInit
{
constructor( private location: Location )
{}
ngOnInit()
{
this.location.replaceState("/some/newstate/");
}
}
You could use location.go(url) which will basically change your url, without change in route of application.
NOTE this could cause other effect like redirect to child route from the current route.
Related question which describes location.go will not intimate to Router to happen changes.
Using location.go(url) is the way to go, but instead of hardcoding the url , consider generating it using router.createUrlTree().
Given that you want to do the following router call: this.router.navigate([{param: 1}], {relativeTo: this.activatedRoute}) but without reloading the component, it can be rewritten as:
const url = this.router.createUrlTree([], {relativeTo: this.activatedRoute, queryParams: {param: 1}}).toString()
this.location.go(url);
For anyone like me finding this question the following might be useful.
I had a similar problem and initially tried using location.go and location.replaceState as suggested in other answers here. However I ran into problems when I had to navigate to another page on the app because the navigation was relative to the current route and the current route wasn't being updated by location.go or location.replaceState (the router doesn't know anything about what these do to the URL)
In essence I needed a solution that DIDN'T reload the page/component when the route parameter changed but DID update the route state internally.
I ended up using query parameters. You can find more about it here: https://angular-2-training-book.rangle.io/handout/routing/query_params.html
So if you need to do something like save an order and get an order ID you can update your page URL like shown below. Updating a centre location and related data on a map would be similar
// let's say we're saving an order. Initally the URL is just blah/orders
save(orderId) {
// [Here we would call back-end to save the order in the database]
this.router.navigate(['orders'], { queryParams: { id: orderId } });
// now the URL is blah/orders?id:1234. We don't reload the orders
// page or component so get desired behaviour of not seeing any
// flickers or resetting the page.
}
and you keep track of it within the ngOnInit method like:
ngOnInit() {
this.orderId = this.route
.queryParamMap
.map(params => params.get('id') || null);
// orderID is up-to-date with what is saved in database now, or if
// nothing is saved and hence no id query paramter the orderId variable
// is simply null.
// [You can load the order here from its ID if this suits your design]
}
If you need to go direct to the order page with a new (unsaved) order you can do:
this.router.navigate(['orders']);
Or if you need to go direct to the order page for an existing (saved) order you can do:
this.router.navigate(['orders'], { queryParams: { id: '1234' } });
I had major trouble getting this to work in RCx releases of angular2. The Location package has moved, and running location.go() inside constructor() wont work. It needs to be ngOnInit() or later in the lifecycle. Here is some example code:
import {OnInit} from '#angular/core';
import {Location} from '#angular/common';
#Component({
selector: 'example-component',
templateUrl: 'xxx.html'
})
export class ExampleComponent implements OnInit
{
constructor( private location: Location )
{}
ngOnInit()
{
this.location.go( '/example;example_param=917' );
}
}
Here are the angular resources on the matter:
https://angular.io/docs/ts/latest/api/common/index/Location-class.html
https://angular.io/docs/ts/latest/api/common/index/LocationStrategy-class.html
I've had similar requirements as described in the question and it took a while to figure things out based on existing answers, so I would like to share my final solution.
Requirements
The state of my view (component, technically) can be changed by the user (filter settings, sorting options, etc.) When state changes happen, i.e. the user changes the sorting direction, I want to:
Reflect the state changes in the URL
Handle state changes, i.e. make an API call to receive a new result set
additionally, I would like to:
Specify if the URL changes are considered in the browser history (back/forward) based on circumstances
use complex objects as state params to provide greater flexibility in handling of state changes (optional, but makes life easier for example when some state changes trigger backend/API calls while others are handled by the frontend internally)
Solution: Change state without reloading component
A state change does not cause a component reload when using route parameters or query parameters. The component instance stays alive. I see no good reason to mess with the router state by using Location.go() or location.replaceState().
var state = { q: 'foo', sort: 'bar' };
var url = this.router.createUrlTree([], { relativeTo: this.activatedRoute, queryParams: state }).toString();
this.router.navigateByUrl(url);
The state object will be transformed to URL query params by Angular's Router:
https://localhost/some/route?q=foo&sort=bar
Solution: Handling state changes to make API calls
The state changes triggered above can be handled by subscribing to ActivatedRoute.queryParams:
export class MyComponent implements OnInit {
constructor(private activatedRoute: ActivatedRoute) { }
ngOnInit()
{
this.activatedRoute.queryParams.subscribe((params) => {
// params is the state object passed to the router on navigation
// Make API calls here
});
}
}
The state object of the above axample will be passed as the params argument of the queryParams observable. In the handler API calls can be made if necessary.
But: I would prefer handling the state changes directly in my component and avoid the detour over ActivatedRoute.queryParams. IMO, navigating the router, letting Angular do routing magic and handle the queryParams change to do something, completely obfuscates whats happening in my component with regards to maintenability and readability of my code. What I do instead:
Compare the state passed in to queryParams observable with the current state in my component, do nothing, if it hasn't changed there and handle state changes directly instead:
export class MyComponent implements OnInit {
private _currentState;
constructor(private activatedRoute: ActivatedRoute) { }
ngOnInit()
{
this.activatedRoute.queryParams.subscribe((params) => {
// Following comparison assumes, that property order doesn't change
if (JSON.stringify(this._currentState) == JSON.stringify(params)) return;
// The followig code will be executed only when the state changes externally, i.e. through navigating to a URL with params by the user
this._currentState = params;
this.makeApiCalls();
});
}
updateView()
{
this.makeApiCalls();
this.updateUri();
}
updateUri()
{
var url = this.router.createUrlTree([], { relativeTo: this.activatedRoute, queryParams: this._currentState }).toString();
this.router.navigateByUrl(url);
}
}
Solution: Specify browser history behavior
var createHistoryEntry = true // or false
var url = ... // see above
this.router.navigateByUrl(url, { replaceUrl : !createHistoryEntry});
Solution: Complex objects as state
This is beyond the original question but adresses common scenarios and might thus be useful: The state object above is limited to flat objects (an object with only simple string/bool/int/... properties but no nested objects). I found this limiting, because I need to distinguish between properties that need to be handled with a backend call and others, that are only used by the component internally. I wanted a state object like:
var state = { filter: { something: '', foo: 'bar' }, viewSettings: { ... } };
To use this state as queryParams object for the router, it needs to be flattened. I simply JSON.stringify all first level properties of the object:
private convertToParamsData(data) {
var params = {};
for (var prop in data) {
if (Object.prototype.hasOwnProperty.call(data, prop)) {
var value = data[prop];
if (value == null || value == undefined) continue;
params[prop] = JSON.stringify(value, (k, v) => {
if (v !== null) return v
});
}
}
return params;
}
and back, when handling the queryParams returned passed in by the router:
private convertFromParamsData(params) {
var data = {};
for (var prop in params) {
if (Object.prototype.hasOwnProperty.call(params, prop)) {
data[prop] = JSON.parse(params[prop]);
}
}
return data;
}
Finally: A ready-to-use Angular service
And finally, all of this isolated in one simple service:
import { Injectable } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { Observable } from 'rxjs';
import { Location } from '#angular/common';
import { map, filter, tap } from 'rxjs/operators';
#Injectable()
export class QueryParamsService {
private currentParams: any;
externalStateChange: Observable<any>;
constructor(private activatedRoute: ActivatedRoute, private router: Router, private location: Location) {
this.externalStateChange = this.activatedRoute.queryParams
.pipe(map((flatParams) => {
var params = this.convertFromParamsData(flatParams);
return params
}))
.pipe(filter((params) => {
return !this.equalsCurrentParams(params);
}))
.pipe(tap((params) => {
this.currentParams = params;
}));
}
setState(data: any, createHistoryEntry = false) {
var flat = this.convertToParamsData(data);
const url = this.router.createUrlTree([], { relativeTo: this.activatedRoute, queryParams: flat }).toString();
this.currentParams = data;
this.router.navigateByUrl(url, { replaceUrl: !createHistoryEntry });
}
private equalsCurrentParams(data) {
var isEqual = JSON.stringify(data) == JSON.stringify(this.currentParams);
return isEqual;
}
private convertToParamsData(data) {
var params = {};
for (var prop in data) {
if (Object.prototype.hasOwnProperty.call(data, prop)) {
var value = data[prop];
if (value == null || value == undefined) continue;
params[prop] = JSON.stringify(value, (k, v) => {
if (v !== null) return v
});
}
}
return params;
}
private convertFromParamsData(params) {
var data = {};
for (var prop in params) {
if (Object.prototype.hasOwnProperty.call(params, prop)) {
data[prop] = JSON.parse(params[prop]);
}
}
return data;
}
}
which can be used like:
#Component({
selector: "app-search",
templateUrl: "./search.component.html",
styleUrls: ["./search.component.scss"],
providers: [QueryParamsService]
})
export class ProjectSearchComponent implements OnInit {
filter : any;
viewSettings : any;
constructor(private queryParamsService: QueryParamsService) { }
ngOnInit(): void {
this.queryParamsService.externalStateChange
.pipe(debounce(() => interval(500))) // Debounce optional
.subscribe(params => {
// Set state from params, i.e.
if (params.filter) this.filter = params.filter;
if (params.viewSettings) this.viewSettings = params.viewSettings;
// You might want to init this.filter, ... with default values here
// If you want to write default values to URL, you can call setState here
this.queryParamsService.setState(params, false); // false = no history entry
this.initializeView(); //i.e. make API calls
});
}
updateView() {
var data = {
filter: this.filter,
viewSettings: this.viewSettings
};
this.queryParamsService.setState(data, true);
// Do whatever to update your view
}
// ...
}
Don't forget the providers: [QueryParamsService] statement on component level to create a new service instance for the component. Don't register the service globally on app module.
I use this way to get it:
const queryParamsObj = {foo: 1, bar: 2, andThis: 'text'};
this.location.replaceState(
this.router.createUrlTree(
[this.locationStrategy.path().split('?')[0]], // Get uri
{queryParams: queryParamsObj} // Pass all parameters inside queryParamsObj
).toString()
);
-- EDIT --
I think that I should add some more informations for this.
If you use this.location.replaceState() router of your application is not updated, so if you use router information later it's not equal for this in your browser. For example if you use localizeService to change language, after switch language your application back to last URL where you was before change it with this.location.replaceState().
If you don't want this behaviour you can chose different method for update URL, like:
this.router.navigate(
[this.locationStrategy.path().split('?')[0]],
{queryParams: queryParamsObj}
);
In this option your browser also doesn't refresh but your URL change is also injected into Router of your application, so when you switch language you don't have problem like in this.location.replaceState().
Of course you can choose method for your needs. The first is more lighter because you don't engage your application more than change URL in browser.
Use attribute queryParamsHandling: 'merge' while changing the url.
this.router.navigate([], {
queryParams: this.queryParams,
queryParamsHandling: 'merge',
replaceUrl: true,
});
For me it was actually a mix of both with Angular 4.4.5.
Using router.navigate kept destroying my url by not respecting the realtiveTo: activatedRoute part.
I've ended up with:
this._location.go(this._router.createUrlTree([this._router.url], { queryParams: { profile: value.id } }).toString())
In 2021 here is the solution I use. Create URL Tree using createUrlTree and navigate to route using location
//Build URL Tree
const urlTree = this.router.createUrlTree(["/employee/"+this.employeeId],{
relativeTo: this.route,
queryParams: params,
queryParamsHandling: 'merge'
});
//Update the URL
this.location.go(urlTree.toString());
In my case I needed to remove a query param of the url to prevent user to see it.
I found replaceState safer than location.go because the path with the old query params disappeared of the stack and user can be redo the query related with this query. So, I prefer it to do it:
this.location.replaceState(this.router.url.split('?')[0]);
Whit location.go, go to back with the browser will return to your old path with the query params and will keep it in the navigation stack.
this.location.go(this.router.url.split('?')[0]);
it's better to use activatedRoute.navigate() to change URL parameters and use snapshot (not subscribe) to call API if u don't want to call API when URL parameters change.
export class MyComponent implements OnInit {
constructor(private activatedRoute: ActivatedRoute) { }
ngOnInit()
{
const params = this.activatedRoute.snapshot.queryParams;
// params is the state object passed to the router on navigation
// Make API calls here
}
}
import { Component, OnInit } from '#angular/core';
import { Location } from '#angular/common';
#Component({
selector: 'child-component',
templateUrl: 'child.component.html',
styleUrls: ['child.component.scss']
})
export class ChildComponent implements OnInit {
constructor(
private location: Location
) {}
ngOnInit() {
// you can put 'this.location.go()' method call in any another method
this.location.go('parentRoute/anotherChildRoute');
}
}
For me, it changes child route in browser, without any current component reloading.
I was trying to update queryparams and navigate without reloading. By nature activatedRoute.snapshot.queryparams are readonly. And this turnaround approach solved my problem.
// Get queryparams
let state = Object.assign({}, this.route.snapshot.queryParams)
// Change parameters of url
state["z"] = "hi";
state["y"] = "bye";
// Create url and navigate to it without reloading
const url = this.router.createUrlTree([], { relativeTo: this.route, queryParams: state }).toString();
this.router.navigateByUrl(url);

Categories

Resources