Angular 2 service get returns undefined - javascript

In my current project, I have a very simple service which sets a string when a request comes from first page and show it in the second page using the same service. Setting the text is working perfectly. But when I called the get function, it returns undefined.
This is my service
import { Injectable } from '#angular/core';
#Injectable()
export class TsService {
constructor() { }
ts: string;
getTs() : string {
return this.ts;
}
setTs(ts) : void {
this.ts = ts;
}
}
In my first component I imported the Service
import { TsService } from './ts.service';
and added it to the providers
providers: [TsService]
and initialized in the contructor
private tsService: TsService
and to the button click, I set a string as well
this.tService.setTs(form.value.member)
In my second component, followed the same steps mentioned above except in the constructor I assigned as follows
this.ts = tsService.getTs();
but it gives me undefined. Is there anything that I missed

As i can make out from your code. You have registered your service as a provider in your component. like
providers: [TsService]
What this line of code will do. Is that it will fetch a new Instance of your service as soon as your component comes into play. So from first component lets say ComponentA you set the service variable as
this.tService.setTs(form.value.member)
But here ComponentA is having suppose Instnace1 of the service. So you have set the value to Instance1 of the Service. Now you navigate to second component say ComponentB As soon as ComponentB comes into play it angular creates a new Instance of the service and same is made available to ComponentB. Now there are two Instances of your service one with ComponentA and one with ComponentB. but you have set the variable using ComponentA so it wont be available to ComponentB hence
this.ts = this.tsService.getTs();
this returns undefined.
In order to check whether you variable is set or not you can try
this.tService.setTs(form.value.member);
console.log(this.tsService.getTs());
in your ComponentA it will log the value set.
The solution for this problem of your is to share the same Instance and that can be achieved by registering the service as provider in the AppModule.
As official docs say
On the one hand, a provider in an NgModule is registered in the root
injector. That means that every provider registered within an NgModule
will be accessible in the entire application.
For more please refer :-
Dependency Injection
NgModule Injectors
Hope it helps :)

Depending on the order in which stuff is executed it may well be that
this.tService.setTs(form.value.member)
is being executed after
this.ts = tsService.getTs();
In which case the behaviour is expected.
As for how to deal with this problem. One way is to add a way for components to subscribe to the service and get notified when ts changes so that they can react by executing some code. Look into RxJS' Subject.
A different reason may be that you are not providing the service correctly.
For example if you provide the service to a parent and a child component (direct or not). In that case the second provider may be shadowing the first due to Angular's hierarchical dependency injection. Which means that one component is setting the value in one instance of the service and the other component is getting it from a different one. Unless you specifically need that kind of behaviour a service should only be provided at the root of the component tree where it's going to be used.
If your components are not related through the parent-child hierarchy then you should be providing the service only once. At the module level.
Without knowing more about your component structure it's not possible to tell what exactly is going on.

You may have use two instances of data object.
use one data service for set and get data.Then you can access to same data object within different components.
And if you set that service as a provider for your components individually those gonna work as different instances. If you need only one instance for your whole app you can set that service as a provider in app.module.ts file.

Related

Calling an angular component method from classic HTML

I'm trying to call a function defined within an angular component.ts from another javascript library
The library supports a snapin, where I can define HTML Elements, which will be rendered (they are not part of angular).
Here is my html (not in a component's template)
<button onclick='ping()'>PING Button</button>
<button onclick='pong()'>PONG Button</button>
How can I, from the html above, call the pong component method defined my component.ts
import { Component, OnInit, AfterViewInit, Inject, ChangeDetectionStrategy, ChangeDetectorRef } from '#angular/core';
function ping() {
alert('PING PING');
}
#Component({ ...
})
export class Component implements OnInit, AfterViewInit { ...
pong() { //This is the method I want to call from outside of the component's template
alert('pong pong');
}
}
I tried, but it will not work
<button (click)="pong()">PONG Button</button>
But I have no idea to call the "pong()" function normally
Thanks!
If you really need this, you can make the method available in the window object
component.ts
constructor()
{
window['pong'] = () => this.pong();
}
pong()
{
alert ('pong inside component')
}
Then in your html, you can use old style event handler
<button onclick="pong()">Pong</button>
Here is a stackblitz demo
Note: If you have several instance of the same angular component implementing this solution, you'll only have once instance of the method. You could save them all to an array if needed, but you'll need to know which one to call
I guess you want to use a non-angular library from angular, which has a global callback. Be warned that this can lead to problems, because you have to manage the lifecycle of the non-angular thing from angular.
From the angular template, you can only call methods on the component class, and you can't call a global callback. You can however create a method on the component class, and call the global callback from there.
There's one more thing before that's possible: typescript doesn't know about your global callback, so you have to explictily declare it, see the example. This tells typescript that there's something that is created outside of typescript, so it will let you call it.
import { Component } from '#angular/core';
declare const libMethod: (any) => any;
#Component({
selector: 'my-app',
template: `
<button (click)="myMethod($event)"></button>
`,
styleUrls: []
})
export class AppComponent {
public myMethod(param) {
libMethod(param);
}
}
If you plan to use that library from multiple of your angular components, then you might want to create a service, declare the global callback only in that, and create a method on the service. That way, this somewhat hacky declaration will not be littered all over your code, but contained to a single place. It also makes your life easier, if you upgrade/replace this library.
Answers to questions in the comments:
TBH I don't completely understand the situation. (Who calls the backend, when it returns the HTML? You from Angular, or the lib? Does the lib process the HTML? Or what does it do?)
Some suggestions: create a global singleton service, which puts up one of its methods to the window (dont' forget to bind it if you use this inside the method) to serve as a callback for the lib. When the lib calls it with the data, regardless of who/when actually triggered the lib to do its thing, the service stores the data in a subject, and the service also provides an observable of the data (maybe with a shareReplay(1) so that the consumers always get something).
With that, actually displaying the data is fairly easy, you can just use the async pipe, and not care about how/when the data got there in the first place, and don't have to sync the component's lifecycle with the service.
Also, you probably need to use https://angular.io/api/platform-browser/DomSanitizer#bypasssecuritytrusthtml but I am not sure about that, since I never had to inject HTML. Speaking about which...
Important security notice: if you inject HTML from outside of angular, and that is hijacked, you just opened up your page to all kind of nasty cross site scripting things.

How does angular service (like ChangeDetectorRef) can get the component ref that used it?

I wonder how does this line work:
this.ref.markForCheck();
Or:
this.ref.detach();
While ref is: ChangeDetectorRef
I didn't see a way to get in a service the component that called you, so how does angular knows which component called it when I call this.ref.detach(); and detach the right component?
I would expect a call like this.ref.detach(this); so I pass the reference to the component, but seems like angular service has a way to access the caller?
Added jsfiddle https://jsfiddle.net/1hk7knwq/11788/
Look at the test() call, somehow the ref service is also getting the component instance without me explicitly passing it.
Thanks

pass data to different component in angular 6

I am building my web application using angular 6. I have some common component which is common on all routes. For example I have a filter component which is common on all route. Now when user select a filter a click on find this filter data should be passed to different component on same route and then result should be display. PFB my angular's app structure :
App.component.html :
<filter (messageToDash)="receiveMessage($event)"></filter>
<router-outlet></router-outlet>
For dash route I have dash component. PFB is code for dash.component.html :
<dashboard></dashboard>
Filter.component.html
<button (click)="somemethod()"></button>
So when user click on button, I want to want to pass some variable to the dashboard component. I also tried using service component and subscribe it into dashboard's ngOnInit() variable, but its not working.
Create a service and give it as reference at the parent Level and on (click) of the the button pass the filter data to the function with return type observable and in component subscribe to the result of the function.
Since ngOnInit lifecycle is initialized only at the beginning of the page load, It may not be of much help.
I too had stuck in the same problem a few months back, the best solution i got at that time was using LocalStorageService
For Example:
import { LocalStorageService } from 'ngx-webstorage';
constructor(private session: LocalStorageService)
{
//works at the begining of the module before OnInit
}
some_function()
{
this.session.store('key_name',yourData);
}
Now in another component just import LocalStorageService and create a obj for it and then:
some_function()
{
this.your_variable = this.session.retrieve('key_name');
}
Note: the key_name for storing and retrieving must be same.
hope this helps.

Inject reducer for on demand component which was not in the store or combined reducers initially

I'm trying to build some modular SAP so many teams can work separatelly.
Basically, I want my containers to be independent in terms of container, store, reducers, sagas.
The actual question is (example code):
I render a basic template:
<div>
<a onClick={emitLoadUserListAction}>Load user list</a>
<UserList/>
</div>
At this point, I make use of 1 reducer for UserList to keep the array of users (empty at the beginning).
Let's assume I have a saga, waiting for this data to come as a user list in a json.
Store:
{
UserList: []
}
Once the saga fetches the data, publishes an action modifiying the current store:
Store:
{
UserList: [{name:"john",counter:0},{name:"pepe",counter:0}]
}
Now my UserList component can list this as we have the mapStateToProps pointing to this part of the store.
this.props.userList.map ( (userData,i) => { return <User data={userData}> } ))
So now everything is working like a charm if User component is just a normal component.
But what if User is actually a container, which is expecting to work on its own, with its own state I didn't connected yet via its own reducer. I don't want his parent to manage it. I want user to be independent as I could pass its location in the store with reselect selector or similar, or I could just pass the index in the array as a prop, so I could be the selector. This way I would have store injected in props, but I won't have reducer.
I'm pretty sure many of you already pass through this but I couldn't find a proper answer.
As you can see the idea is to have a component, which is loading on demand, not in the initial combineReducers, not handled by its parents, just render, and reducer injected to work on its own.
If I could have just a way to load its reducer on demand then, I would not store the data in the UserList but it will be a composition of reducers.
Thanks a lot in advance.
I'm continuing on from my comment and the question that followed so I can expand on it without the restrictions of the comments section.
Yes, my library calls replaceReducer on the store to in order to, well, replace the reducer with the new one included. In order to do so, I provide a Higher-Order Component (HOC) which bundles the component with it's associated reducer and performs the replacement when it is mounted.
The interface looks something like this:
export const MyBundledComponent = bundle(MyComponent, myReducer)
The only requirement for it to work is that the component is mounted within a Provider from react-redux. This gives the HOC access to the store on React's context the same way the connect HOC does. This isn't really a very prohibitive restriction though, as most redux apps have a Provider at the top of the tree already.
Hope this helps.
So far I found resources like this:
https://medium.com/#jimmy_shen/inject-reducer-arbitrarily-rather-than-top-level-for-redux-store-to-replace-reducer-fdc1060a6a7
which allow you to inject reducers on demand by replacing the main reducer by using the Redux store API store.replaceReducer(nextReducer)
The problem with this solution is the need to have access to the main store object from the child component that should be encapsulated.
For the moment working not ideal solution that I found is to deliver the encapsulated component with a "multiple components reducers" meaning that the reducer assumes there could be more than one component under the same parent where each one has different ids.
So each action should check the payload ID, in order to get the state from the store object.
This would mean a small change in the hierarchy as the component would not be child but sibling.
Following the previous example, imagine that we list a shallow version of the user list and then you show more data once u click on any user:
`
Store: {
UserList: [], // basic info, id plus minimal data
users: {} --> userReducer // listing each user by key
}
`
This way the user component will expose multiUserReducer instead of logic for just one.
This obviously means the reducer is loaded in advance, even if you never load any user componet.

Structuring a Vue + Vuex project

I am kind of confused here on where to place my global functions. In a lot of examples a main.js file points to an app component and this is placed somewhere within the html. This workflow would be fine for me If I were to simply contain all my logic within this app component. But I am combining components with Laravel functionality so this does not work for me.
Currently my main.js file contains a bunch of methods that I need to have access from anywhere in my app. These methods don't contain any broadcasting events so they can effectively be placed anywhere as long as they get a vue-resource instance.
My main.js file:
https://github.com/stephan-v/BeerQuest/blob/develop/resources/assets/js/main.js
Hopefully somebody can tell me where I could place my friendship methods if I were to use vuex or in general since this does not seem like best practice at all.
Thank you.
Vuex manages all of the data in your application. It's a "single source of truth" for data on your front-end. Therefore, anything that changes the state of your application, such as adding a friend, or denying a friend, needs to flow through Vuex. This happens through three main function types, getters, actions, and mutations.
Check out: https://github.com/vuejs/vuex/tree/master/examples/shopping-cart/vuex
Getters are used to fetch data from storage in Vuex. They are reactive to changes, meaning if Vuex data changes, the information in your component is updated as well. You can put these in something like getters.js so that you can import them in any module you need them in.
Actions are functions that you call directly, ie. acceptFriendRequest when a user clicks the button. They interact with your database, and then dispatch mutations. In this app, all of the actions are in actions.js.
So you'd call this.acceptFriendRequest(recipient) in your component. This would tell your database to update the friend status, then you get a confirmation back that this happened. That's when you dispatch a mutation that updates the current users' list of friends within Vuex.
A mutation updates the data in Vuex to reflect the new state. When this happens, any data you are retrieving in a getter is updated as well. Here is an example of the entire flow:
import {addFriend} from './actions.js';
import {friends} from './getters.js';
new Vue({
vuex:{
getters:{
friends
}
},
methods:{
addFriend
}
}
store.js:
export default {
state:{
friends: []
},
mutations:{
ADD_FRIEND(state, friend) {
state.friends.push(friend);
}
}
}
actions.js:
export default {
addFriend(friend){
Vue.http.post('/users/1/friends',friend)
.then((response)=>{
dispatch("ADD_FRIEND", response) //response is the new friend
})
}
}
getters.js
export default {
friends(state) {
return state.friends;
}
}
So all of these are organized into their own files, and you can import them in any component you need. You can call this.addFriend(friend) from any component, and then your getter which is accessed from this.friends will automatically update with the new friend when the mutation happens. You can always use the same data in any view in your app and know that it is current with your database.
Some misc stuff:
getters automatically receive state as a variable, so you can always reference the state of your Vuex store
mutations should never be asynchronous. Do fetching/updating in actions and then dispatch mutations just to update your data
creating services (or resources) using Vue Resource will make fetching/updating/deleting resources even easier. you can put these in separate files and import them in your actions.js to keep the database retrieval logic separated. Then you'd be writing something like FriendService.get({id: 1}) instead of Vue.http.get('/users/1'). see https://github.com/vuejs/vue-resource/blob/master/docs/resource.md
Vuex works with vue devtools for "time-traveling". You can see a list of every mutation that has taken place and rewind them/redo them. It's great for debugging and seeing where data is being changed.

Categories