My situation:
I have a component showing tiles with each tile representing an object from an array that is looped over with an ngfor. When a tile is clicked I want to pass the object to a different component, which is responsable for displaying all properties of that object in fields that can be modified.
What I have tried:
After doing some research and coming across multiple posts showing me how to achieve this for a parent - child hierarchy and some posts explaining that it is necessary to use a shared service in order to achieve the wanted funcitonality, I decided to try and setup such a service.
However, what I don't seem to get is when I should navigate to the different route. It seems that the navigation finds place to early as the object passed to the service is undefined when retrieving it in the detail component.
My code:
The component showing the tiles has the following function to pass the clicked object to the shared service:
editPropertyDetails(property: Property) {
console.log('Edit property called');
return new Promise(resolve => {
this.sharedPropertyService.setPropertyToDisplay(property);
resolve();
}).then(
() => this.router.navigate(['/properties/detail'])
)
}
The shared service has a function to set a property object and one to retrieve it and looks like this:
@Injectable()
export class SharedPropertyService {
// Observable
public propertyToDisplay = new Subject<Property>();
constructor( private router: Router) {}
setPropertyToDisplay(property: Property) {
console.log('setPropertyToDisplay called');
this.propertyToDisplay.next(property);
}
getPropertyToDisplay(): Observable<Property> {
console.log('getPropertyToDisplay called');
return this.propertyToDisplay.asObservable();
}
}
Finally the detail component which has to receive the object that was clicked on but gets an undefined object:
export class PropertyDetailComponent implements OnDestroy {
property: Property;
subscription: Subscription;
constructor(private sharedPropertyService: SharedPropertyService) {
this.subscription = this.sharedPropertyService.getPropertyToDisplay()
.subscribe(
property => { this.property = property; console.log('Detail Component: ' + property.description);}
);
}
ngOnDestroy() {
// When view destroyed, clear the subscription to prevent memory leaks
this.subscription.unsubscribe();
}
}
Thanks in advance!
I solved the problem by passing the id of the object of the tile that was clicked as in the navigation extras of the route and then use a service in the detail component to fetch the object based on the id passed through the route.
I will provide the code below so hopefully nobody has to go through all of this ever again.
The component showing the tiles that can be clicked in order to see the details of the object they contain:
editPropertyDetails(property: Property) {
console.log('Edit property called');
let navigationExtras: NavigationExtras = {
queryParams: {
"property_id": property.id
}
};
this.router.navigate(['/properties/detail'], navigationExtras);
}
the detail component that receives the object that was clicked on
private sub: any;
propertyToDisplay: Property;
constructor
(
private sharedPropertyService: SharedPropertyService,
private router: Router,
private route: ActivatedRoute
) {}
ngOnInit() {
this.sub = this.route.queryParams.subscribe(params => {
let id = params["property_id"];
if(id) {
this.getPropertyToDisplay(id);
}
});
}
getPropertyToDisplay(id: number) {
this.sharedPropertyService.getPropertyToDisplay(id).subscribe(
property => {
this.propertyToDisplay = property;
},
error => console.log('Something went wrong'));
}
// Prevent memory leaks
ngOnDestroy() {
this.sub.unsubscribe();
}
The service
properties: Property[];
constructor( private propertyService: PropertyService) {}
public getPropertyToDisplay(id: number): Observable<Property> {
if (this.properties) {
return this.findPropertyObservable(id);
} else {
return Observable.create((observer: Observer<Property>) => {
this.getProperties().subscribe((properties: Property[]) => {
this.properties = properties;
const prop = this.filterProperties(id);
observer.next(prop);
observer.complete();
})
}).catch(this.handleError);
}
}
private findPropertyObservable(id: number): Observable<Property> {
return this.createObservable(this.filterProperties(id));
}
private filterProperties(id: number): Property {
const props = this.properties.filter((prop) => prop.id == id);
return (props.length) ? props[0] : null;
}
private createObservable(data: any): Observable<any> {
return Observable.create((observer: Observer<any>) => {
observer.next(data);
observer.complete();
});
}
private handleError(error: any) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
private getProperties(): Observable<Property[]> {
if (!this.properties) {
return this.propertyService.getProperties().map((res: Property[]) => {
this.properties = res;
console.log('The properties: ' + JSON.stringify(this.properties));
return this.properties;
})
.catch(this.handleError);
} else {
return this.createObservable(this.properties);
}
}
Please Try with below sample:
Step 1: Create Service [DataService]
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class DataService {
private userIdSource = new BehaviorSubject<number>(0);
currentUser = this.userIdSource.asObservable();
private orderNumSource = new BehaviorSubject<number>(0);
currentOrder = this.orderNumSource.asObservable();
constructor() { }
setUser(userid: number) {
this.userIdSource.next(userid)
}
setOrderNumber(userid: number) {
this.orderNumSource.next(userid)
}
}
Step 2: Set value in login component
import { Component } from '@angular/core';
import { DataService } from "../services/data.service";
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
constructor( private dataService:DataService) { }
onSubmit() {
this.dataService.setUser(1);
}
}
Step 3 : Get value in another component
import { Component, OnInit } from '@angular/core';
import { DataService } from "../services/data.service";
@Component({
selector: 'app-shopping-cart',
templateUrl: './shopping-cart.component.html',
styleUrls: ['./shopping-cart.component.css']
})
export class ShoppingCartComponent implements OnInit {
userId: number = 0;
constructor(private dataService: DataService) { }
ngOnInit() {
this.getUser();
}
getUser() {
this.dataService.currentUser.subscribe(user => {
this.userId = user
}, err => {
console.log(err);
});
}
}
Note: On page refresh the value will lost.
Using service first you create one funtion on service. call that funtion and use subject to other component. write this code
this.handleReq.handlefilterdata.subscribe(() => {
this.ngDoCheck();
});
here, handleReq is service. handlefilterdata is rxjs subject.
try like this :
try to subscribe this.sharedPropertyService.propertyToDisplay
instead of this.sharedPropertyService.getPropertyToDisplay()
this.sharedPropertyService.propertyToDisplay.subscribe((property) => {
this.property = property;
console.log('Detail Component: ' + property.description);
});
and send the object like below :
editPropertyDetails(property: Property) {
this.sharedPropertyService.setPropertyToDisplay(property);
}
I was working on similar functionality and came across same issue(as undefined). You could initialize like this.
public propertyToDisplay = new BehaviorSubject<Property>(undefined);
After making a change like this. I am able to get the value from Observable
in service file and as well as in the component where I am trying to use this service.
What does your console output? Is this.property
ever set on the child component?
I would try to get rid of this function:
getPropertyToDisplay(): Observable<Property>
And try to just access propertyToDisplay
directly.
Also .navigate can take data as a second param, so you might try passing the data in the route change.
constructor(
private route: ActivatedRoute,
private router: Router) {}
ngOnInit() {
this.property = this.route
.variableYouPassedIntoNavigator
来源:https://stackoverflow.com/questions/47471726/angular-4-pass-an-object-from-one-component-to-another-no-parent-child-hier