Subject in RxJs and EventEmitter in Angular2 - javascript

What's the difference between them, when and how to use them? I read that Subject is the equivalent to an EventEmitter.
If I want to rewrite this, how?
import { Injectable} from '#angular/core';
import { Subject,BehaviorSubject } from 'rxjs';
import {Playlists} from 'channel' /** Assumes this is where you have defined your Playlists interface **/
#Injectable()
export class PlaylistService {
private _currentPlaylists$: Subject<Playlists> = new BehaviorSubject<Playlists>(null);
constructor() {}
currentPlaylists() {
return this._currentPlaylists$.asObservable();
}
setCurrentPlaylists(playlists:Playlists){
this._currentPlaylists$.next(playlists);
}
}

EventEmitters should be used only when implementing custom events in Angular2 components with the Output decorator:
#Output()
someEvent: EventEmitter = new EventEmitter();
In other cases, you can use Subjects (from Rxjs) since it's not related to Angular2 particular feature.
EventEmitter internally extends Subject. See https://github.com/angular/angular/blob/master/modules/%40angular/facade/src/async.ts#L63
Your code looks good to me ;-)

Related

how to share objects between sibling components using services?

I need to call a service on a component, and ideally this service would fetch info in another 2 or 3 components (which already are communicated with the database, etc). I need to be able to share objects.
I've created a service called DashService, like this:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class DashService {
constructor() { }
}
I've linked it to the component that will us it, and I also imported and added the service name in the 'providers' part of the NgModule.
Added:
Import { OnInit } from '#angular/core';
and
implements OnInit{...}
on the module that will send info to the service.
I dont know how to go forward. How can I share an object that exists on a component, to my service? I fail to set the Constructor correctly (on the component sending the info)
When I try
public constructor( private DashService: DashService)
I get an error telling me 'DashService refers to a value but is being used as a type'
Thank you for your help.
Because you are calling the service as the type of the service!
Just change the name, usually the first letter of the service is in lowercase
public constructor( private dashService: DashService)
https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service
here you can find an example how to communicate between components.In general for parent/child components you can use #Input / #Output decorator.
For others case you can use a service ( or a any state management)
Riccardo Gai is right, you should do that to use a service,
public constructor( private dashService: DashService)
But to answer your question, in order to be able to share data between components through a service, you should create an object in that service, for example a public object, then you can access that object through the service.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class DashService {
objectToShareHere: any;
constructor() { }
}
I think anyway, the best way of this to work is to have get and set methods and access the object through them (having a private obj). Like this:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class DashService {
private objectToShareHere: any;
constructor() { }
setPrivateObj(obj) {
this.objectToShareHere = obj;
}
getPrivateObj() {
return this.objectToShareHere;
}
}
If the data you need to share is from a BE, you can use a Subject object (see RxJs subject here)

Emit method of EventEmitter not emitting values to subscribers

So I am trying to connect two components through a service, LoaderComponent and AppComponent with LoaderService, so that when app fetches data a loader shows up. But when I try to use an EventEmitter to emit changes to the components they don't get the changes but when the service subscribes
to itself, it can get the changes
LoaderService.ts
import { EventEmitter, Injectable } from '#angular/core';
#Injectable()
class LoaderService {
#Output change: EventEmitter<number> = new EventEmitter<number>();
private state: number = 0;
constructor() {
this.change.subscribe(state => console.log(state));
this.setState(1)
}
setState(state: number) {
this.state = state;
this.change.emit(this.state);
}
}
// Shows state when but outside of the service event is not detected, also tried EventEmitter from from events
I expect to get events from the LoaderService to subscribers
You need to use LoaderService in some component for angular to create it, if we do not use the service any where angular will automatically discard it. Inject LoaderService in app component like below:
constructor(private _loadService: LoaderService) {} and then you will see the console.log().
Also, it is recommended to use either Subject or Behavior Subject from Rxjs instead of Output in a service.
First thing, EventEmitters and Outputs don't belong in a service.
I will refactor to use subjects and also protect your subject by making it private and exposing a public observable, this limits how your subject state can be modified, this is not required but is generally considered good practice:
import { Injectable } from '#angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
#Injectable()
class LoaderService {
private change: Subject<number> = new Subject<number>();
change$: Observable<number> = this.change.asObservable();
private state: number = 0;
constructor() {
this.change$.subscribe(state => console.log(state));
this.setState(1)
}
setState(state: number) {
this.state = state;
this.change.next(this.state);
}
}
Second, this is likely an issue with how you provide your service. If you have an app component template like:
<loader-component></loader-component>
<loader-component></loader-component>
with 2 loader components side by side, and the loader component has a providers array like:
providers: [LoaderService]
then these 2 loaders are receiving different copies of the same service as they each provide and inject their own, so they will not see each other's events.
To remedy this, you provide instead in the app component (and not in the loader component) so they have the same copy of the service, because then the parent is providing the service that each of them inject. If you were to provide in both app component and loader component, they would all receive a different copy.
If you provide at root (module level) then every component that injects the service (and doesn't provide it's own) will receive the same copy of that service.
The appropriate place to provide a service is dependent on your app's needs and the function of the particular service.

How to declare a service with Angular5

I have an Angular service that looks like this
#Component({})
#Inject(ChromeDataService)
#Injectable()
export class MainDataService {
}
when I run ng build --prod, I get this error
ERROR in : No template specified for component MainDataService
my only guess is that an Angular service does not need to be a component? So I removed the #Component annotation, but then I get this:
ERROR in : Unexpected value 'MainDataService in
/home/.../main.ts'
declared by the module 'SharedModule in
/home/.../src/app/shared.module.ts'.
Please add a #Pipe/#Directive/#Component annotation.
Uh, how do I create a service in Angular5?
To use an angular service properly you only need injectable()
Here's an example
#Injectable()
export class myService {
//some logic
}
then in your app.module or in a feature module you add the service in the providers array and to have the angular DI handle the service for you.
#Component({
usual stuff with template, selector, css})
export class someComponent {
constructor(private myService: MyService){}
}
The constructor will tell angular to auto-magically inject the service you need in.
your service only needs the #Injectable() decorator.like this
import {Injectable} from '#angular/core'
import {Router} from '#angular/router'
#Injectable()
export class AuthService {
constructor(private router: Router) {}
}
That's how I declare services in Angular:
#Injectable()
export class AuthService {
...
}
I think you only need #Injectable decorator. Maybe your problem comes from shared.module.ts.

Angular2: Service with Model - "no provider for model"

What I'm trying to do is create a service that uses a model to show an alert. The alert-model should be necessary nowhere else but in that service but I am not able to make this work. My service:
import {Injectable, Inject} from "angular2/core";
import {AlertModel} from "../models/alert.model";
#Injectable()
export class AlertService {
constructor(#Inject(AlertModel) alertModel: AlertModel) {
}
public alert(){
this.alertModel.message = 'success';
//...
}
}
But I keep getting this error:
Uncaught (in promise): No provider for AlertModel! (UserComponent -> AlertService -> AlertModel)
I'm new to angular and I do not understand this. What am I missing? Thanks in advance!
You need to provide the AlertModel somewhere
bootstrap(AppComponent, [AlertModel])
or in the root component (preferred):
#Component({
selector: 'my-app',
providers: [AlertModel],
...
})
Ensure AlertModel has the #Injectable() decorator and all its constructor parameters are provided as well (if it has any)
#Inject(AlertModel) is redundant if the type of the constructor parameter is already AlertModel. #Inject() is only necessary if the type differs or if AlertModel doesn't have the #Injectable() decorator.
constructor(#Inject(AlertModel) alertModel: AlertModel) {
You have this error since there is no provider for the AlertModel class visible from the UserComponent component (that calls the service). You can define either this class in the providers attribute of the component either when bootstrapping your application.
See the question to know more about how hierarchical injectors works and how to inject things into services:
What's the best way to inject one service into another in angular 2 (Beta)?
Since the AlertModel class seems to be a model class I don't think that you need to inject it. You can simply import the class and instantiate it:
#Injectable()
export class AlertService {
alertModel: AlertModel = new AlertModel();
public alert(){
this.alertModel.message = 'success';
//...
}
}

React/Flux Dispatcher as Singleton in Typescript

I'm rebuilding the Atlassian React+Flux tutorial in TypeScript (using bindings of React, Flux and Node from tsd) but I'm encountering a problem with implementing the dispatcher, which essentially is a singleton.
I'm trying the following:
AppDispatcher.ts
export var instance = AppDispatcher.getInstance();
class AppDispatcher extends Flux.Dispatcher<AppPayload> {
private static _instance:AppDispatcher = new AppDispatcher();
constructor() {
super()
if(AppDispatcher._instance){
throw new Error("Error: Using this constructor should not be possible...WTH javascript");
}
AppDispatcher._instance = this;
}
public static getInstance():AppDispatcher
{
return AppDispatcher._instance;
}
...
}
Like in the tutorial I use a JSON file on localStorage to fake a web API. So my ActionCreator does the following:
ActionCreator.ts
import AppDispatcherInstance = require('../dispatcher/AppDispatcher');
//ActionCreator
//In Flux ActionCreators
export function receiveAll(rawNodes:any) {
AppDispatcherInstance.instance.handleServerAction(AppDispatcherInstance.ActionIdentifier.REFRESH_FROM_SERVER, rawNodes)
}
Unfortunately I get a runtime exception Uncaught TypeError: Cannot read property 'getInstance' of undefined pointing to the transpiled Javascript line
exports.instance = AppDispatcher.getInstance();
Am I implementing the singleton wrong in TypeScript?
Is there a better way in general to do this? I thought about not writing a class at all and to the singleton by only exporting certain functions (like recommended in other SO questions) but I still want to extend the Flux.Dispatcher class?
Here's how I've been using singletons with modules in a React/Flux project:
class AppDispatcher extends Flux.Dispatcher {
// ...
}
export default new AppDispatcher();
The singleton can be imported in another file like:
import AppDispatcher from "./AppDispatcher";
AppDispatcher.dispatch(...);
If you need the class type exported, you can add export to class AppDispatcher and import the singleton by a different name if you need both in the same file (I tend to need this in unit tests):
import AppDispatcherInstance, {AppDispatcher} from "./AppDispatcher"
As the commentor suggests, you need to move code that consumes a class below the declaration of that class. Classes are just variables, and variables in JavaScript get initialized in order.
In general, you shouldn't need to use the explicit class singleton pattern in TypeScript. See How to define Singleton in TypeScript
Useless singleton pattern
class Singleton {
/* ... lots of singleton logic ... */
public someMethod() { ... }
}
// Using
var x = Singleton.getInstance();
x.someMethod();
Namespace equivalent
namespace Singleton {
export function someMethod() { ... }
}
// Usage
Singleton.someMethod();
var x = Singleton; // If you need to alias it for some reason

Categories

Resources