I cant seem to figure out what the issue is,
I have a service that has a behavior subject like so...
popupSource = new BehaviorSubject<any>('');
popup(component) {
this.popupSource.next(component);
}
then In my header component
popupClick() {
this._service.popup('example');
}
then in my header.html
<button (click)="popupClick()"></button>
then In my app component
ngOnInit() {
this._service.popupSource.subscribe((result) => {
console.log(result);
})
}
so whats happening is the click is firing the this._service.popup('example'); but its never hitting the subscription...
I've put breakpoints on each function and It succesfully reaches this.popupSource.next(component) but then nothing?? Every Time I click the button I should be getting the console log.
Im not sure what Im doing wrong... Ive left out code for brevity sake so please let me know if you need more information
EDIT
Ive also tried doing
private popupSource = new BehaviorSubject<any>('');
getPopup = this.popupSource.asObservable();
popup(component) {
this.popupSource.next(component);
}
and the in my app component listend to the getPopup instead
ngOnInit() {
this._service.getPopup.subscribe((result) => {
console.log(result);
})
}
and that's not working either, I cant figure out what the problem is...
Thanks
Your issue here is that you provide your service in two different modules, that end up with two instances of your service. The simplest way if you are using Angular v6 is to use the providedIn flag in your service:
import { Injectable } from '#angular/core';
#Injectable({providedIn: 'root'})
class myService {}
With this way you don't need to provide your service in the providers array of any module, it will be automatically provided in the root injector.
Documentation about this can be found here : Angular providers.
If your are using Angular v5 you should only provide your service in your AppModule.
In your service, write a new method like this:
popupSource = new BehaviorSubject<any>('');
getPopupSource() {
return this.popupSource.asObservable();
}
And subscribe to that instead of subscribing directly to the subject itself. You could just add the 'asObservable()' part whenever you subscribe to it instead of creating an entirely new method, but I like the separate method so I can keep the Subject private, and I usually subscribe to something multiple times in different places throughout an app, so it cuts down on repetition.
In your component :
this._service.getPopupSource().subscribe( result => { etc...})
EDIT :
Demo recreation of your scenario - https://stackblitz.com/edit/angular-n6esd5
You may not have the same instance of service, my same problem was that I had #Injectable({ providedIn: 'root'}) for my service, but also I put this service in the component providers [] array, just remove the providers array, then it works
Related
I have been using BehaviorSubject to share data between components.
Say, relative url /article belongs to ArticleComponent. Then comes /article/list in ArticlelistComponent which contains a table with list of article details.On click of any row, i need that row detail to be passed to all the other component and that's my agenda.
Issue i face is,ngOnInit works only on page reload. Initially,only default {} gets reflected in articlecomponent which is nothing but the default result i have set for BehaviorSubject.WHen i click on a row in table, that data doesnot get updated and subscribed automatically in ngOnInit.the old value {} is retained throughout .I ain't sure where to place it inorder to make the changes reflect immediately. Please help resolving.
My code is below:
articlelist.component.ts
getSpecificDetail(value){
this.articleService.getArticleDetail(value);
}
article.service.ts
public articledata=new BehaviorSubject<Object>({});
currentdata=this.articledata.asObservable();
getArticleDetail(data){ //data comes from articlelist.component
return this.articledata.next(data);
}
article.component.ts
ngOnInit(){
this.articleService.currentdata.subscribe(data=>{
this.data=data;
console.log(this.data);
})
}
Edit: I have extended my observation in Angular2:RxJS Subject not responding to events
Depending on what you are doing with that data, another way to share data between components that are binding to that data is by leveraging Angular's change detection instead of using your own BehaviorSubject.
import { Injectable } from '#angular/core';
#Injectable()
export class DataService {
serviceData: string;
}
I have a blog post about it here: https://blogs.msmvps.com/deborahk/build-a-simple-angular-service-to-share-data/
And a plunker here: https://plnkr.co/edit/KT4JLmpcwGBM2xdZQeI9?p=preview
It should work like this (this is how I've always used Observables and Subjects). I always try to make sure I'm triggering and providing the subscription from the host service, to keep concerns separated.
article component
getSpecificDetail(value){
this.articleService.setData(value);
}
article.service.ts
articledata=new BehaviorSubject<Object>({});
getData() : Observable<Object> {
return this.articleData.asObservable();
}
setData(data) : void { this.articleData.next(data); }
article.component.ts
ngOnInit(){
this.patientService.getData().subscribe( data =>{
this.data=data;
console.log(this.data);
})
}
Any reason you're using a Behavior Subject specifically?
I'm currently trying to get a Register/Subscribe system to work with RxJs.
The situation is that I have component A with several sub components A1, A2, A3, ... The amount has to be dynamic. What I want to do now is that whenever an event I will call "somethingChanged" occurs (which is already distributed through an Observable) all sub components A1, ... will do some processing and then return some information (a state) as an event I'll call newStates to the parent action A probably using another observable. For this to work the sub components first have to register themselves to the "event manager" as children of A so that these events can be processed accordingly.
First idea
My first idea for this was to use a bufferCount on the newStates observable with the count being the amount of registered sub components. The problem is that the sub component registering and the parent component subscribing to the newStates observable is happening at almost the same time, the parent even being slightly faster which means the amountSub is usually 0 which breaks this attempt.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return newStates.bufferCount(amountSub).mergeMap();
}
Second idea
The second attempt was to use the somethingChanged Event and use that to initialize a takeLast to get the last items when they should be thrown. The problem is again as i will run into race condition as sub components take longer to throw their newStates events meaning I'll get old values.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return somethingChanged.map(() => newStates.takeLast(amountSub);
}
Third idea
So currently my only idea would be to catch the newStates event in the event manager, store the states in an array and check everytime if all registered components send them by looking at the array length. When all states are in i could then send the saved states and reset the array.
registerSubComponent() {
amountSub++;
}
getParentObservable() {
return newParentObservable;
}
newStates.subscribe(state => {
savedStates.push(state);
if(savedStates.length == amountSub) {
newParentObservable.next(savedStates);
savedStates = [];
}
});
Is this the only way or am I missing something so it could be done easier/with observables?
Btw: This is all pseudo code as my actual code also has to support multiple parent components in one manager making it cumbersome to read through.
It sounds like you want change detection up the tree. Using the following method with an angular service sounds like it might be what you need:
I found a solution on this guy Jason Watmore's blog that describes using rxjs Observables and Subjects. Using this method allows data changes to easily propagate up and down the angular inheritance tree to any component you want
Jason's Post
Accompanying Plunkr
Briefly:
You declare a service as a provider at the module.ts level with 3 methods:
sendMessage
clearMessage
getMessage
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MessageService {
private subject = new Subject();
sendMessage(message: string) {
this.subject.next({ text: message });
}
clearMessage() {
this.subject.next();
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
}
This service needs imports of Observable and Subject from rxjs
In each component you want to share data with:
create a subscription object in the constructor which calls the service.getMessage() function
call rxjs subscription.unsubscribe() in ngOnDestroy for each component so you aren't leaking memory
you can hook in a function to handle the incoming subscription updates
When you have data you want to share with your other components:
Create a public method which calls the service.sendMessage() method
This will send your updated data to each component and fire those functions you've hooked in to handle the changed data
I believe the blog post I linked to and the plunkr from the post say it best and have really helped me move data around efficiently in my own app but if you have any questions I'll do my best to answer them
I am an Angular2 beginner, creating a test note-taking app. The app is simple: I have a NotesContianerComponent which connects to a NoteService which has the getNotes() and addNote() method.
In the NotesContainerComponent, I am storing the data returned from service in a member myNotes and on ngOnInit event, refreshing the array. I am not touching that array anywhere in that code as of now.
Now, when I add a new note using the form and call the service's addNote() method from the NotesContainerComponent, the note does get added to the backend mock notes array, but at the same time the UI (i.e. NotesContainerComponent) gets updated with the new note automatically! I am not doing anything to refresh it manually.
To investigate, I added a console.log() to the getNotes method of the service, however it is being only called the first time, possibly by ngOnInit hook.
What I cannot figure out is, how does Angular2 know about the new note without even querying the service automatically? And how to stop this? Any clues will be appreciated.
NotesContainerComponent code for reference:
export class NotesContainerComponent implements OnInit {
constructor(
private noteService: NoteService
) { }
addNote(newNote):void {
this.noteService.add(newNote);
}
myNotes: Notes[];
ngOnInit() {
this.noteService.getNotes()
.then(notes => this.myNotes = notes);
}
}
If you are storing your mock data in an Object or an Array and this.myNotes's reference is that Objects reference. When your mock datas content changes, all the references will change too. This is because objects are mutable in javascript.
I just started playing with angular 2 and i've ran into a small problem, that i ve searched for in various forms and also angulars documentation.
I've managed to make a service that makes a call and then i want in a component when i press a button to load another component with dynamicload component and have access to the ajax result.
The problem is that I can t figure out how to do that..
The question is how can I make the result accesible in other components using Observables or Promises method.
If I understood correctly your question, you are looking a way to insert a data from request to another nested component.
I hope this image will clarify for you the data flow for this case.
Your Root component is calling a service method which returns for you promise object.
Then you map needed data from response to the component model inside Root Component constructor.
And your Child component should be subscribed for the model which you was preparing in previous step.
ngOnInit() {
this.dataService.getSomeData()
.subscribe((data: IData) => {
this.data = data;
});
}
Just a short example above how to set model in the root component from the promise object to the local model.
New research:
There is another way to fill your components by data from api's. You can use EventEmitter to emit event from service, and then, you can subscribe for this event inside you created components, so they will get a data, each time there will be called the service. Here is nice example of this strategy in the first answer. Service Events
Hope it will help you, let me know if you will need additional info!
Just create a service, then inject the service where you want.
Here it's an example how to share a service ajax data across many components without making the request twice :
https://stackoverflow.com/a/36413003/2681823
the Service:
#Injectable()
export class DataService {
constructor(private http: Http) { }
private _dataObs = new ReplaySubject<request>(1);
getData(forceRefresh?: boolean) {
// On Error the Subject will be Stoped and Unsubscribed, if so, create another one
this._dataObs = this._dataObs.isUnsubscribed ? new ReplaySubject(1) : this._dataObs;
// If the Subject was NOT subscribed before OR if forceRefresh is requested
if (!this._dataObs.observers.length || forceRefresh) {
this.http.get('http://jsonplaceholder.typicode.com/posts/2')
.subscribe(
requestData => {
this._dataObs.next(requestData);
},
error => this._dataObs.error(error));
}
return this._dataObs;
}
}
the Component:
#Component({
selector: 'child',
template : `<button (click)="makeRequest()" class="btn">Click me!</button>`
})
export class Child {
constructor(private _dataService: DataService) { }
makeRequest() {
this._dataService.getData().subscribe(
requestData => {
console.log('ChildComponent', requestData);
}
}
}
A full working example/plunker can be found here : http://plnkr.co/edit/TR7cAqNATuygDAfj4wno?p=preview
I am currently working on porting a Backbone project to an Angular 2 project (obviously with a lot of changes), and one of the project requirements requires certain methods to be accessible publicly.
A quick example:
Component
#component({...})
class MyTest {
private text:string = '';
public setText(text:string) {
this.text = text;
}
}
Obviously, I could have <button (click)="setText('hello world')>Click me!</button>, and I would want to do that as well. However, I'd like to be able to access it publicly.
Like this
<button onclick="angular.MyTest.setText('Hello from outside angular!')"></click>
Or this
// in the js console
angular.MyTest.setText('Hello from outside angular!');
Either way, I would like the method to be publicly exposed so it can be called from outside the angular 2 app.
This is something we've done in backbone, but I guess my Google foo isn't strong enough to find a good solution for this using angular.
We would prefer to only expose some methods and have a list of public apis, so if you have tips for doing that as well, it'd be an added bonus. (I have ideas, but others are welcomed.)
Just make the component register itself in a global map and you can access it from there.
Use either the constructor or ngOnInit() or any of the other lifecycle hooks to register the component and ngOnDestroy() to unregister it.
When you call Angular methods from outside Angular, Angular doesn't recognize model change. This is what Angulars NgZone is for.
To get a reference to Angular zone just inject it to the constructor
constructor(zone:NgZone) {
}
You can either make zone itself available in a global object as well or just execute the code inside the component within the zone.
For example
calledFromOutside(newValue:String) {
this.zone.run(() => {
this.value = newValue;
});
}
or use the global zone reference like
zone.run(() => { component.calledFromOutside(newValue); });
https://plnkr.co/edit/6gv2MbT4yzUhVUfv5u1b?p=preview
In the browser console you have to switch from <topframe> to plunkerPreviewTarget.... because Plunker executes the code in an iFrame. Then run
window.angularComponentRef.zone.run(() => {window.angularComponentRef.component.callFromOutside('1');})
or
window.angularComponentRef.zone.run(() => {window.angularComponentRef.componentFn('2');})
This is how i did it. My component is given below. Don't forget to import NgZone. It is the most important part here. It's NgZone that lets angular understand outside external context. Running functions via zone allows you to reenter Angular zone from a task that was executed outside of the Angular zone. We need it here since we are dealing with an outside call that's not in angular zone.
import { Component, Input , NgZone } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'example',
templateUrl: './example.html',
})
export class ExampleComponent {
public constructor(private zone: NgZone, private router: Router) {
//exposing component to the outside here
//componentFn called from outside and it in return calls callExampleFunction()
window['angularComponentReference'] = {
zone: this.zone,
componentFn: (value) => this.callExampleFunction(value),
component: this,
};
}
public callExampleFunction(value: any): any {
console.log('this works perfect');
}
}
now lets call this from outside.in my case i wanted to reach here through the script tags of my index.html.my index.html is given below.
<script>
//my listener to outside clicks
ipc.on('send-click-to-AT', (evt, entitlement) =>
electronClick(entitlement));;
//function invoked upon the outside click event
function electronClick(entitlement){
//this is the important part.call the exposed function inside angular
//component
window.angularComponentReference.zone.run(() =
{window.angularComponentReference.componentFn(entitlement);});
}
</script>
if you just type the below in developer console and hit enter it will invoke the exposed method and 'this works perfect ' will be printed on console.
window.angularComponentReference.zone.run(() =>
{window.angularComponentReference.componentFn(1);});
entitlement is just some value that is passed here as a parameter.
I was checking the code, and I have faced that the Zone is not probably necessary.
It works well without the NgZone.
In component constructor do this:
constructor(....) {
window['fncIdentifierCompRef'] = {
component = this
};
}
And in the root script try this:
<script>
function theGlobalJavascriptFnc(value) {
try {
if (!window.fncIdentifierCompRef) {
alert('No window.fncIdentifierCompRef');
return;
}
if (!window.fncIdentifierCompRef.component) {
alert('No window.fncIdentifierCompRef.component');
return;
}
window.fncIdentifierCompRef.component.PublicCmpFunc(value);
} catch(ex) {alert('Error on Cmp.PublicCmpFunc Method Call')}
}
</script>
This works to me.
The problem is that Angular's components are transpiled into modules that aren't as easy to access as regular JavaScript code. The process of accessing a module's features depends on the module's format.
An Angular2 class can contain static members that can be defined without instantiating a new object. You might want to change your code to something like:
#component({...})
class MyTest {
private static text: string = '';
public static setText(text:string) {
this.text = text;
}
}
Super simple solution!! save component or function with an alias outside
declare var exposedFunction;
#Component({
templateUrl: 'app.html'
})
export class MyApp {
constructor(public service:MyService){
exposedFunction = service.myFunction;
}
at index.html add in head
<script>
var exposedFunction;
</script>
Inside exposed function do not use this. parameters if you need them you will have to use closures to get it to work
This is particularly useful in ionic to test device notifications on web instead of device