Calling Components:
<some-component></some-component>
<some-component></some-component>
Component:
#Component ({
selector : 'some-component',
})
export class SomeComponent implements OnInit {
constructor (private someService : SomeService) {}
ngOnInit(): void {
this.someService.register();
}
}
Service:
#Injectable()
export class SomeService{
private targets: Array;
constructor (private http: Http) {}
register(){
this.targets.push('x');
getData();
}
getData(){
console.log(targets);
let params = this.targets.join(); // 'x,x'
return this.http.get(params);
}
}
console:
['x']
['x','x']
// what I need to do to whit until the last one and make the get request.
Hello as you can see in my code I need to call a component many times with the same injected service and not to repeat the get request just making the Get request once for all the components
First approach
import { Input } from '#angular/core';
#Component ({
selector : 'some-component',
})
export class SomeComponent implements OnInit {
#Input() isLast = false;
constructor (private someService : SomeService) {}
ngOnInit(): void {
this.someService.register();
if (this.isLast) {
this.someService.getData();
}
}
}
Delete the this.getData() inside your service's register()
Then in the template where you drop SomeComponents in
<some-component></some-component>
<some-component></some-component>
<some-component [isLast]='true'></some-component>
Second approach
#Component ({
selector : 'some-component',
})
export class SomeComponent implements OnInit {
constructor (private someService : SomeService) {}
ngOnInit(): void {
this.someService.register();
}
}
Delete the this.getData() inside your service's register()
Then inside the component class of the template where you insert your SomeComponents, let's say this parent component is AppComponent
.
.
.
export class AppComponent ... {
constructor (private someService : SomeService) {}
// import AfterViewInit from angular core package
ngAfterViewInit(): void {
this.someService.getData();
}
}
Related
I want to send the value from one component to another, they are not related so all solutions are saying that I must use shared service to do that. But these services are using templates (if I'm right). Is there a way to do this sharing without services?
I want to send the BMI value from homepage.component.ts to result.component.ts.
homepage.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
constructor() { }
myHeight!:number;
myWeight!:number;
bmi!:number;
ngOnInit(): void {
}
onGenerate( height:string,width:string){
this.myHeight = +height;
this.myHeight=Math.pow(this.myHeight/100,2);
this.myWeight = +width;
this.bmi=this.myWeight/this.myHeight
console.log(this.bmi); //this is the calculated value to send
}
}
result.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.css']
})
export class ResultComponent implements OnInit {
constructor() { }
//I want to get the bmi here
ngOnInit(): void {
}
}
There are Two ways to communicate between unrelated components in angular:
1 - Through services, you have to understand where to inject it, in your case I think it should be injected in root, so try this with your service ( follow this tutorial to implement your service, just add my code instead of theirs )
#Injectable({
providedIn: 'root',
})
2 - Through a store ( a lot of boilerplate coding, to use if you have complexe states to keep synchronized through the whole app, by the way the store is basically a service )
If your components are not related then you can create a shared service between them. Then, you need to use dependency injection to communicate between these components. So, there is a great Angular tutorial which describes how to do it.
The service code would look like this:
#Injectable()
export class FooService {
constructor( ) { }
private yourData;
setData(data){
this.yourData = data;
}
getData(){
let temp = this.yourData;
this.clearData();
return temp;
}
}
and sender component:
import { Router } from '#angular/router';
import { FooService} from './services/foo.service';
export class SenderComponent implements OnInit {
constructor(
private fooService: FooService,
private router:Router) {}
somefunction(data){
this.fooService.setData(data);
this.router.navigateByUrl('/reciever');//as per router
}
}
and subscriber:
import { Router } from '#angular/router';
import { TransfereService } from './services/transfer.service';
export class RecieverComponent implements OnInit {
data;
constructor(
private fooService: FooService){
}
ngOnInit() {
data = this.transfereService.getData();
console.log(`data: `, data)
}
}
Solution: To pass the data from one component to another we can store it in a session storage or a local storage and then access it in other components from that storage. Here I have provided a sample code using local storage for your reference.
homepage.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
constructor() { }
myHeight!:number;
myWeight!:number;
data:string='';
bmi!:number;
ngOnInit(): void {
}
onGenerate( height:string,width:string){
this.myHeight = +height;
this.myHeight=Math.pow(this.myHeight/100,2);
this.myWeight = +width;
this.bmi=this.myWeight/this.myHeight;
this.data=localStorage.setItem('bmi',this.bmi);
console.log(this.bmi); //this is the calculated value to send
}
}
resultcomponent.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.css']
})
export class ResultComponent implements OnInit {
data:any;
constructor() { this.data=localstorage.getItem('bmi')}
//Access the bmi using the data variable here
ngOnInit(): void {
}
}
How to re-fetch data after parameter change from:
oglas/1 to oglas/2 by click, so when put URL and than click ENTER everything works, but when click on oglas/2 button when oglas/1 is rendered URL changes to oglas/2 but data is from oglas/1?
TS
import { Component, OnInit } from "#angular/core";
import { ActivatedRoute } from "#angular/router";
import { Post } from "../post.model";
import { ServerService } from "../server.service";
#Component({
selector: "post",
templateUrl: "./post.component.html",
styleUrls: ["./post.component.css"]
})
export class PostComponent implements OnInit {
post: Post[];
constructor(
private route: ActivatedRoute,
private serverService: ServerService
) {}
ngOnInit(): void {
this.getPost();
}
getPost(): void {
const id = +this.route.snapshot.paramMap.get("id");
this.serverService.getPosts(id).subscribe(post => (this.post = post));
}
}
Service
import { HttpClient } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { Post } from "./post.model";
import { User } from "./user.model";
import { Observable } from "rxjs";
#Injectable({ providedIn: "root" })
export class ServerService {
usersUrl = "http://localhost:3000/users";
postsUrl = "http://localhost:3000/posts";
constructor(private http: HttpClient) {}
getPosts(id: number | string): Observable<Post[]> {
const url = `${this.postsUrl}/${id}`;
return this.http.get<Post[]>(url);
}
getUser(id: number | string): Observable<User[]> {
const url = `${this.usersUrl}/${id}`;
return this.http.get<User[]>(url);
}
}
Since your are making an API call for data in ngOnInit(), requested data may not be available by the time your component loads. And Angular might be reusing the same instance of the component, making ngOnInit() to be called only once.
You can use Angular Resolvers to ensure that you have the required data before loading the component.
1) Create a route resolver to fetch the required data before loading the route.
PostDataResolver.ts:
// ... imports
#Injectable()
export class PostDataResolver implements Resolve<any> {
constructor(private serverService: ServerService) {}
resolve(route: ActivatedRouteSnapshot) {
const id = route.paramMap.get('id');
return this.serverService.getPosts(id);
}
}
2) Add this resolver to your routing module:
{ path: "oglas/:id", component: PostComponent, resolve: { postData: PostDataResolver }}
3) Then access the resolved data in your component.
PostComponent.ts:
export class PostComponent implements OnInit {
post: Post[];
constructor(
private route: ActivatedRoute,
private serverService: ServerService
) {}
ngOnInit(): void {
this.post = this.route.snapshot.data.postData;
}
}
This ensures that you have the latest and appropriate data before the component loads.
Got it...
import { Component, OnInit, OnChanges } from "#angular/core";
import { ActivatedRoute, Router } from "#angular/router";
import { Post } from "../post.model";
import { ServerService } from "../server.service";
#Component({
selector: "post",
templateUrl: "./post.component.html",
styleUrls: ["./post.component.css"]
})
export class PostComponent implements OnInit {
post: Post[];
id: number;
constructor(
private route: ActivatedRoute,
private serverService: ServerService
) {}
ngOnInit(): void {
this.route.paramMap.subscribe(params => {
this.id = parseInt(params.get("id"));
this.getPost(this.id);
});
}
getPost(id: number): void {
this.serverService.getPosts(id).subscribe(post => (this.post = post));
}
}
This code re-fetch data to a component
ngOnInit(): void {
this.route.paramMap.subscribe(params => {
this.id = parseInt(params.get("id"));
this.getPost(this.id);
});
}
Thank you all for your effort!
Here is my code that gives error cannot read property title undefined.
Parent Component
import { Child } from './child.component';
#Component({
selector: 'parent',
})
export class ParentComponet implements OnInit, AfterViewInit {
constructor(){}
#ViewChild(Child) child: Child;
ngAfterViewInit(){
console.log("check data", this.child.title)
}
}
And Child Component is.
#Component({
selector: 'child',
})
export class ChildComponet {
public title = "hi"
constructor(){}
}
routing.module.ts is like
{
path: "",
component: ParentComponent,
children: [
{
path: '/child',
component: ChildComponent
}
]
}
And Gives error is
ERROR TypeError: Cannot read property 'title' of undefined(…)
I think you are missing 'template' or 'templateUrl' in relevance to creating a Component
ParentComponent
import { ChildComponent } from './child.component'; // {ChildComponent} not {Child} as we are referencing it to the exported class of ChildComponent
#Component({
selector: 'parent',
template: `<child></child>`
})
export class ParentComponet implements OnInit, AfterViewInit {...}
ChildComponent
#Component({
selector: 'child',
template: `<h1>{{ title }}</h1>`
})
export class ChildComponent {...} // Be sure to spell it right as yours were ChildComponet - missing 'n'
UPDATE as per the user's clarification on this thread
Had added a Stackblitz Demo for your reference (Check the console)
If you want to access the ChildComponent that is rendered under the Parent Component's <router-outlet> you can do so by utilizing (activate) supported property of router-outlet:
A router outlet will emit an activate event any time a new component is being instantiated
Angular Docs
ParentComponent's Template
#Component({
selector: 'parent',
template: `<router-outlet (activate)="onActivate($event)"></router-outlet>`
})
export class ParentComponent {
onActivate(event): void {
console.log(event); // Sample Output when you visit ChildComponent url
// ChildComponent {title: "hi"}
console.log(event.title); // 'hi'
}
}
The result will differ based on the visited page under your parent's children
If you visit Child1Component you will get its instance Child1Component {title: "hi"}
If you visit Child2Component you will get its instance Child2Component {name: "Angular"}
These results will then be reflected on your ParentComponent's onActivate(event) console for you to access
That's not how it's supposed to work. You'll be only able to get the ChildComponent in your ParentComponent ONLY if you have the <app-child></app-child> tag in your ParentComponent Template.
Something like this:
...
<app-child></app-child>
...
But since you're using child routing, and the ChildComponent will load on the router-outlet of your ParentComponent you won't have access to that using ViewChild
PS: You'll only have access to it inside ngAfterViewInit as ViewChild can only be considered safe to have instantiated after the View has loaded:
import { Component, OnInit, ViewChild } from '#angular/core';
import { ChildComponent } from '../child/child.component';
...
#Component({...})
export class ParentComponent implements OnInit {
#ViewChild(ChildComponent) childComponent: ChildComponent;
...
ngAfterViewInit() {
console.log(this.childComponent);
}
}
Here's a Working Sample StackBlitz for your ref that illustrates your scenario in both the cases.
PS: To get the ChildComponent properties in your ParentComponent, with Routing, you'll have to either use a SharedService or you'll have to pass the ChildProperty in the route as a QueryParam and read it in your ParentComponent using the ActivatedRoute
UPDATE:
Sharing Data using Route Query Params:
Although this won't make much sense, but in your ChildComponent, you can have a Link that would route the user to the ChildComponent with the title property passed as a queryParam. Something like this:
<a
[routerLink]="['/child']"
[queryParams]="{title: title}">
Go To Child Route With Query Params
</a>
And in your ParentComponent have access to it using ActivatedRoute like this:
...
import { ActivatedRoute } from '#angular/router';
...
#Component({...})
export class ParentComponent implements OnInit {
...
constructor(
private route: ActivatedRoute,
...
) { }
ngOnInit() {
this.route.queryParams.subscribe(queryParams => {
console.log('queryParams[`title`]', queryParams['title']);
});
...
}
...
}
Using a SharedService
Just create a SharedService with a private BehaviorSubject that would be exposed as an Observable by calling the asObservable method on it. It's value can be set by exposing a method(setChildProperty) that will essentially call the next method with the updated childProperty value :
import { Injectable } from '#angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
#Injectable()
export class SharedService {
private childProperty: BehaviorSubject<string> = new BehaviorSubject<string>(null);
childProperty$: Observable<string> = this.childProperty.asObservable();
constructor() { }
setChildProperty(childProperty) {
this.childProperty.next(childProperty);
}
}
You can then inject it both in your ParentComponent and in your ChildComponent:
In ChildComponent set the value:
import { Component, OnInit } from '#angular/core';
import { SharedService } from '../shared.service';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
public title = "hi"
constructor(private sharedService: SharedService) { }
ngOnInit() {
this.sharedService.setChildProperty(this.title);
}
}
And in your ParentComponent get the value:
...
import { SharedService } from '../shared.service';
#Component({...})
export class ParentComponent implements OnInit {
...
constructor(
...,
private sharedService: SharedService
) { }
ngOnInit() {
...
this.sharedService.childProperty$.subscribe(
childProperty => console.log('Got the Child Property from the Shared Service as: ', childProperty)
);
}
...
}
Make sure inside your parent.component.html template you've added the <child></child> tag.
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.
I have two components that are not parent and child components but i need to pass value from component A to component B.
example:
src/abc/cde/uij/componentA.ts has variable CustomerId = "ssss"
need to pas that variable customerID to src/abc/xyz/componentB.ts
Simple example:
Component A:
#Component({})
export class ComponentA {
constructor(private sharedService : SharedService) {}
sendMessage(msg : string) {
this.sharedService.send(msg);
}
}
Component B:
#Component({})
export class ComponentB {
constructor(private sharedService : SharedService) {
this.sharedService.stream$.subscribe(this.receiveMessage.bind(this));
}
receiveMessage(msg : string) {
console.log(msg); // your message from component A
}
}
Shared service:
#Injectable()
export class SharedService {
private _stream$ = new Rx.BehaviorSubject("");
public stream$ = this._stream$.asObservable();
send(msg : string) {
this._stream$.next(msg);
}
}
Shared service have to be placed in the same NgModule.
On your service define a setMyProperty() and a getMyProperty(). Then setMyProperty with a value from Component A. ComponentB then getMyProperty where you return the value...
You have to inject the service into both components.
You could give this a shot. Its pretty simple and straight forward.
I just followed THIS example and made some changes so that it could be siblings talking instead of parent/child.
my-service.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MyService {
// Observable string sources
private myAnnouncedSource = new Subject<string>();
// Observable string streams
myAnnounced$ = this.myAnnouncedSource.asObservable();
// Service message commands
announceItem(item: string) {
this.myAnnouncedSource.next(item);
}
}
my-comp1.component.ts
import { Component } from '#angular/core';
import { MyService } from './my-service.service';
#Component({
selector: 'my-compA',
template: `...`,
providers: [MyService]
})
export class MyComponentA {
constructor(private myService: MyService) {
}
announceToOtherComps() {
let sharedItem = "shibby";
this.myService.announceItem(sharedItem);
}
}
my-comp2.component.ts
import { Component, Input, OnDestroy } from '#angular/core';
import { MyService } from './my-service.service';
import { Subscription } from 'rxjs/Subscription';
#Component({
selector: 'my-compB',
template: `...`,
providers: [MyService]
})
export class MyComponentB implements OnDestroy {
sharedItem = '<no data>';
subscription: Subscription;
constructor(private myService: MyService) {
this.subscription = myService.myAnnounced$.subscribe(
item => {
this.sharedItem = item;
});
}
ngOnDestroy() {
// prevent memory leak when component destroyed
this.subscription.unsubscribe();
}
}
<component-a [id]="product.id"></component-a>
In the component-a ts file .Use it like below
export class ComponentA implements OnInit {
#Input() // <------------
id: number;
(...)
}