i created sharedService it works perfectly , i can shared data from one component to another (this both are irrelevant component in different module).
Data Transfer as follows:
AdminDashboard.Component (update value) ===> conference.component (get new updated value)
problem : when i refresh my conference.component i lost the value
EventService.ts
import { Injectable } from '#angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { importExpr } from '#angular/compiler/src/output/output_ast';
import {Events} from '../models/event.model'
#Injectable()
export class EventService {
private dataSource = new BehaviorSubject(null);
sendMessage(data) {
this.dataSource.next(data);
}
getMessage(): Observable<any> {
return this.dataSource.asObservable();
}
}
dashboard.component (url /dashboard)
on Button Click msg() method called , which updated BehaviourSubjectvalue.
import { Component, OnInit} from '#angular/core';
import { NgForm } from '#angular/forms';
import { EventService } from '../../shared/sharedServies/eventService.service';
export class AdminDashboardComponent implements OnInit {
constructor( private testEventService: EventService){ }
msg() {
debugger
this.testEventService.sendMessage('Message from Home Component to App
Component!');
}
}
conference.component (url /conference)
Here , i hold value in message and bind to ui.
import { Component, OnInit } from '#angular/core';
import { EventService } from'../../shared/sharedServies/eventService.service';
import { Subscription } from 'rxjs/Subscription';
export class ViewconferenceComponent implements OnInit {
message: any;
constructor(private EventService: EventService) {
this.subscription = this.EventService.getMessage().subscribe(message => {
console.log(message)
this.message = message;
});
}
}
Question :
when i get data on /conference page , at this when i refresh the
service holded value is lost , i didn't understand what this happens.
also i need to add json to sharedService , how it will achive?
This is expected since when you "switch" components they are destroyed. You could work around this quickly by adding state variables to your service.
Personally, I encourage you to make use of some state library like ngRx https://github.com/ngrx/platform
Related
New to MVVM clean archietecture .Building an app which has single screen consisting of Recycler view. The data is fetched through retrofit.According to the documentation ViewModel is able to live through the configuration changes but in my case it is not working when i change orientation from portrait to landscape. No clue about the issue, Please advise
**NewsViewModel.Kt**
import android.util.Log
import androidx.lifecycle.*
import com.example.recyclerviewjsonarray.model.NewsList
import com.example.recyclerviewjsonarray.network.remote.RetrofitInstanceDto
import com.example.recyclerviewjsonarray.network.remote.RetrofitServiceDto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private const val TAG ="NewsViewModel"
//viewmodel for handling clean archietecture
class NewsViewModel : ViewModel() {
//Mutable live data for the news list
private val _newsMutableLiveData: MutableLiveData<NewsList> = MutableLiveData()
val newsMutableLiveData : LiveData<NewsList> get() =
_newsMutableLiveData
//viewmodel will observe the latest updated data with the help of mutable live data
fun newsListObserver(): LiveData<NewsList> {
return newsMutableLiveData
}
/* making an api call using viewmodel scope (custom coroutines scope can be used as well)
launch is like a builder . Here it is launching Dispatcher.IO for memory intensive operation
Now inside we will create synchronized retrofit instance and fetch the response
in the form of getDataFromApi() with a delay of 2 seconds respectively
post value is called lastly for setting the value from a background thread */
fun getDataFromApi() {
Log.i(TAG,"init")
viewModelScope.launch(Dispatchers.IO) {
val retrofitInstance = RetrofitInstanceDto.getRetrofitInstance().create(RetrofitServiceDto::class.java)
val response = retrofitInstance.getDataFromApi()
delay(1500)
_newsMutableLiveData.postValue(response)
}
}
**NewsListFragment.kt**
import android.os.Bundle
import android.util.Log
import android.view.*
import androidx.fragment.app.Fragment
import android.widget.Toast
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.recyclerviewjsonarray.R
import com.example.recyclerviewjsonarray.databinding.FragmentNewsListBinding
import com.example.recyclerviewjsonarray.model.NewsList
import kotlinx.android.synthetic.main.fragment_news_list.*
private const val TAG ="NewsListFragment"
//The view of MVVM architecture
class NewsListFragment : Fragment() {
/* view binding with late init as dont want to redraw again n again
also late init promises no nullable data when it is called later */
private lateinit var binding: FragmentNewsListBinding
//Kotlin property delegate used to define viewmodels
private val viewmodel: NewsViewModel by viewModels()
private lateinit var newsAdapter: NewsListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = FragmentNewsListBinding.inflate(layoutInflater)
Log.i(TAG,"onCreate")
//Creating the observer which updates the UI(Main Thread)
viewmodel.newsMutableLiveData.observe(viewLifecycleOwner, {
if(it!=null )
{
hideProgressBar()
Log.i(TAG,"Received the data")
initAdapterModel()
newsAdapter.setLatestData(it.rows,activity)
}
else {
showProgressBar()
Toast.makeText(activity,"No data",Toast.LENGTH_LONG).show()
}
})
viewmodel.getDataFromApi()
return binding.root
}
override fun onPause() {
super.onPause()
}
//Binding the recycler view adapter and with the news adapter
private fun initAdapterModel() {
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
newsAdapter = NewsListAdapter()
binding.recyclerView.adapter = newsAdapter
}
private fun hideProgressBar() {
progressBar.visibility = View.INVISIBLE
}
private fun showProgressBar() {
progressBar.visibility = View.VISIBLE
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.scrolltotop,menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
binding.recyclerView.smoothScrollToPosition(0)
return super.onOptionsItemSelected(item)
}
companion object {
#JvmStatic
fun newInstance() =
NewsListFragment()
}
}
}
Well I found the solution by using event and save instance state.
I used shared module with dynamic configuration in application.
Shared module contains interceptor and service which contains client configured from shared parameters.
I injected the service into the interceptor by predefined name (importing SharedModule dynamically into SecondAppModule). So client name can have different value. Inside of service I need to know the clients name before client injecting. Right now it is hard coded:
#Injectable()
export class SumClientService {
constructor(#Inject('MATH_SERVICE') private client: ClientProxy) {
console.log('[SumClientService] - created')
}
sumCalculation(row: number[]): Observable<number> {
return this.client.send<number>({ cmd: 'sum' }, row);
}
}
Question
Is there any ways to load service from context by name in case name known at construction time only?
I've detected two ways at list to past name as a parameter into service without corrupting DI managed by nest.js. But I have no idea how to get access to module context for loading service by specified name (the code of idea fragment is below)
#Injectable()
export class SumClientService {
constructor(#Inject('service_name') private name: string) {
console.log('[SumClientService] - created')
}
client: (clientName: string) => ClientProxy = (clientName: string): ClientProxy => // TODO load by clientName real client from `nest.js` context
// ...
}
P.S.
The idea is to use multiple clients in the same application. I considered scenario one client per module for the first time.
git code
There are few options to create microservice clients in dynamic module:
1st option
in module configuration level:
import { Module } from '#nestjs/common';
import { ClientModule } from '#nestjs/microservices';
import { configForYourClient } from '../configs';
#Module({
imports: [
ClientModule.register([
{ 'NAME_FOR_DI', ...configForYourClient }
])
],
...
})
...
usage:
import { Inject } from '#nestjs/common';
import { ClientProxy } from '#nestjs/microservices';
...
constructor(#Inject('NAME_FOR_DI') private readonly client: ClientProxy) { }
2st option
For dynamically configured module:
import { Module, DynamicModule } from '#nestjs/common';
import { ClientModule } from '#nestjs/microservices';
import { configForYourClient } from '../configs';
#Module({})
export class YourModule{
static register(): DynamicModule {
return {
module: YourModule,
imports: [
ClientModule.register([
{ 'NAME_FOR_DI', ...configForYourClient }
])
],
}
}
}
3st option
import { ClientProxy, ClientProxyFactory } from '#nestjs/microservices';
import { configForYourClient } from '../configs';
...
private client: ClientProxy = ClientProxyFactory.create(configForYourClient)
Following second option it can be created in service directly or registered for module specifying DI key in providers, like:
import { Module } from '#nestjs/common';
import { ClientProxyFactory } from '#nestjs/microservices';
import { configForYourClient } from '../configs';
#Module({
providers: [
{
provide: 'NAME_FOR_DI',
useValue: ClientProxyFactory.create(configForYourClient)
},
],
...
})
...
with following injection by specified key.
P.S.
I've described here general idea for dynamic injection. This idea can be enriched with different combinations (injecting list of values for the single provider, or injecting list of configurations into ClientModule.register where you should apply some corrections in you DI approach)
I have a REST API, I want to send event to the client via websocket.
How to inject websocket instance in controller or another component?
Better solution is to create global module. You can then emit events from any other module/controller. A. Afir approach will create multiple instances of Gateway if you try to use it in other modules.
Note: This is just simplest solution
Create socket.module.ts
import { Module, Global } from '#nestjs/common';
import { SocketService } from './socket.service';
#Global()
#Module({
controllers: [],
providers: [SocketService],
exports: [SocketService],
})
export class SocketModule {}
socket.service.ts
import { Injectable } from '#nestjs/common';
import { Server } from 'socket.io';
#Injectable()
export class SocketService {
public socket: Server = null;
}
app.gateway.ts see afterInit function
import { WebSocketGateway, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, WebSocketServer } from '#nestjs/websockets';
import { Logger } from '#nestjs/common';
import { Server, Socket } from 'socket.io';
import { SocketService } from './socket/socket.service';
#WebSocketGateway()
export class AppGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
constructor(private socketService: SocketService){
}
#WebSocketServer() public server: Server;
private logger: Logger = new Logger('AppGateway');
afterInit(server: Server) {
this.socketService.socket = server;
}
handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
this.logger.log(`Client connected: ${client.id}`);
}
}
Then import SocketModule into AppModule and you can use Socket service everywhere.
class Gateway can be injected in another component, and use the server instance.
#Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private readonly messageGateway: MessageGateway
) {}
#Get()
async getHello() {
this.messageGateway.server.emit('messages', 'Hello from REST API');
return this.appService.getHello();
}
}
I suppose that #Raold missed a fact in the documentation:
Gateways should not use request-scoped providers because they must act as singletons. Each gateway encapsulates a real socket and cannot be instantiated multiple times.
So it means that we can neither instantiate the gateway class multiple times nor do it explicitly using injection scopes features.
So creating just only one gateway for one namespaces will be right and it will produce only one instance of the websocket or socket.io server.
I am attempting to make a call to the server using promises. When trying to add my parameters, it comes out as 'object%20Object'
Here is the call
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/map';
import { User } from '../models/user';
#Injectable()
export class UserService {
private baseUserUrl = 'api/User/'
constructor(private http: Http) { }
getUsers(currentPage: number): Promise<User[]> {
return this.http.get(this.baseUserUrl + 'GetUsers?currentPage=' + currentPage)
.map(resp => resp.json() as User[])
.toPromise()
}
}
I was accidentally passing an object into the method, so I wasn't accessing the property, I was accessing the object. I fixed that and removed the object and passed a property.
<!--Simple Angular2 Service-->
import { Injectable } from '#angular/core';
#Injectable()
export class HomeService {
constructor() {
}
getSomething() {
return 'hiii';
}
}
<!--Consuming Service in Component-->
import { Component } from '#angular/core';
import { HomeService } from './components/home/home.service';
#Component({
selector:'home',
templateUrl:'app/components/home/home.html'
})
export class HomeComponent {
}
<!--I am Getting below compilation Errors-->
Error TS2307 Cannot find module 'app/components/home/home.service'.
Error Build:Cannot find module './components/home/home.service'.
Please help stuck here for 3 days