Working around with observable and subscriptions - javascript

I am using Angular6, below is my scenario:
I am trying to pass information between Component1 and COmponent2 via a service. I see that service receives message from Component1, but Component2 doesnt receive the passed message.
In Page1:
//when a mouse move in a particular area happens, this method is called
actionMove: function(evt,obj) {
var messService = new MessService();
messService.sendMessage('Sending message from Component1 to Component2!');
}
inMessService file:
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
#Injectable({ providedIn: 'root' })
export class MessService {
private subject = new Subject<any>();
sendMessage(message: string) {
console.log("*** service got message from component 1 ***");
this.subject.next({ text: message });
}
getMessage(): Observable<any> { // this communicates with component2
console.log("*** component 2 trying to get message ***");
return this.subject.asObservable();
}
}
In Component2:
constructor(private messService: MessService) {
// subscribe to message service method
this.subscription = this.messService.getMessage()
.subscribe(message => { this.message = message; });
Could you please help me understand what am I missing?

Im not an Angular pro, but I think you using two different instances of MessService here because you new up one in actionMove in component1, and injecting an other one into component2, hence working with one does not reflect in the other.
Try to inject MessService into component1 also using constructor injection, as fridoo suggests. Because you provide your service in root, you'll get a singleton.
Singleton services - Angular

fridoo's comment goes to the heart of it --
to use a service in multiple components you need to take advantage of angular's dependency injection. you want to reference your service class in the constructor like in your component2 in component1, this way both components are using the same MessService.
you can just create an empty constructor like
constructor(private messService: MessService) {}
to get the service injected -- have you tried doing this?

Related

How can I reproduce the Angular JS .broadcast() / .on() behaviour in Angular?

I'm working on rewriting an AngularJS application in Angular8. I have read of the different ways to communicate between components but can't seem to find the proper way to achieve this given my current requirements.
I currently have 2 sibling components which both use a common service that handles basic crud functionality. The form component calls a create method in the service and the list component calls a fetch method.
<page-component>
<form component></form component>
<list-component></list-component>
</page-component>
In AngularJS this would have been achieved by using a $scope.broadcast() and $scope.on() which is the effect that I'm looking to reproduce.
What I'm trying to figure out is the best way for the form component to emit an event to which the list component would (i assume) subscribe in order to tell it to refresh itself.
To be clear, I don't want to pass the updated values to the list component. I would like to simply communicate to it that its dataset has been updated and that it needs to re-fetch its records.
I have tried using an #Output in my form component:
export class FormComponent implements OnInit {
#Output() valueChange = new EventEmitter();
onUpdate() {
this.valueChange.emit(true);
}
...
}
But I'm confused on how to implement the event listener as all the documentation I have read seems to point to events passed between parents and children and not to siblings as it were. Also the examples I have seen all seem to be focused on passing the actual data to the component instead of simply having it watch for an event and do the work on its own, as I require.
I have also seen methods that use #Input which require parameters to be passed into them on declaration. Somehow I feel that these components should be able to work on their own and not depend on this.
I believe there are multiple ways to achieve this.
In case of unrelated components, like the sibling components, one method would be to use a Subject Observable of RxJS module in the common service to which Form component will push a new value that can be subscribed to by the List component.
Service:
import { Subject } from 'rxjs/Subject';
#Injectable()
export class SampleService {
public triggerSource = new Subject<boolean>();
public trigger$ = this.triggerSource.asObservable();
}
Form Component:
import { SampleService } from '../services/sample.service';
#Component({
selector: 'form-component',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css'],
})
export class FormComponent implements OnInit {
constructor(private _sampleService: SampleService) {
}
private setTriggerState(state: boolean) {
this._sampleService.triggerSource.next(state);
}
}
List Component:
import { SampleService } from '../services/sample.service';
#Component({
selector: 'list-component',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css'],
})
export class ListComponent implements OnInit {
constructor(private _sampleService: SampleService) {
this._sampleService.trigger$.subscribe(value => {
if (value) {
// value is true (refresh?)
} else {
// value is false (don't refresh?)
}
});
}
}
And since we are returning an Observable, there are numerous operators to refine the outflow of data. For eg., in your case operator distinctUntilChanged() can be used to avoid pushing redundant information. Then the declaration of the observable would be
public trigger$ = this.triggerSource.asObservable().distinctUntilChanged();
Answer to this question could be a topic for a nice holywar, you know )
My opinion based on experience is in following:
Let's assume user typing something and than performs refresh page. Ask ourself what should happen?
If User's changes should recover to "before editing" state - than the parent component is enough to share such events.
If User's changes should be as it was before refresh page action, so if you need to store it localStorage or database or elsewhere, than it's better to involve services for it.
Create a #Input property in your ListComponent and pass all the items that list component will render from PageComponent. Whenever create component emit a value fetch the new list in PageComponent and update passed property.
Implement the OnChange lifecycle hook in ListComponent and capture the property change in ngOnChange(changes) method.
If you don't like to pass the list from PageComponent then there are few options that can be used to solve the issue.
Option 1:
instead of passing a list:Array<any> pass a list:Observable<Array<any>> and subscribe the observable in your ListComponent so technically you http call will happen inside list component. But you have to reassign the list with new Observable<Array<any>> everytime FormComponent emit a value so ngOnChange will notified.
Option 2 :
You can pass a Subject to ListComponent as a propery and Subscribe to subject in ListComponent. Then you can fetch the product in ListComponent. When ever your FormComponent emit value call next() method in subject you passed. Every time you call next method your subscribles will notify and you can fetch the details from API.
reference links
https://angular.io/api/core/OnChanges
https://angular.io/api/core/Input
#Component({
//Component Metadata
})
export class ListComponent implement OnChange{
#Input() listOfAny:Array<any>=[];
ngOnChanges(changes: SimpleChanges) {
this.listOfAny =changes.listOfAny.currentValue
}
}
#Component({
//Component Metadata
})
export class FormComponent {
#Output() valueChange = new EventEmitter();
onUpdate() {
this.valueChange.emit(true);
}
}
#Component({
//Component Metadata
template:`
<page-component>
<form component (valueChange)="valueChanges($event)" ></form component>
<list-component [listOfAny]="list"></list-component>
</page-component>`
})
export class PageComponent {
list=[]
valueChanges(newValue){
// you can fetch items from API if needed.
this.list.push(newValue)
}
}
Have a look at the Rxjs Subjects. Subjects makes the inter-components communication easier.
Since Forms and List component use a common CRUD service in your case, you can have a subject (say, newRecordSubject) and push any new records created into that subject. Now the List component should subscribe to this subject to be notified of any new records whenever it is created by the Forms component.
Have a look at the sample CRUD service below. I beleieve everything is very much self explainatory from the code snippets below.
crud.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
import { Record } from './record.model';
#Injectable({providedIn: 'root'})
export class CrudService {
private newRecordSubject = new Subject<Record>();
get newRecordListener() {
return this.newRecordSubject.asObservable();
}
public insertRecord(record: Record): void {
// logic to pass the record to the backend
this.newRecordSubject.next(record);
}
public fetch(): Record[] {
// logic to fetch the inital records from the backend
return [];
}
}
Now in your List component, inject this CRUD service and subscribe to the newRecordsSubject as below.
list.component.ts
import { OnInit, Component, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs';
import { CrudService } from './crud.service';
import { Record } from './record.model';
#Component({
selector: 'list-component',
template: ''
})
export class ListComponent implements OnInit, OnDestroy {
records: Record[];
private sub: Subscription;
constructor(private service: CrudService) {}
ngOnInit() {
this.records = this.service.fetch();
this.sub = this.service.newRecordListener.subscribe(record => this.records.push(record));
}
ngOnDestroy() {
if (this.sub) {
this.sub.unsubscribe();
}
}
}
Note:
I'm converting Subject to Observable so that the subject is available as read-only to methods outside Crud Service.
It is always a best practice to unsubscribe to the Subscriptions of Subjects and Observables, to avoid memory leaks.
Hope this helps! Cheers and Happy Coding.
You can create a function in parent that sets the value in the child component that you need.
In the component
<page-component>
<form component (value change)="formValueFunction($event)" emitedformValue="formEmitiedValue"></form component>
<list-component (list change)="listValueFunction($event)" emitedListValue="listEmitedValue"></list-component>
</page-component>
In the component you can set the value to the other components like this
formEmitiedValue:any;
listEmitedValue:any;
formValueFunction(event){
this.formEmitiedValue= event;
}
listValueFunction(event)
{this.listEmitedValue=event;
}
check the event value using logs and if there are changes in form input use OnChange event hook in the components
When I was migrating my app from AngularJS to Angular 2+. I choose to use RxJS, super easy to use, but there are learning curve.
You create service and RxJS BehaviorSubject. Any component can now subscribe for changes if needed, and any component can send updated data using .next() method.
This is your service:
import {Injectable} from "#angular/core";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export default class yourService {
valueChange = new BehaviorSubject<object>({});
//add service functions if needed
...
}
This is your component:
import { Component, OnInit, OnDestroy } from "#angular/core";
import YourService from "../path to your service";
#Component({
selector: "name of component",
template: require("your.html")
})
export class nameOfComponent implements OnInit, OnDestroy {
private sub;
privat val = "Your value";
constructor( private YourService: YourService) {
}
ngOnInit() {
this.sub = this.YourService.valueChange.subscribe( (value) => {
this.YourService.valueChange.next(this.val); // If you need to pass some value to the service.
this.val = value;
})
}
ngOnDestroy() {
this.sub.unsubscribe();
}

Angular 2/4 communication between two component via a shared service

I am trying to build a simple shopping cart application.
I have two components and a cart service like following.
<app-header></app-header>
<app-cart></app-cart>
In cart service has all the functionality for doing adding an item to cart, delete item in the cart, cart quantity etc.
I need to update the cart count in the header component when the user adds a product to the cart.
How to do that with a shared service.
In this case, You can use a service with subject. A service in Angular is a singleton, meaning it is managed as a single instance. So if each of the components access the service, they will access the same shared data.
export class cartService{
private prodCount = 0;
prodCountCountChange: Subject<number> = new Subject<number>();
UpdateCount(count: number) {
this.prodCount = count;
this.prodCountCountChange.next(this.prodCount);
}
}
In your component you can do this,
this._cartService.UpdateCount(this.prod.length);
In the header, you must subscribe to some event in the Shared Service. The same event will be emitted from your cart component.
Please have a look at this plunkr https://plnkr.co/edit/LS1uqB?p=preview
You should use Subject from RxJs. In your cart you will subscribe to some counter in your service so you'll able to update cart information in app-header.
In angular the components will share an instance of a service if it is from the same ancestor. For example you have your app-header and app-cart wich are part of the AppModule. So if you add SharedCartService to the providers array of your module, the two components will get the same instance of that service.
Dependency injection and hierarchical dependency injection
Create a service by :-
ng g service data
Upon running this, your output may look something like
installing service
create src\app\data.service.spec.ts
create src\app\data.service.ts
WARNING Service is generated but not provided, it must be provided to be used
The warning simply means that we have to add it to the providers property of the NgModule decorator in src/app/app.module.ts, so let's do that:
import { DataService } from './data.service';
#NgModule({
providers: [DataService],
})
Now that we have created a service, let's take a look at what the Angular CLI created:
import { Injectable } from '#angular/core';
#Injectable()
export class DataService {
constructor() { }
}
now create a function that holds your data in data.service.ts
constructor() { }
public currentData: any;
storeData(dataFromComponent){
this.currentData = dataFromComponent;
}
getData(){
return this.currentData;
}
now in app.component
import { DataService } from './data.service';
export class AppComponent {
constructor(private dataService:DataService) {
}
anytimeSaveData(){
// to store data in service call storeData function
this.dataService.storeData("Hello! I have some data here..");
}
}
to get data in another component just import our service and call getData function
export class HomeComponent {
constructor(private dataService:DataService) {
}
// to get data from service call getData function
console.log(this.dataService.getData());
}
Output in console
Hello! I have some data here..
Happy Coding

Angular 2/4 - best pratice of getting a variable from service in all html files of components?

I have alert service in a angular 4 project. All components can set alerts in that service, but only some show them and each one shows them in a unique location.
So my question is how is it possible to define get a variable from a service in all the html files?
My service looks like this:
import { Injectable } from '#angular/core';
#Injectable()
export class AlertService {
message;
constructor() { }
setMessage(message){
this.message = message;
}
}
And the component that wants to set a message just imports the service and calls setMessage method. But when i try to use message in html file like:
{{message}}
then its out of scope. How can i make this message accessible in all html files of all components?
Or maybe there is a better way to solve this kind of an requirement than a service variable?
In order to consume the message in your component you need to inject the service in your constructor (which you are probably doing).
Component:
export class MyComponent {
constructor(public alertService: AlertService)
In the markup reference alertService.message instead of message.
{{ alertService.message }}

Angular 2 Shared Data Service is not working

I have built a shared data service that's designed to hold the users login details which can then be used to display the username on the header, but I cant get it to work.
Here's my (abbreviated) code:
// Shared Service
#Injectable()
export class SharedDataService {
// Observable string source
private dataSource = new Subject<any>();
// Observable string stream
data$ = this.dataSource.asObservable();
// Service message commands
insertData(data: Object) {
this.dataSource.next(data)
}
}
...
// Login component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class loginComponent {
constructor(private sharedData: SharedDataService) {}
onLoginSubmit() {
// Login stuff
this.authService.login(loginInfo).subscribe(data => {
this.sharedData.insertData({'name':'TEST'});
}
}
}
...
// Header component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class headerComponent implements OnInit {
greeting: string;
constructor(private sharedData: SharedDataService) {}
ngOnInit() {
this.sharedData.data$.subscribe(data => {
console.log('onInit',data)
this.greeting = data.name
});
}
}
I can add a console log in the service insertData() method which shoes the model being updated, but the OnInit method doesn't reflect the change.
The code I've written is very much inspired by this plunkr which does work, so I am at a loss as to what's wrong.
Before posting here I tried a few other attempts. This one and this one again both work on the demo, but not in my app.
I'm using Angular 2.4.8.
Looking through different tutorials and forum posts all show similar examples of how to get a shared service working, so I guess I am doing something wrong. I'm fairly new to building with Angular 2 coming from an AngularJS background and this is the first thing that has me truly stuck.
Thanks
This seems to be a recurring problem in understanding Angular's dependency injection.
The basic issue is in how you are configuring the providers of your service.
The short version:
Always configure your providers at the NgModule level UNLESS you want a separate instance for a specific component. Only then do you add it to the providers array of the component that you want the separate instance of.
The long version:
Angular's new dependency injection system allows for you to have multiple instances of services if you so which (which is in contrast to AngularJS i.e. Angular 1 which ONLY allowed singletons). If you configure the provider for your service at the NgModule level, you'll get a singleton of your service that is shared by all components/services etc. But, if you configure a component to also have a provider, then that component (and all its subcomponents) will get a different instance of the service that they can all share. This option allows for some powerful options if you so require.
That's the basic model. It, is of course, not quite so simple, but that basic rule of configuring your providers at the NgModule level by default unless you explicitly want a different instance for a specific component will carry you far.
And when you want to dive deeper, check out the official Angular docs
Also note that lazy loading complicates this basic rule as well, so again, check the docs.
EDIT:
So for your specific situation,
#Component({
providers: [SharedDataService] <--- remove this line from both of your components, and add that line to your NgModule configuration instead
})
Add it in #NgModule.providers array of your AppModule:
if you add it in #Component.providers array then you are limiting the scope of SharedDataService instance to that component and its children.
in other words each component has its own injector which means that headerComponentwill make its own instance of SharedDataServiceand loginComponent will make its own instance.
My case is that I forget to configure my imports to add HttpClientModule in #NgModules, it works.

Angular 2 - service - dependency injection from another service

I have written two services in Angular 2. One of those is a basic, customised class of Http with some custom functionality in (it looks basic for now, but it will be expanding):
ServerComms.ts
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
#Injectable()
export class ServerComms {
private url = 'myservice.com/service/';
constructor (public http: Http) {
// do nothing
}
get(options) {
var req = http.get('https://' + options.name + url);
if (options.timout) {
req.timeout(options.timeout);
}
return req;
}
}
Another class, TicketService utilises this class above, and calls one of the methods in the service. This is defined below:
TicketService.ts
import {Injectable} from 'angular2/core';
import {ServerComms} from './ServerComms';
#Injectable()
export class TicketService {
constructor (private serverComms: ServerComms) {
// do nothing
}
getTickets() {
serverComms.get({
name: 'mycompany',
timeout: 15000
})
.subscribe(data => console.log(data));
}
}
However, I receive the following error whenever I try this:
"No provider for ServerComms! (App -> TicketService -> ServerComms)"
I do not understand why? Surely I do not need to inject every service that each other service relies upon? This can grow pretty tedious? This was achievable in Angular 1.x - how do I achieve the same in Angular 2?
Is this the right way to do it?
In short since injectors are defined at component level, the component that initiates the call services must see the corresponding providers. The first one (directly called) but also the other indirectly called (called by the previous service).
Let's take a sample. I have the following application:
Component AppComponent: the main component of my application that is provided when creating the Angular2 application in the bootstrap function
#Component({
selector: 'my-app',
template: `
<child></child>
`,
(...)
directives: [ ChildComponent ]
})
export class AppComponent {
}
Component ChildComponent: a sub component that will be used within the AppComponent component
#Component({
selector: 'child',
template: `
{{data | json}}<br/>
Get data
`,
(...)
})
export class ChildComponent {
constructor(service1:Service1) {
this.service1 = service1;
}
getData() {
this.data = this.service1.getData();
return false;
}
}
Two services, Service1 and Service2: Service1 is used by the ChildComponent and Service2 by Service1
#Injectable()
export class Service1 {
constructor(service2:Service2) {
this.service2 = service2;
}
getData() {
return this.service2.getData();
}
}
#Injectable()
export class Service2 {
getData() {
return [
{ message: 'message1' },
{ message: 'message2' }
];
}
}
Here is an overview of all these elements and there relations:
Application
|
AppComponent
|
ChildComponent
getData() --- Service1 --- Service2
In such application, we have three injectors:
The application injector that can be configured using the second parameter of the bootstrap function
The AppComponent injector that can be configured using the providers attribute of this component. It can "see" elements defined in the application injector. This means if a provider isn't found in this provider, it will be automatically look for into this parent injector. If not found in the latter, a "provider not found" error will be thrown.
The ChildComponent injector that will follow the same rules than the AppComponent one. To inject elements involved in the injection chain executed forr the component, providers will be looked for first in this injector, then in the AppComponent one and finally in the application one.
This means that when trying to inject the Service1 into the ChildComponent constructor, Angular2 will look into the ChildComponent injector, then into the AppComponent one and finally into the application one.
Since Service2 needs to be injected into Service1, the same resolution processing will be done: ChildComponent injector, AppComponent one and application one.
This means that both Service1 and Service2 can be specified at each level according to your needs using the providers attribute for components and the second parameter of the bootstrap function for the application injector.
This allows to share instances of dependencies for a set of elements:
If you define a provider at the application level, the correspoding created instance will be shared by the whole application (all components, all services, ...).
If you define a provider at a component level, the instance will be shared by the component itself, its sub components and all the "services" involved in the dependency chain.
So it's very powerful and you're free to organize as you want and for your needs.
Here is the corresponding plunkr so you can play with it: https://plnkr.co/edit/PsySVcX6OKtD3A9TuAEw?p=preview.
This link from the Angular2 documentation could help you: https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html.
Surely you do.
In Angular2, there are multiple injectors. Injectors are configured using the providers array of components. When a component has a providers array, an injector is created at that point in the tree. When components, directives, and services need to resolve their dependencies, they look up the injector tree to find them. So, we need to configure that tree with providers.
Conceptually, I like to think that there is an injector tree that overlays the component tree, but it is sparser than the component tree.
Again, conceptually, we have to configure this injector tree so that dependencies are "provided" at the appropriate places in the tree. Instead of creating a separate tree, Angular 2 reuses the component tree to do this. So even though it feels like we are configuring dependencies on the component tree, I like to think that I am configuring dependencies on the injector tree (which happens to overlay the component tree, so I have to use the components to configure it).
Clear as mud?
The reason Angular two has an injector tree is to allow for non-singleton services – i.e., the ability to create multiple instances of a particular service at different points in the injector tree. If you want Angular 1-like functionality (only singleton services), provide all of your services in your root component.
Architect your app, then go back and configure the injector tree (using components). That's how I like to think of it. If you reuse components in another project, it is very likely that the providers arrays will need to be changed, because the new project may require a different injector tree.
Well, i guess you should provide both services globally:
bootstrap(App, [service1, service2]);
or provide to component that uses them:
#Component({providers: [service1, service2]})
#Injectable decorator adds necessary metadata to track dependecies, but does not provide them.

Categories