How to retrieve subcollection documents - Angular Firebase - javascript

I'm trying to get the data in my schedule subcollection so i can display it in my html template.
What I got so far is in my getScheduleList method in schedule.service.ts. it's seem I'm only getting metadata no matter what i try to do.
I've tried using: https://github.com/angular/angularfire for documentation because firebases own is kinda lacking.
schedule.service.ts
import {Injectable} from '#angular/core';
import {AngularFirestore} from '#angular/fire/firestore';
import {Schedule} from '../../model/schedule';
import {map} from 'rxjs/operators';
import {AuthService} from '../../auth/auth.service';
#Injectable({
providedIn: 'root'
})
export class ScheduleService {
constructor(public fireService: AngularFirestore, private authService: AuthService) {
}
getScheduleList() {
this.fireService.collection<any>('companies').snapshotChanges().pipe(map(actions => {
return actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
if (data.UID === this.authService.currentUserId) {
console.log(id);
this.fireService.collection(`companies/${id}/schedules`).snapshotChanges().subscribe(result => {
console.log('test', result);
});
}
});
})).subscribe();
}
}
schedule.component.ts
import {Component, OnInit} from '#angular/core';
import {NgbModal, NgbDateStruct, NgbCalendar, NgbModalConfig} from '#ng-bootstrap/ng-bootstrap';
import {NgForm} from '#angular/forms';
import {Schedule, ScheduleInterface} from '../../model/schedule';
import {ScheduleService} from '../../service/schedule/schedule.service';
import {ActivatedRoute} from '#angular/router';
#Component({
selector: 'app-schedule',
templateUrl: './schedule.component.html',
styleUrls: ['./schedule.component.css'],
})
export class ScheduleComponent implements OnInit {
dateModel: NgbDateStruct; // Holds the date structure day/month/year format
date: { year: number, month: number }; // Is for the datepicker month and year selector
schedule: ScheduleInterface; // schedule object uses interface from schedule models
scheduleList: ScheduleInterface[];
constructor(private config: NgbModalConfig,
private modalService: NgbModal,
private calendar: NgbCalendar,
private serviceSchedule: ScheduleService) {
// Customize default values of modals used by this component tree
config.backdrop = 'static';
config.keyboard = false;
}
// Initialises the schedule object with default values
ngOnInit(): void {
// this.schedule = new Schedule('', 'default', '10', '00:00');
this.schedule = new Schedule('', 'default', '2020', '00:00');
this.getScheduleList();
}
// Opens modal window
open(content) {
this.modalService.open(content);
}
// Gives to current day in the datepicker
selectToday() {
this.dateModel = this.calendar.getToday();
}
// Creates new task for the schedule
createSchedule(schedulecontent: NgForm) {
console.log(schedulecontent.value);
if (schedulecontent.valid) {
this.serviceSchedule.createSchedule(schedulecontent.value);
}
}
getScheduleList() {
const test = this.serviceSchedule.getScheduleList();
// console.log(test);
}
}
firebase collection

This is almost certainly not doing what you expect:
this.fireService.collection('companies/{id}/schedules')
If you want to use the value of id inside the string here, you should express it like this:
this.fireService.collection(`companies/${id}/schedules`)
Note the backtick quotes and dollar sign. This is JavaScript's way of doing string interpolation with template literals.

Related

Angular/Typescript - Getting Warning for Circular dependency

I am facing circular dependency warning in my current project. I need some help to fix this warning issue. I have searched StackOverflow or tech blogs to fix this issue. Unfortunately, I am ending with no proper solution. It will greater if someone helps me with this.
Below is the project folder structure.
src
app
services
slice
slice.service.ts
slices
home
help
help.component.html
help.component.ts
home.module.ts
index.ts
WARNING in Circular dependency detected:
src\app\slices\home\help\help.component.ts -> src\app\services\slice\slice.service.ts ->
src\app\slices\index.ts -> src\app\slices\home\help\help.component.ts
help.component.ts
import { ChangeDetectionStrategy, Component, OnInit } from '#angular/core'
import { select, Store } from '#ngrx/store'
import { Observable } from 'rxjs'
// components
import { BaseSliceComponent } from '#app/components/slice/base-slice.class'
// services
import { SliceService } from '#app/services/slice/slice.service'
// models
import { SliceOptions } from '#app/models/slice/slice.model'
// selectors
import config from './store/victims.selector'
#Component({
selector: 'app-help',
templateUrl: './help.component.html',
styleUrls: ['./help.component.scss'],
})
export class HelpComponent extends BaseSliceComponent implements OnInit {
config: Observable<SliceOptions> = this.store.pipe(select(config))
constructor(private store: Store<any>, private sliceService: SliceService) {
super()
}
ngOnInit(): void {}
}
slice.service.ts
import {
ComponentRef,
Injectable,
ViewContainerRef
} from '#angular/core'
import { Router } from '#angular/router'
import { Store } from '#ngrx/store'
import SliceMap from '#app/slices'
import { SliceNameKeys } from '#app/models/slice/slice.model'
#Injectable({
providedIn: 'root'
})
export class SliceService {
private sliceStack: ComponentRef<any>[] = []
private sliceHost!: ViewContainerRef
constructor(
private store: Store<any>,
private router: Router,
) { }
create(
name: SliceNameKeys,
id?: string | undefined,
shouldClear?: boolean,
index?: number
) {
id = id ?? name // if no id specified keep name as id
const slice = SliceMap[name]
}
}
slices/index.ts
import { SliceNames } from '#app/models/slice/slice.model'
// components
import { AboutUsComponent } from './home/aboutus/aboutus.component'
import { HelpComponent } from './home/help/help.component'
const SliceMap: SliceNames = {
help: HelpComponent,
aboutUs: AboutUsComponent
}
export default SliceMap
base-slice.class.ts
export abstract class BaseSliceComponent {
id = ''
}
There is no right solution or tool that can find circulation dependency automatically in your project.
You just need to carefully check each service and injectable that is not circularly dependent.
Like
A->B and B->A
You need to check-in each service dependency as well.

How to import a page property to a data service with Angular?

I want to import the trackingId from tracking.page.ts to geolocation.service.ts in my Ionic App. Anyone an idea how to do that with Angular and TypeScript?
Below you can see an extract of the code.
tracking.page.ts
import { Component, OnInit, ViewChild, ElementRef } from '#angular/core';
import { Geolocation } from '#ionic-native/geolocation/ngx';
import { GeolocationService } from '../../app/geolocation.service';
import { UserService } from '../../app/user.service';
import { Insomnia } from '#ionic-native/insomnia/ngx';
import { LoadingController } from '#ionic/angular';
import { AlertController } from '#ionic/angular';
import { AppComponent } from '../app.component';
import { Storage } from '#ionic/storage';
declare var google;
#Component({
selector: 'app-tracking',
templateUrl: './tracking.page.html',
styleUrls: ['./tracking.page.scss'],
})
export class TrackingPage implements OnInit {
#ViewChild('map', { static: true }) mapElement: ElementRef;
map: any;
markers = [];
geoLocations: any;
watchLocationUpdates: any;
isWatching: boolean;
interval: any;
geoLatitude: number;
geoLongitude: number;
geoAccuracy: number;
timeStamp: any;
uId: string;
trackingId: string; // This is the trackingId
...
geolocation.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { environment } from '../environments/environment';
#Injectable({
providedIn: 'root'
})
export class GeolocationService {
databaseUrl = environment.firebase.databaseURL;
constructor(private http: HttpClient
) {
console.log('Hello GeolocationService Provider');
console.log('GeolocationService: ', this.databaseUrl);
}
...
You can make a service to save the trackingid inside it and then inject it inside the second page.
Trying to do this outside of services will run into problems with Zoning and change detection.
Define trackingid in service file with observable subject like
public trackingid: BehaviorSubject<number> = new BehaviorSubject(1);
And then set it inside service and fetch it also like below
public getTrackingId(): boolean {
return this.trackingid.value;
}
public setTrackingId(){
return this.trackingid.next(2);
}
Now from component you can do get and set to trackingId.

Angular: Access this.id declared in an "OnInit" function

Update 1
After I read Alexanders suggestions, I updated the code and got no error back. But Angular doesn't do a request to the server anymore, which make me curious. And also the pageTitle does not update.
appointmentDetail.component.html
{{appointmentDetail.time}}
appointmentDetail.component.ts
import { Component, OnInit, OnDestroy, Injectable } from '#angular/core';
import { ActivatedRoute, ParamMap } from '#angular/router';
import { Title } from '#angular/platform-browser';
import { APIService } from './../../../api.service';
import { Observable } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
#Component({
selector: 'app-appointmentdetail',
templateUrl: './appointmentDetail.component.html',
styleUrls: ['./appointmentDetail.component.scss']
})
export class AppointmentDetailComponent implements OnInit {
id: any;
appointmentDetail$: Observable<Object>; // I'd really create an interface for appointment or whatever instead of object or any
pageTitle = 'Some Default Title Maybe';
constructor(
private route: ActivatedRoute,
private title: Title,
private apiService: APIService
) {}
ngOnInit() {
this.appointmentDetail$ = this.route.paramMap.pipe(
tap((params: ParamMap) => {
this.id = params.get('id');
// Or this.id = +params.get('id'); to coerce to number maybe
this.pageTitle = 'Termin' + this.id;
this.title.setTitle(this.pageTitle);
}),
switchMap(() => this.apiService.getAppointmentDetailsById(this.id))
);
}
public getData() {
this.apiService
.getAppointmentDetailsById(this.id)
.subscribe((data: Observable<Object>) => {
this.appointmentDetail$ = data;
console.log(data);
});
}
}
api.service.ts
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class APIService {
API_URL = 'http://localhost:5000';
constructor(private httpClient: HttpClient) {}
getAppointments() {
return this.httpClient.get(`${this.API_URL}/appointments/`);
}
getAppointmentDetailsById(id) {
return this.httpClient.get(`${this.API_URL}/appointments/${id}`);
}
getAppointmentsByUser(email) {
return this.httpClient.get(`${this.API_URL}/user/${email}/appointments`);
}
getCertificatesByUser(email) {
return this.httpClient.get(`${this.API_URL}/user/${email}/certificates`);
}
}
As you can see, I want to grab that parameter id from the router parameters and want to pass it into my API call, which will do a Angular HTTP request. Hope I'm right, haha.
Original Question
Currently, I ran into a nasty problem. The thing is, I want to read the params, which are given to me by ActivatedRouter and the Angular OnInit function. I subscribe them params and log them in the console. Until here, everything is working fine. But I want to access "this.id" outside from my OnInit function, so I can use it on pageTitle for example.
But, this.id is undefined. So the page title is Termineundefined.
Source code:
import { Component, OnInit, OnDestroy, Injectable } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Title } from '#angular/platform-browser';
import { APIService } from './../../api.service';
#Component({
selector: 'app-appointment-details',
templateUrl: './appointment-details.component.html',
styleUrls: ['./appointment-details.component.scss']
})
#Injectable()
export class AppointmentDetailsComponent implements OnInit, OnDestroy {
private routeSub: any;
id: any;
private appointmentDetail: Array<object> = [];
constructor(
private route: ActivatedRoute,
private title: Title,
private apiService: APIService
) {}
pageTitle = 'Termin' + this.id;
ngOnInit() {
this.title.setTitle(this.pageTitle);
this.getData();
this.routeSub = this.route.params.subscribe(params => {
console.log(params);
this.id = params['id'];
});
}
ngOnDestroy() {
this.routeSub.unsubscribe();
}
public getData() {
this.apiService
.getAppointmentDetailsById(this.id)
.subscribe((data: Array<object>) => {
this.appointmentDetail = data;
console.log(data);
});
}
}
The issue here really comes down to async availability of route params and observable streams. You simply cannot use the value until it has resolved for all practical purposes. You can use RxJS operators such as switchMap and tap in line with the official Routing & Navigation documentation to ensure route param id is available prior to use. tap can be used to introduce side effects such as setting class id property from route params and/or setting title. You could even create a class property of an Observable<YourObject[]> and utilize Angular Async Pipe to avoid subscribing and unsubscribing to display the data.
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Title } from '#angular/platform-browser';
import { APIService, MyFancyInterface } from './../../api.service';
import { Observable } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
#Component({
selector: 'app-appointment-details',
templateUrl: './appointment-details.component.html',
styleUrls: ['./appointment-details.component.scss']
})
export class AppointmentDetailsComponent implements OnInit {
id: any;
appointmentDetail$: Observable<MyFancyInterface>;
appointmentDetail: MyFancyInterface;
pageTitle = 'Some Default Title Maybe';
constructor(
private route: ActivatedRoute,
private title: Title,
private apiService: APIService
) {}
ngOnInit() {
this.appointmentDetail$ = this.route.paramMap.pipe(
tap((params: ParamMap) => {
this.id = params.get('id')
// Or this.id = +params.get('id'); to coerce to type number maybe
this.pageTitle = 'Termin' + this.id;
this.title.setTitle(this.pageTitle);
}),
switchMap(() => this.apiService.getAppointmentDetailsById(this.id))
);
/* Or
this.route.paramMap.pipe(
tap((params: ParamMap) => {
this.id = params.get('id')
// Or this.id = +params.get('id'); to coerce to type number maybe
this.pageTitle = 'Termin' + this.id;
this.title.setTitle(this.pageTitle);
}),
switchMap(() => this.apiService.getAppointmentDetailsById(this.id))
).subscribe((data: MyFancyInterface) => {
this.appointmentDetail = data;
});
*/
}
}
Template:
<div>{{(appointmentDetail | async)?.id}}</div>
I'd recommend to create an interface to represent your data model and type the return of your api service method:
import { Observable } from 'rxjs';
// maybe put this into another file
export interface MyFancyInterface {
id: number;
someProperty: string;
...
}
export class APIService {
...
getAppointmentDetailsById(id): Observable<MyFancyInterface> {
return this.httpClient.get<MyFancyInterface>(`${this.API_URL}/appointments/${id}`);
}
...
}
If you really must, you can save the observable as you do now for the route params and subscribe as needed in the various parts of the class, but this demonstrated way you almost absolutely know that route param id will be available for use and can explicitly set the things you need to set.
I'd also remove #Injectable() as there is no reason to have it here with a #Component() decorator.
Note* the async pipe operator in this example ensures the Http call is executed. Otherwise a subscribe() is needed (search SO for Angular http not executing to see similar issues)
Hopefully that helps!
Instead of
id: any;
You could try using a getter, like so
public get id(): any {
this.route.params.subscribe(params => {
return params['id'];
}
}
In your template, just
{{ id }}

Display Dynamic Data in Angular6 with GoogleChart

I use Angular Cli6, angularfire2 and firebase. I want to create a timeline with GoogleChart.
//GoogleChart.service
declare var google: any;
export class GoogleChartsBaseService
{
constructor() { google.charts.load('current', {'packages':["timeline"]}); }
protected buildChart(data: any[], chartFunc: any, options: any) : void {
var func = (chartFunc, options) =>
{
var datatable = google.visualization.arrayToDataTable(data);
chartFunc().draw(datatable, options);
};
var callback = () => func(chartFunc, options);
google.charts.setOnLoadCallback(callback);
}
}
TimelineChart.service
import { GoogleChartsBaseService } from './google-charts-base.service';
import { Injectable } from '#angular/core';
import { GanttChartConfig } from './../models/GanttChartConfig.model';
declare var google: any;
#Injectable()
export class GoogleGanttChartService extends GoogleChartsBaseService {
constructor() { super(); }
public BuildPieChart(elementId: string, data: any[], config: GanttChartConfig) : void {
var chartFunc = () => { return new google.visualization.Timeline(document.getElementById(elementId)); };
var options = {
traitement: config.traitement,
datedebut: config.datedebut,
datefin: config.datefin,
};
this.buildChart(data, chartFunc, options);
}
}
Timeline.html
<div id="{{elementId}}" ></div>
Timeline.ts
import { Component, Input, OnInit } from '#angular/core';
import { GoogleGanttChartService } from './../../services/google-gantt-chart.service';
import { GanttChartConfig } from './../../models/GanttChartConfig.model';
declare var google: any;
#Component({
selector: 'app-gantt',
templateUrl: './gantt.component.html',
styleUrls: ['./gantt.component.scss']
})
export class GanttComponent implements OnInit {
#Input() data: any[];
#Input() config: GanttChartConfig;
#Input() elementId: string;
constructor(private _ganttChartService: GoogleGanttChartService) {}
ngOnInit(): void {
this._ganttChartService.BuildPieChart(this.elementId, this.data, this.config);
}
}
And the component to display the graph :
Component.html
<div class="full"><app-gantt [data]="data1" [config]="config1" [elementId]="elementId1"></app-gantt></div>
Component.ts
import { Component, OnInit, Inject } from '#angular/core';
import { Patient } from '../models/patient.model';
import { Diagnostic } from '../models/diagnostic.model';
import { ActivatedRoute, Router } from '#angular/router';
import { PatientsService } from '../services/patients.service';
import { DiagnosticsService } from '../services/diagnostics.service';
import { PPSsService } from '../services/ppss.service';
import { AngularFireDatabase, AngularFireList, AngularFireObject, AngularFireAction } from 'angularfire2/database';
import { Location } from '#angular/common';
import { Observable } from 'rxjs/Observable';
import { GanttChartConfig } from './../models/GanttChartConfig.model';
import { PPS } from '../models/pps.model';
import {MAT_MOMENT_DATE_FORMATS, MomentDateAdapter} from '#angular/material-moment-adapter';
import {DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE} from '#angular/material/core';
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA,MatDatepickerModule, MatFormFieldModule,} from '#angular/material';
import { FormControl, FormControlName, FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormsModule } from '#angular/forms';
import { startWith } from 'rxjs/operators/startWith';
import { map, filter, catchError, mergeMap } from 'rxjs/operators';
#Component({
selector: 'app-pps',
templateUrl: './pps.component.html',
styleUrls: ['./pps.component.scss']
})
export class PpsComponent implements OnInit {
patientid: string;
patientToDisplay;
ppssToDisplay;
data1: any[];
config1: GanttChartConfig;
elementId1: string;
constructor(
private route: ActivatedRoute,
private location: Location,
private patientsService: PatientsService,
private diagnosticsService: DiagnosticsService,
private ppssService: PPSsService,
private router: Router,
public dialog: MatDialog,
){ }
ngOnInit() {
this.route.params.forEach((urlParameters) => {
this.patientid = urlParameters['id'];});
this.patientToDisplay =
this.patientsService.getSinglePatient(this.patientid);
this.ppssToDisplay = this.ppssService.getPPSByPatientid(this.patientid);
this.data1 = [[ 'traitement','start', 'end'],
[ 'Chirurgie', new Date(2017, 3, 29), new Date(2017, 3, 30)],
[ 'Chimiothérapie', new Date(2017, 2, 4), new Date(2018, 2, 4)],
[ 'Radiothérapie', new Date(2017, 2, 4), new Date(2018, 2, 4)]];
this.config1 = new GanttChartConfig( '',new Date (),new Date ());
this.elementId1 = 'myGanttChart';
Now i can display my graph easily with data write in my component
but my data are stocked in firebase like that :
I display the data with an observable from angularfire2
DATA.Service.TS
getPPSByPatientid(Patientid: string){
return this.database.list('/ppss', ref => ref.orderByChild("Patientid").equalTo(Patientid)).valueChanges();
}
I try this in my component .ts in order to have the good array for this.data1 but witout succes
Console.log(this.data1) send an array of undefined
let interestingFields = [ 'treatement','dateA', 'dateB'];
this.ppssToDisplay.subscribe(obj => {
this.data1 = [
interestingFields,
interestingFields.map(field => obj[field]),
];
console.log(this.data1);
});
Error:
core.js:1598 ERROR Error: Uncaught (in promise): Error: Not an array Error: Not an array
I wanted to put all of my code so you could have complete visualization of what I want to do.
My question is: Have I chosen the right solution or should I use a loop in my template to populate the chart?
And could someone show me the voice so I can sleep again (5 days lol)
It seems you have gotten the right solution. There is just one problem when getting your data to data1.
For the retrieved data to match the pattern on your data written in hard, you have to loop through it from the perspective of the retrieved data:
this.ppssToDisplay.subscribe(ppsList => {
this.data1 = [
interestingFields,
...ppsList.map(pps => interestingFields.map(field => pps[field]))
];
});
And there you go. That should solve your problem.

Angular 4 pass data between 2 not related components

I have a questions about passing data in Angular.
First, I don't have a structure as <parent><child [data]=parent.data></child></parent>
My structure is
<container>
<navbar>
<summary></summary>
<child-summary><child-summary>
</navbar>
<content></content>
</container>
So, in <summary /> I have a select that do send value to <child-summary /> and <content />.
OnSelect method is well fired with (change) inside <summary /> component.
So, I tried with #Input, #Output and #EventEmitter directives, but I don't see how retrieve the event as #Input of the component, unless to go on parent/child pattern. All examples I've founded has a relation between component.
EDIT : Example with BehaviorSubject not working (all connected service to API works well, only observable is fired at start but not when select has value changed)
shared service = company.service.ts (used to retrieve company data)
import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';
#Injectable()
export class SrvCompany {
private accountsNumber = new BehaviorSubject<string[]>([]);
currentAccountsNumber = this.accountsNumber.asObservable();
changeMessage(accountsNumber: string[]) {
this.accountsNumber.next(accountsNumber);
}
private _companyUrl = 'api/tiers/';
constructor(private http: Http) { }
getSociete(): Promise<Response> {
let url = this._companyUrl;
return this.http.get(url).toPromise();
}
}
invoice.component.ts (the "child")
import { Component, OnInit, Input } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import { SrvInvoice } from './invoice.service';
import { SrvCompany } from '../company/company.service';
#Component({
selector: 'invoice',
templateUrl: 'tsScripts/invoice/invoice.html',
providers: [SrvInvoice, SrvCompany]
})
export class InvoiceComponent implements OnInit {
invoice: any;
constructor(private srvInvoice: SrvInvoice, private srvCompany: SrvCompany)
{
}
ngOnInit(): void {
//this.getInvoice("F001");
// Invoice data is linked to accounts number from company.
this.srvCompany.currentAccountsNumber.subscribe(accountsNumber => {
console.log(accountsNumber);
if (accountsNumber.length > 0) {
this.srvInvoice.getInvoice(accountsNumber).then(data => this.invoice = data.json());
}
});
}
//getInvoice(id: any) {
// this.srvInvoice.getInvoice(id).then(data => this.invoice = data.json());
//}
}
company.component.ts (the trigerring "parent")
import { Component, Inject, OnInit, Input } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import { SrvCompany } from './company.service';
#Component({
selector: 'company',
templateUrl: 'tsScripts/company/company.html',
providers: [SrvCompany]
})
export class CompanyComponent implements OnInit {
societes: any[];
soc: Response[]; // debug purpose
selectedSociete: any;
ville: any;
ref: any;
cp: any;
accountNumber: any[];
constructor(private srvSociete: SrvCompany)
{
}
ngOnInit(): void {
this.getSocietes();
}
getSocietes(): void {
this.srvSociete.getSociete()
.then(data => this.societes = data.json())
.then(data => this.selectItem(this.societes[0].Id));
}
selectItem(value: any) {
this.selectedSociete = this.societes.filter((item: any) => item.Id === value)[0];
this.cp = this.selectedSociete.CodePostal;
this.ville = this.selectedSociete.Ville;
this.ref = this.selectedSociete.Id;
this.accountNumber = this.selectedSociete.Accounts;
console.log(this.accountNumber);
this.srvSociete.changeMessage(this.accountNumber);
}
}
This is a case where you want to use a shared service, as your components are structured as siblings and grandchildren. Here's an example from a video I created a video about sharing data between components that solves this exact problem.
Start by creating a BehaviorSubject in the service
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class DataService {
private messageSource = new BehaviorSubject("default message");
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
Then inject this service into each component and subscribe to the observable.
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-parent',
template: `
{{message}}
`,
styleUrls: ['./sibling.component.css']
})
export class ParentComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
}
You can change the value from either component and the value will be updated, even if you don't have the parent/child relationship.
import { Component, OnInit } from '#angular/core';
import { DataService } from "../data.service";
#Component({
selector: 'app-sibling',
template: `
{{message}}
<button (click)="newMessage()">New Message</button>
`,
styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
newMessage() {
this.data.changeMessage("Hello from Sibling")
}
}
if component are not related than you need use Service
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
There are two solutions for this.
This can be done through shared service by using observable's.
You can use ngrx/store for this. This is similar to Redux arch. You will be getting data from state.
Here is the simplest example of sharing data between two independent components, using event emitter and service
https://stackoverflow.com/a/44858648/8300620
When you mention non related components, I'm gonna assume that they don't have any parent component. If assumption isn't correct, feel free to read another of my answers where both cases are addressed.
So, as there's no common parent, we can use an injectable service. In this case, simply inject the service in the components and subscribe to its events.
(Just like the next image shows - taken from here - except that we'll inject the service in two Components)
The documentation explains it quite well how to Create and register an injectable service.

Categories