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.
Related
EDIT 2: This appears to be my general problem, and solution (using setTimeout so Angular's lifecycle can happen). I'll either close this or post an answer to my own question when I can.
See EDIT for a simpler repro that doesn't involve Subjects/Observables but is essentially the same problem.
I have a parent component that's responsible for fetching data from a service.
export class ParentComponent implements OnInit {
public mySubject: Subject<Foo[]> = new Subject<Foo[]>();
public buttonClicked = false;
private currentValues: Foo[] = null;
constructor(private SomeService myService) { }
this.myService.get().subscribe(values => {
this.mySubject.next(values); // Does NOT work when a child component is hidden, as expected.
this.currentValues = values; // Keep value so we can manually fire later.
});
private buttonClickHandler() {
this.buttonClicked = true;
this.mySubject.next(this.currentValues);
}
}
This data is subscribed to in the HTML by a child component. This component is hidden by default via *ngIf, and only becomes visible on a button click:
<app-child-component [values]="mySubject.asObservable()" *ngif="buttonClicked" />
In the parent component above you see I'm trying to pass the current available data to the child by invoking next() when the component is made visible in some way:
this.mySubject.next(this.currentValues);
This does not work when initially un-hiding the component via *ngIf. If I click the button a second time, which then calls next() again, then it works as expected. But when Angular is in the current context of un-hiding something, observables aren't getting their data. (This also happens when things are unhidden by other means, but the result is the same: If in the same method, the subject/data passing does not work; the component has to already be visible as of the method call.)
I'm guessing the binding to the observable is not happening until after *ngIf shows the child component, after the method call resolves. Is there some place I can hook into that I can then pass child data down?
EDIT for clarification: I don't believe this is an issue of Subject vs. BehaviorSubject. I'm not having issue passing the data. The issue is that the data-passing (confirmed via console.log()) is not occurring at all in the first place. It's not that the child component is receiving a null value. The subscription just isn't firing to the child.
I found I can reproduce this in a simpler fashion too: Trying to select an element in the DOM of *ngIf HTML reveals undefined if I make *ngIf's value true within the same Angular method.
<div *ngIf="buttonClicked">
<div id="someElement">Foo</div>
</div>
public someMethod(): void {
this.buttonClicked = true;
const container = document.getElementById('someElement'); // DOES NOT WORK if this.buttonClicked was false at the start of this method!
}
You going to need to use a BehaviourSubject instead of Subject, which emits the previously set value initially.
What is the difference between Subject and BehaviorSubject?
I need to make a reusable component in angular that will be packaged into a npm module. Being new to angular I've hit a roadblock with the UI change detection.
for the component to work first the application needs to make an api call to collect the data required for setup then the component needs to run some initalization.
this.dataService.getData().subscribe((data) => {
this.data = data;
this.npmComponent.init(this.data);
});
<app-npm-component></app-npm-component>
In my component I then need to run a bunch of setup tasks and afterwards it needs to listen for changes in the data rerun a sub set of setup tasks and update the ui.
#Component({
...,
changeDetection: ChangeDetectionStrategy.OnPush
});
...
init(setupData: any): void {
this.cdr.detectChanges();
this.data = setupData;
}
<ul *ngIf="data.arrayOfStuff">
<li *ngFor="let item of data.arrayOfStuff">{{item}}</li>
</ul>
the usual ChangeDection implementation throws an error: NullInjectorError: No provider for ChangeDetectorRef! since the data isn't being passed from it's parent via the template.
Whats the right way to implement this kind of thing? Thanks in advance for your time.
Edit: I found the answer in ViewChild
#ViewChild(NpmComponent, {static: false}) npmComponent: NpmComponent;
Put an input on the child
<app-npm-component [data]="setupData"></app-npm-component>
and in the child component's class
#Input() data;
Angular will take care of listening for changes to inputs.
You can also do it with subscribing using the async pipe.
In the parent component
setupData$ = this.dataService.getData();
and pass the data to the child with the async pipe
<app-npm-component [data]="setupData$ | async"></app-npm-component>
No subscriptions required.
If you need to run the data through a function use a map
setupData$ = this.dataService.getData().pipe(map(data => yourFunction(data)));
I'm making a pop-up component that I want to use in several of my other components, so I made a popup.service that enable the component to be loaded through *ngIf inside other components. This is creating a problem for me since the PopupComponent is a separate entity and I'm unsure how to pass data from the child component(PopupComponent) to its respective parents.
Atm the loading looks like this in ParentComponent.ts:
public openPopup(order_id: string, invoice_id: string): void{
this.load_popup=this.popupService.openPopup(order_id, "selected_order", invoice_id, "selected_invoice");
}
And ParentComponent.html:
<app-popup *ngIf="load_popup"></app-popup>
And it loads like a charm, the problem is in closing it. The close button is located on the PopupComponent, is there an efficient way to have the Child Component (PopupComponent) to affect a variable in the Parent Component ie. ParentComponent.load_popup=false?
My other thought was dynamically loading the component, however I have no idea on how to do that. I was fidgeting around with using the PopupService and putting something like this in it:
import { Injectable, ComponentRef } from '#angular/core';
import {PopupComponent} from '../popup/popup.component';
#Injectable({
providedIn: 'root'
})
export class PopupService {
popup_ref: ComponentRef<PopupComponent>
constructor(
) { }
//Implemented in orderoverviewcomponent, invoicecomponent, and placeordercomponent
public openPopup(id1:string, storage_label1:string, id2:string, storage_label2:string): Boolean{
if (id1){
localStorage.setItem(storage_label1, JSON.stringify(id1));
}
if (id2){
localStorage.setItem(storage_label2, JSON.stringify(id2));
}
this.popup_ref.initiate(); //this line is a made up example of loading the component
return true;
}
public closePopup(storage_label1: string, storage_label2:string): Boolean{
if(storage_label1){
localStorage.removeItem(storage_label1);
}
if(storage_label2){
localStorage.removeItem(storage_label2);
}
this.popup_ref.destroy();
return false;
}
}
Where this.popup_ref.destroy(); would ideally destroy PopupComponent, but when I did that I got a "cannot read property of undefined" on the popup_ref, I'm having trouble declaring it, the syntax seems a bit tricky.
The problem also remains that i need a function to load the component, the opposite of .destroy(), if this is possible I would much prefer it over loading and destroying with *ngIf.
Edit: Partially solved it by just using a boolean in the service as the trigger for *ngIf, is there a way to do a function load and destroy on a component still?
You can bind an EventEmitter() to your component to invoke a function in the parent component.
<app-popup [onClose]="load_popup = false" *ngIf="load_popup"></app-popup>
Then inside of your app-popup component:
#Output onClose = new EventEmitter();
public closePopup(/* code emitted for brevity */) {
/* code emitted for brevity */
this.onClose.emit(); //Call the parent function (in this case: 'load_popup = false')
}
It's important to know that you can pass entire functions to the bound function, and you can even pass variables back to the parent from the child:
[onClose]="myFunction($event)"
this.onClose.emit(DATA HERE);
As an aside, since you're using Angular; I would suggest looking into using Modals for popup dialogue boxes. You can see a good example here:
https://ng-bootstrap.github.io/#/components/modal/examples
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.
How do you properly pass a function from a parent to a child component when the function takes in parameters?
In the ngOnInit, how to scope a function like:
addToList(id) {
this.store.dispatch(this.listActions.addToList(id));
}
ngOnInit, which is wrong right now.
ngOnInit() {
this.addToList = this.addToList.bind(this, id);
}
In my parent component, I have the addToCart(id) function.
I want to pass that function to my child component, which has a list of items, and on clicking the ADD button on an item, I want to callback addToCart(item_id) to the parent.
#Maarek's answer is a good one, and is the 'right' way to do it, probably. What I am presenting here is a simpler means of communicating specifically from the Child to the Parent.
What you proposed in the original post was to have the Parent send a callback method to the Child, so the Child can call it with data when appropriate. To accomplish this specific task (data from Child to Parent on some action in the Child) using Events is appropriate, using the EventEmitter from inside the Child. See this API reference which has an example: https://angular.io/docs/ts/latest/api/core/index/EventEmitter-class.html and this Plunker I made as a demo: https://embed.plnkr.co/T1wFqVOhMXgX6NRfTuiC/
In the child, you have code like this:
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'item',
template: `
<div class="item">
<button type="button" (click)="addItem()">Add</button>
<p>{{id}}
</div>
`
})
export class ItemComponent {
#Input() id: string;
//key line here: this emitter can be bound to by parent to get notifications
#Output() add: EventEmitter<string> = new EventEmitter<string>();
constructor() { }
addItem() {
//then when the button is clicked, emit events to the parent.
this.add.emit(this.id);
}
}
The Parent would call create the component like this:
<item id="1" (add)="addToList($event)"></item>
Where addToList() is a function on the Parent that does the work your callback was intended to do. The $event is the data passed from the child (the id).
There's not a lot of detail here, but from what I'm gathering I think what you will want is an injectable service (demonstrated here: https://angular.io/docs/ts/latest/tutorial/toh-pt4.html) to handle the data objects being shared between the components. Rather than type a bunch of code in here (which is better shown at that page in the tutorial) I'll describe what I think you're trying to do and how I'd go about doing it.
The entire store data model can be handled via a service (store.service.ts maybe). Which will have your CRUD functions exposed for the different properties of the store model. The list you are adding to here should have a public getter that returns an observable of the list in the service as well as a public function for adding and deleting from the list. Something like this:
#Injectable
export class StoreService {
private _storeList:BehaviorSubject<Array<any>> = new BehaviorSubject<Array<any>>([]);
/*I'm sure the store has other properties, set them up here. I'd suggest
breaking any arrays out of the general object (unless you want to use
pipes which are awesome but keeping it simple here) but if the store has
a lot of general properties (name, address, whatever) they can be stored
in a single BehaviorSubject of type any.
*/
constructor(){}
get StoreList() { return this._storeList.asObservable() }
public addToList(id) {
let curVal = this._storeList.getValue();
curVal.push(id);
this._storeList.next(curVal);
}
}
You would then inject this service into the constructor of both the parent and the child constructor(private _storeService:StoreService){} (and any other components that need it). The child could then subscribe to the list: get List() { return this._storeService.StoreList } and the parent can call the add function to add to the list. One thing to note, when you add this to your template as an *ngFor, make sure to pass the value through the async pipe. *ngFor="List | async" or your may tear your hair out trying to figure out why you're getting errors.
This article helped me a lot with this as well (although I might suggest avoiding immutable at first until you're comfortable with Angular 2 completely): http://blog.angular-university.io/how-to-build-angular2-apps-using-rxjs-observable-data-services-pitfalls-to-avoid/