I'd like to refresh my card set from navigation bar which is part of app.component.html so I prepared refresh() function.
When it is called it does update variable Cards but doesn't render it in ngFor on html element in mainView.html.
It does render updated set if I call from html element in mainView.html (as (click)="loadCards()") but not if the same ((click)="refresh()") is done in app.component.html.
export class MainView implements OnInit {
constructor(private mMainController: MainController) {}
Cards: any = [];
ngOnInit() {
this.loadCards();
}
loadCards() {
this.mMainController.getAllCards().subscribe(
(data) => {this.Cards = data); },
(error) => {},
() => {console.log(this.Cards));
}
...
}
export class AppComponent {
...
constructor(private router: Router, private mMainView: MainView) {}
refresh(){
console.log('done');
this.mMainView.loadCards();
}
...
}
Update
Tried with #Input() but couldn't get it work. I implemented RefreshService as explained in accepted answer and now I'm able to refresh content from other components.
Thank you all for quick response.
FIST WAY: USING A SHARED SERVICE
You need to introduce a service that manage the state of your car.
In this case it may be usefull to introduce for this a BehaviorSubject like this:
Your Service:
private refresh: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public getRefresh(): Observable<boolean> {
return this.refresh.asObservable();
}
public setRefresh(value: boolean): void {
this.refresh.next(value);
}
Inside your MainView class
First: inject your service as dependency
Second: Subscribe to your observable inside OnInit hook e.g like this:
this.myService.getRefresh().subscribe((value: boolean) => {
if(value) {
this.loadCards()
}
})
Inside your AppComponent class
First: inject your service as dependency
Second: Set the value of your observable inside your refresh method.
e.g something like this:
public refresh(){
this.myService.setRefresh(true);
}
SECOND WAY: USING #Input Decorator to pass value down.
You're attempting to use MainView as a dependency but it's not an injectable dependency. Try to use inputs/outputs between app component and MainView, if possible. If MainView isn't a child of AppComponent then abstract the logic for loading cards into a service and inject it into both.
You can implement the component interaction in two ways
(i) If the components are related to each other use the common and straightforward method of sharing data. It works by using the #Input() decorator to allow data to be passed via the template.
(ii) If the components are not related to each other you can use a shared service using subject to communicate between the two components
Related
in home component, a line like ...<app-root [message]="hii"> is opening that app-root comp , with value to app-root component which has #input and {{message}} in html is working..
But i need to redirect to that app-root component instead of opening in current component.
any ways like "button onclick to redirect to that with [message]="hi" as data?
the best way to shared data between an unrelated component in the angular is, use the "BehaviorSubject" from rxjs library.
we imagine that we have two components that there are completely unrelated and we want to shared data between them.
first of all, you should create a service file.
Imagine that we have a service with the name of sharing-data.serice.ts :
import { BehaviorSubject, Subject } from 'rxjs';
import { Injectable } from '#angular/core';
#Injectable({
providedIn:'root'
})
export class SharingDataService {
public subject = new BehaviorSubject("");
getDataFromFirstComponent(x){
this.subject.next(x);
}
sharedDataWithSecondComponent(){
return this.subject.asObservable();
}
}
here we use BehaviorSubject class to make a pipe between unrelated components for transferring data.
in the first component.ts we inject the service by dependency injection, then make a method to use this service and fill the pipe by data :
constructor(
private service : SharingDataService,
) { }
sendDataOutOftheComponent(){
this.service.getDataFromFirstComponent(this.dataFormFirst);
}
now in the second component we just need to use this service again and after that subscribe to the method to get the data.
in the second component.ts we have the following code :
constructor(
private service : SharingDataService,
) { }
getDataFromFirstComponent(){
this.service.sharedDataWithSecondComponent()
.subscribe(data=>{
this.dataFromFirst= data;
})
}
with this method, you can easily shared data between unrelated components in the angular
I have a UserService that when my user signs on my application, I populate it with the data from the user to show in all my components.
But I'm facing a little problem When I reference the object like this:
ngOnInit() {
this.userModel = this.userService.getUserModel();
}
He works fine for the first load, but I have a page that changing the user data and I want to be reflected for all my application, so when I change this object this.userModel, the changes don't reflect all my components is like just a copy from the object from Service.
The solution that I make is Putting this object directly in my HTML files this.userService.getUserModel() but it is too large and I want to avoid that, just for a good syntax.
The question is how I can make a real reference to this variable without putting this service method directly on my components.
You could try and use observable, then subscribe to it.
For example:
public function getUserModel(): Observable<IUser> {
return this.apiService.getUser(); // This is an observable
}
Then on your component, instead of doing this:
this.userModel = this.userService.getUserModel();
You should instead do this:
this.userService.getUserModel().pipe(takeUntil(this._onDestroy$)).subscribe(user => {
this.userModel = user;
}
Pointers
this._onDestroy$ is a really easy way to unsubscribe when the component is destroyed, helps minimize memory leaks.
So you can add:
private _onDestroy$: Subject<any> = new Subject<any>();
Then in your ngOnDestroy
ngOnDestroy() {
this._onDestroy$.next();
this._onDestroy$.complete();
}
So, this should update the value in your HTML automatically.
I hope this helps.
You can create an EventEmitter that emits the userModel every time it changes. And then, eu get that observable on the classes that you want to be notified, and subscribe to the changes. Like this.
class UserService {
userModelEvent: EventEmitter<UserModel> = new EventEmitter();
}
class AnotherClass {
userModel: UserModel;
constructor(userService: UserService) {}
onInit() {
this.userService.userModelEvent.subscribe(userModel => this.userModel = userModel);
}
}
I have two methods in a service that both hit different controllers for searching my database. I'd like to make a shared child component which will be used in two different places, however I need each instance to be able to specifically call one of the service methods. For example, these are my methods:
// SERVICE
searchOne(search: string){
// do some searching in Controller 1
}
searchTwo(search: string){
// do some searching in Controller 2
}
With my shared child component having a similar method to hit the service:
// CHILD COMPONENT
#Input(Function) func: Function;
componentSearch(search: string){
this.func(search);
}
However, this doesn't seem to work when trying to pass a function from a parent like so:
// PARENT COMPONENT
func = this.service.searchOne;
constructor(private service: Service){}
// PARENT COMPONENT.HTML
<app-child-component [func]="func"></app-child-component>
How do I pass access or reference of the service functions into my child component?
I have created a working Stackblitz for your code here
Few corrections in your code:
Parent component:
export class AppComponent {
name = 'Angular';
func;
constructor(private service: AppService){
this.func = this.service.searchOne;
}
}
Child Component
#Input() func: Function;
componentSearch(search: string){
this.func(search);
}
I'm trying to send the result of HttpClient post requests multiple components in my Angular app. I'm using a Subject and calling its next() method whenever a new post request is successfully executed. Each component subscribes to the service's Subject.
The faulty services is defined as
#Injectable()
export class BuildingDataService {
public response: Subject<object> = new Subject<object>();
constructor (private http: HttpClient) { }
fetchBuildingData(location) {
...
this.http.post(url, location, httpOptions).subscribe(resp => {
this.response.next(resp);
});
}
The components subscribe to BuildingService.response as follows
#Component({
template: "<h1>{{buildingName}}</h1>"
...
})
export class SidepanelComponent implements OnInit {
buildingName: string;
constructor(private buildingDataService: BuildingDataService) { }
ngOnInit() {
this.buildingDataService.response.subscribe(resp => {
this.buildingName = resp['buildingName'];
});
}
updateBuildingInfo(location) {
this.buildingDataService.fetchBuildingData(location);
}
}
updateBuildingInfo is triggered by users clicking on a map.
Retrieving the data from the server and passing it to the components works: I can output the payloads to the console in each component. However, the components' templates fail to update.
After Googling and fiddling for most of today I found that this implementation does not trigger Angular's change detection. The fix is to either
wrap my call to next() in the service in NgZone.run(() => { this.response.next(resp); }
call ApplicationRef.tick() after this.title = resp['title'] in the component.
Both solutions feel like dirty hacks for such a trivial use case. There must be a better way to achieve this.
My question therefore is: what is the proper way to fetch data once and send it off to several components?
I'd furthermore like to understand why my implementation escapes Angular's change detection system.
EDIT it turns out I was initiating my call to HttpClient outside of Angular's zone hence it could not detect my changes, see my answer for more details.
One way is to get an Observable of the Subject and use it in your template using async pipe:
(building | async)?.buildingName
Also, if different components are subscribing to the service at different times, you may have to use BehaviorSubject instead of a Subject.
#Injectable()
export class BuildingDataService {
private responseSource = new Subject<object>();
public response = this.responseSource.asObservable()
constructor (private http: HttpClient) { }
fetchBuildingData(location) {
this.http.post(url, location, httpOptions).subscribe(resp => {
this.responseSource.next(resp);
});
}
}
#Component({
template: "<h1>{{(building | async)?.buildingName}}</h1>"
...
})
export class SidepanelComponent implements OnInit {
building: Observable<any>;
constructor(private buildingDataService: DataService) {
this.building = this.buildingDataService.response;
}
ngOnInit() {
}
updateBuildingInfo(location) {
this.buildingDataService.fetchBuildingData(location);
}
}
The standard way to do this in an angular app is a getter in the service class.
get data()
{
return data;
}
Why are you trying to complicate matters? What are the benefits, and if there are benefits they will overcome the drawbacks?
I think I found the issue. I briefly mention that fetchBuildingData is triggered by users clicking on a map; that map is Leaflet as provided by the ngx-leaflet module. I bind to its click event as follows
map.on('click', (event: LeafletMouseEvent) => {
this.buildingDataService.fetchBuildingData(event.latlng);
});
The callback is, I now realise, fired outside Angular's zone. Angular therefore fails to detect the change to this.building. The solution is to bring the callback in Angular's zone through NgZone as
map.on('click', (event: LeafletMouseEvent) => {
ngZone.run(() => {
this.buildingDataService.fetchBuildingData(event.latlng);
});
});
This solves the problem and every proposed solution works like a charm.
A big thank you for the quick and useful responses. They were instrumental in helping me narrow down the problem!
To be fair: this issue is mentioned in ngx-leaflet's documentation [1]. I failed to understand the implications right away as I'm still learning Angular and there is a lot to take in.
[1] https://github.com/Asymmetrik/ngx-leaflet#a-note-about-change-detection
EDIT:
As your change detection seems to struggle with whatsoever detail in your object, I have another suggestion. And as I also prefer the BehaviorSubject over a sipmple Subject I adjusted even this part of code.
1st define a wrapper object. The interesting part is, that we add a second value which definitely has to be unique compared to any other wrapper of the same kind you produce.
I usually put those classes in a models.ts which I then import wherever I use this model.
export class Wrapper {
constructor(
public object: Object,
public uniqueToken: number
){}
}
2nd in your service use this wrapper as follows.
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Wrapper } from './models.ts';
#Injectable()
export class BuildingDataService {
private response: BehaviorSubject<Wrapper> = new BehaviorSubject<Wrapper>(new Wrapper(null, 0));
constructor (private http: HttpClient) { }
public getResponse(): Observable<Wrapper> {
return this.response.asObservable();
}
fetchBuildingData(location) {
...
this.http.post(url, location, httpOptions).subscribe(resp => {
// generate and fill the wrapper
token: number = (new Date()).getTime();
wrapper: Wrapper = new Wrapper(resp, token);
// provide the wrapper
this.response.next(wrapper);
});
}
Import the model.ts in all subscribing classes in order to be able to handle it properly and easily. Let your Subscribers subscribe to the getResponse()-method.
This must work. Using this wrapper + unique token technique I always solved such change detection problems eventually.
The code of sabith is wrong (but the idea is the correct)
//declare a source as Subject
private responseSource = new Subject<any>(); //<--change object by any
public response = this.responseSource.asObservable() //<--use"this"
constructor (private http: HttpClient) { }
fetchBuildingData(location) {
this.http.post(url, location, httpOptions).subscribe(resp => {
this.responseSource.next(resp);
});
}
Must be work
Another idea is simply using a getter in your component
//In your service you make a variable "data"
data:any
fetchBuildingData(location) {
this.http.post(url, location, httpOptions).subscribe(resp => {
this.data=resp
});
}
//in yours components
get data()
{
return BuildingDataService.data
}
In my angular component I have ngAfterViewInit method which contains some statements that I want to execute after the view is initialized cause it contains some DOM manupulations.
My question is when some parameter is changed I want to run the code inside ngAfterViewInit.
You could define an EventEmitter in the constructor, subscribe to it in the ngAfterViewInit() function to update those values, emit it in the ngAfterViewInit() function, and then emit it again every single time you want to call it in subsequent areas of the component. Here is an example:
import { EventEmitter } from '#angular/core';
export class MyComponent implements AfterViewInit {
public myEvent: EventEmitter<void>;
public constructor() {
this.myEvent = new EventEmitter<void>();
}
public ngAfterViewInit(): void {
// This is how you call the function to do what you want to do with your DOM manipulations below. You can also call this exact function even from the HTML, if you wish to do so (see HTML example).
this.myEvent.emit();
this.myEvent.subscribe(
() => {
// Do whatever actions that you need to do here to perform your DOM manipulations.
},
(err: Error) => console.error(err);
);
}
public emitMyEvent(): void {
this.myEvent.emit();
}
}
<my-component (click)="myEvent.emit()"></my-component>
<!-- or -->
<my-component (click)="emitMyEvent()"></my-component>
If you want to execute those statements on every changes than it is better to write those in ngAfterViewChecked().From the docs:
A lifecycle hook that is called after the default change detector has completed checking a component's view for changes
A callback method that is invoked immediately after the default change detector has completed one change-check cycle for a component's view.
So it will be called on every subsequent changes.
More information can also be found on the Lifecycle Hooks#AfterView docs
If your parameter is available as an Observable, you can just subscribe to it in the ngAfterViewInitmethod. If your parameter is not yet available as Observable, I suggest you take a look at the BehaviourSubject class. With this, you can control when the Observable will emit a new value + It will be triggered with the last value when you subscribe to it
You declare ordinary class methords in the class body, and later define them in the context of ngAfterviewInit.
here's a simple use case example:
export class ViewtestComponent implements OnInit, AfterViewInit{
#ViewChild('someElementMewantedToDoAction') elRef: ElementRef;
constructor() { }
ngOnInit(): void {
}
changeVal;
ngAfterViewInit() {
this.changeVal= (useME) => {
// some action
this.elRef.nativeElement.innerText++;
}
}
Later, use the method in template as
// Do Action= Button value will increment 11, 12 13 ... on each button click.
<button class="button success" (click)="changeVal($emit)>10</button>
I have solved my problem by implementing OnChanges and call ngAfterviewInit for changes other than the firstchange.This way I will make sure that the view is initiallized. By the way the variable subjected to change(changed_var) holds data used in DOM manipulation.
ngOnChanges(changes: SimpleChanges){
if(!changes.changed_var.isFirstChange()){
this.ngAfterViewInit();
}
}