Angular: pass single object within ngFor to router linked component - javascript

How do I pass the data binded information within the <a> tag (Within the volume-links.component.html ) to my page-view component when the link is clicked.
I want to pass that particular diary object to my page-view.
I've looked into parent and child component interaction but I don't think that is the proper way to do it. I've looked into communicating via a service but I do not know how that would work for a problem such as this.
volume-links.component.html
<ul class="navigation">
<li *ngFor="let d of diary">
<a id={{d.notebook_id}} routerLink="/page-view" routerLinkActive="active">Volume {{d.notebook_id}}, {{ d.date }}, {{ d.volume_id }}, Add MS {{ d.ms_id }}</a>
</li>
</ul>
volume-links.component.ts
import { Component, OnInit } from '#angular/core';
import { Http } from '#angular/http';
import { HttpClient, HttpClientModule } from '#angular/common/http';
import 'rxjs/add/operator/map'
#Component({
selector: 'app-volume-links',
templateUrl: './volume-links.component.html',
styleUrls: ['./volume-links.component.scss'],
//encapsulation: ViewEncapsulation.None
})
export class VolumeLinksComponent implements OnInit {
diary : String;
constructor(private http: HttpClient) { }
ngOnInit() {
this.http.get('/api/diaries').subscribe(data => {
this.diary = data["data"]["docs"];
console.log(this.diary);
})
}
}

You want to look at https://angular.io/guide/component-interaction
There are several methods / ways to achieve this and depending on your use case choose one.
I would define an Input property in VolumeLinksComponent and pass the diary object in there (that's the first part "Pass data from parent to child with input binding").
This would look something like:
<a *ngFor='let diary of diaries' (click)='chooseDiary(diary)'>
<my-diary-container [diary]='selectedDiary'></my-diary-container>
and that parent component of course needs a property 'selectedDiary' and a method:
chooseDiary(diary: Diary): void {
this.selectedDiary = diary;
}
But in your provided case it seems like you just need the specific id since you want to retrieve details from the api? In that case you could just define a route with the id and when the route is accessed ask an additional DiaryService to retrieve what you need.

Related

how to pass form value to another component in angular

I want to show taxDetailsId in my child component Html page.
But when click submit button.
After click submit button then shows taxDetailsId in my child component Html page.
Parent Component
export class OnlinePaymentComponent implements OnInit {
HttpClient: any;
paymentForm: FormGroup = this.formBuilder.group({
taxDetailsId: ['', [Validators.required]]
});
constructor(
private formBuilder: FormBuilder,
private router: Router,
) {}
ngOnInit() {}
submitForm(): void {
if (!this.paymentForm.valid) {
this.router.navigate(['/home/online-payment/error']);
return;
}
}
}
Parent.Component.html
<form [formGroup]="paymentForm" (ngSubmit)="submitForm()">
<label>Tax Details Id</label>
<input type="text" formControlName="taxDetailsId" placeholder="Tax Details Id" />
<button>Pay Bill</button>
<form>
Child Component
export class OnlinePaymentErrorComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
Child.Component.html
<div>
<button [routerLink]="['/home/online-payment']" >Back Home</button>
</div>
you can try this pattern this.router.navigate(['/heroes', { id: heroId }]);
https://angular.io/guide/router
you can use angular #Input() decorator for it.
Child Component
import { Component, Input } from '#angular/core';
export class ChileComponent {
#Input() public taxDetailsId: number;
}
Child Component HTML
enter code here
<div>
{{ taxDetailsId }}
<button [routerLink]="['/home/online-payment']" >Back Home</button>
</div>
Parent Component HTML
<app-child-component [taxDetailsId]="taxDetailsId"> </app-child-component>
https://angular.io/guide/inputs-outputs
You can pass components around using Angular's InjectionToken.
First you start off by creating the token:
export const ONLINE_PAYMENT_REF = new InjectionToken<OnlinePaymentComponent>('OnlinePaymentComponent');
Next you add the token to one of the root components as a provider, in this case it is the OnlinePaymentComponent. This way everything that is a child of this component, and everything that is a child of those components, and so on; will have a reference to the main parent that we create here:
#Component({
selector: 'online-payment',
template: `
<online-payment-error></online-payment-error>
`,
providers: [
{
provide: ONLINE_PAYMENT_REF,
// Forwards the instance of OnlinePaymentComponent when injected into
// the child components constructor.
useExisting: forwardRef(() => OnlinePaymentComponent)
}
]
})
export class OnlinePaymentComponent {
message = 'I am the Online Payment Component';
}
Now that we have the main component setup, we can access it through the constructor of anything that is a child of OnlinePaymentComponent (no matter how deep it is).
#Component({
selector: 'online-payment-error',
template: `
<h2>Child</h2>
<strong>Parent Message:</strong> {{parentMessage}}
`
})
export class OnlinePaymentErrorComponent implements OnInit {
parentMessage = '';
constructor(
#Inject(ONLINE_PAYMENT_REF) private parent: OnlinePaymentComponent
) {}
ngOnInit() {
this.parentMessage = this.parent.message;
}
}
When all is said and done, you will see the following:
The pros of this method are that you don't have to bind values to the elements in the template, and if those components have components that need to reference the parent you wouldn't have to bind to those either. Since components look up the hierarchy till they find the first instance of the provider that we are looking for they will find the one in OnlinePaymentComponent.
This becomes very helpful when components get deeper and deeper into the parent component (say 5 levels deep), that means every time you would have to pass a reference to the template element 5 times, and if it changes or gets deeper you would have to update all the templates.
With this method we no longer need to update templates to pass data from one component to another component, we just request it in our constructor as seen in OnlinePaymentErrorComponent.
There are two simple ways:
Using query params (without routerLink).
Using Observables.
Using query params, you can use the router.navigate and pass the params you need (Id) along with the route.
eg: this.route.navigate(['yourroute/route', { tId: variableWithId }])
Using Observable, when you click on the button, use the same router navigate without params and pass the required data to an observable. On successful routing to the next page, get the resolved data from the observable.

Angular: How to get value from one component's frontend (app.compont.html) to another component's backend (other.component.ts)

Consider a simple crud scenario. I have a lot of input fields and buttons in app.component.html. When i press a button from app.component.html, it will send html field value to 'other.component.ts' component and will display the result back in app.component.html after processing (like add, subtract or other).
Here is app.component.html
<a routerLink="posts/">Show Posts</a>
<input type="number" [(ngModel)]="get-one-post-id">
<a routerLink="/post-by-id">Show One Posts</a>
<router-outlet>
</router-outlet>
post-by-id-component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from '../data.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-post-by-id',
templateUrl: './post-by-id.component.html',
styleUrls: ['./post-by-id.component.css']
})
export class PostByIdComponent implements OnInit {
posts: object;
constructor(private dataService: DataService) { }
ngOnInit(): void {
// const id = ??
this.GetPost(1);
}
async GetPost(id: number)
{
const response = await this.dataService.Get_A_Post(id);
const dataService = await response.json();
this.posts = dataService;
}
}
post-by-id-component.html
<div *ngFor="let post of posts">
<h3>{{post.title}}</h3>
<p>{{post.body}}</p>
</div>
I just want to get value from the field called get-one-post-id from app.component.html to post-by-id-component.ts [where I commented // const id = ??]. But i can't find a way to import it.
To share Data between Angular Components exists 4 different ways:
Parent to Child: Sharing Data via Input
Child to Parent: Sharing Data via ViewChild
Child to Parent: Sharing Data via Output() and EventEmitter
Unrelated Components: Sharing Data with a Service
You can read this useful article to see how it works.

Angular 6 : Real time data

I have app in which a user can add a comment , and the comment is displayed , my problem is comments are not displayed untill I refresh the page.
I want a comment to be displayed after the user click just enter or submit button
Here is what I have so far:
Getting data service.ts
this.activeRouter.params.subscribe((params) => {
let id = params['id'];
this.moviesService.getComments(id)
.then(comments => {
console.log(comments);
this.comments = comments;
});
});
2.Then display to the front end: html
<div *ngFor="let comment of comments" class="col-md-7">
<ul class="list-group">
<li class="list-group-item">Author: {{comment.author}}</li>
<li class="list-group-item">Comments: {{comment.description}}</li>
</ul>
<br>
</div>
Unfortunately when my server updates the JSON, the html does not update at all until I refresh the page then I can see the added comments wrong
what am I missing in my code to accomplish what I want? newbie though
Your code is good but unfortunately a Promise only resolves to one value.
However, observables can provide you with a real time stream of data!
Make the moviesService.getComments() method return an observable which returns comments.
It should look a little something like this (assume you are using the angular HttpClient to fetch the comments):
// movieService.service.ts
import { HttpClient } from '#angular/common/http'
...
constructor(
private http: HttpClient
)
getComments() {
return this.http.get<Comments>(url)
}
...
You can consume the observable like so:
// comment.component.ts
...
comments: Observable<Comments>
...
ngOnInit() {
this.comments = this.movieService.getComments()
}
...
And finally in the template:
// comments.component.html
<div *ngFor="let comment of comments | async" class="col-md-7">
<ul class="list-group">
<li class="list-group-item">Author: {{comment.author}}</li>
<li class="list-group-item">Comments: {{comment.description}}</li>
</ul>
<br>
</div>
Using Async Pipe & Observables
Pipes in Angular work just as pipes work in Linux. They accept an input and produce an output. What the output is going to be is determined by the pipe's functionality. This pipe accepts a promise or an observable as an input, and it can update the template whenever the promise is resolved or when the observable emits some new value. As with all pipes, we need to apply the pipe in the template.
Let's assume that we have a list of products returned by an API and that we have the following service available:
// api.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class ApiService {
constructor(private http: HttpClient) { }
getProducts() {
return this.http.get('http://localhost:3000/api/products');
}
}
The code above is straightforward - we specify the getProducts() method that returns the HTTP GET call.
It's time to consume this service in the component. And what we'll do here is create an Observable and assign the result of the getProducts() method to it. Furthermore, we'll make that call every 1 second, so if there's an update at the API level, we can refresh the template:
// some.component.ts
import { Component, OnInit, OnDestroy, Input } from '#angular/core';
import { ApiService } from './../api.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/switchMap';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
#Input() products$: Observable<any>;
constructor(private api: ApiService) { }
ngOnInit() {
this.products$ = Observable
.interval(1000)
.startWith(0).switchMap(() => this.api.getProducts());
}
}
And last but not least, we need to apply the async pipe in our template:
<ul>
<li *ngFor="let product of products$ | async">{{ product.prod_name }} for {{ product.price | currency:'£'}}</li>
</ul>
This way, if we push a new item to the API (or remove one or multiple item(s)) the updates are going to be visible in the component in 1 second.

Angular 4 show popup onclick by other component

i'm struggling about this problem and can't figure out.
I simply need to show a popup div situated in the page clicking from a menu entry in my navbar.component.
I added a property "show" in my popup which prints the "show" class on my div using the ngClass (with if) directive. I can get this working if the action button is inside my popup component but i cannot print the show class clicking on another component. The property in the Object get updated but the class is not printed. I'm using angular 4 with ng-bootstrap. I tried both with services and with parent/child emit event.
This is is my situation:
app.component.html
<app-nav-bar></app-nav-bar>
<app-login></app-login>
<router-outlet></router-outlet>
<app-footer></app-footer>
navbar.component.html
...
<button class="dropdown-item" (click)="showPopup()">LOGIN</button>
...
navbar.component.ts
import {Component, EventEmitter, Input, OnInit, Output} from '#angular/core';
#Component({
moduleId: module.id,
selector: 'app-nav-bar',
templateUrl: 'navbar.component.html',
styleUrls: ['./navbar.component.css'],
})
export class NavbarComponent implements OnInit {
#Output() show = new EventEmitter<boolean>();
ngOnInit() {
}
showPopup() {
this.show.emit(true);
}
}
login.component.html
<div id="wrapper-login-popup" class="fade-from-top" [(class.show)]="show">
<div id="container-login-popup">
<div class="row">
<div class="col-sm-12 text-center">
<img id="popup-bomb" src="assets/images/bomb.png" alt="bomb"/>
<img id="popup-close" class="close-icon" src="assets/images/close.png" alt="close"
(click)="closePopup()"/>
</div>
</div>
</div>
</div>
login.component.ts
import {Component, Input, OnInit} from '#angular/core';
import {AuthService} from '../services/auth.service';
import {IUser} from './user';
#Component({
selector: 'app-login',
templateUrl: 'login.component.html',
styleUrls: ['login.css']
})
export class LoginComponent implements OnInit {
private username: string;
private password: string;
#Input() show: boolean = false;
constructor(private AuthService: AuthService) {
}
ngOnInit() {
}
login() {
...
}
showPopup() {
console.log(this); //Show is false
this.show = true;
console.log(this); //Show is true but does not trigger the show class
}
closePopup() {
this.show = false;
}
}
The issue here is that your nav-bar and login components are siblings and can't directly communicate with each other. You have show as an output of navbar and as an input of login, but you haven't connected the dots.
You need to update your app.component to connect them.
export class AppComponent implements OnInit {
show = false;
onShow() { this.show = true; }
}
and in the template:
<app-nav-bar (show)="onShow()"></app-nav-bar>
<app-login [(show)]="show"></app-login>
There's a lot of two way binding going on here which works for something simple liek this, but generally it's a bad idea as it leads to unmaintainable code. You should choose one owner of the show variable and force all changes to it through him. In this case the app component is the most logical owner, so I'd change the login component to emit an event that changes the show variable in app component adn remove all 2 way bindings, but in a bigger app, you may even want a separate service that manages hiding/showing pop ups. This eliminates the need for the sending a message up and down your component tree, you can inject the service where it's needed.
As another commenter mentioned, you also should be using ngClass for class manipulation like
[ngClass]="{'show':show}"
a service based solution would look like
import {Subject} from 'rxjs/Subject';
#Injectable()
export class PopUpService {
private showPopUpSource = new Subject();
showPopUp$ = this.showPopUpSource.asObservable();
showPopUp() { this.popUpSource.next(true); }
closePopUp() { this.popUpSource.next(false); }
}
Then you provide in app module or at app component level:
providers:[PopUpService]
make sure you don't re provide this later, as you only want one copy to exist so everyone shares it.
then inject into both components, and have them call the services close or show pop up methods.
then in the login component you bind to the popUp$ observable like
constructor(private popUpSvc:PopUpService){}
show$;
ngOnInit() { this.show$ = this.popUpSvc.showPopUp$; }
showPopUp() { this.popUpSvc.showPopUp(); }
closePopUp() { this.popUpSvc.closePopUp(); }
and in the template subscribe w async pipe like
<div id="wrapper-login-popup" class="fade-from-top" [ngClass]="{'show': (show$ | async) }">
The reason for using the async pipe is garbage collection managemetn is simpler. If you don't use async, you need to garbage collect manually in ngOnDestroy by calling unsubscribe(), otherwise your subscriptions will keep stacking up. There is also a more nuanced benefit in that the async pipe triggers change detection, but this only becomes important if you start using onPush change detection for performance optimization.

Angular 2 Router Link with multiple parameters

I have a route like this
path: 'projects/:id/settings'
And in my header.component.html I want to have a link to this page
<a routerLink="['projects', progectId,'settings']" routerLinkActive="active">cool link</a>
I have a project.component, where when I click on some project I go on the project page. And then I need have a possibility go to projects/:id/settings or another similar route.
How can I pass progectId variable from projects.component?
Or maybe somebody knows another way to implement this.
I had the same issue for this kind of route you should use routerLink like this:
<a routerLink="/projects/{{progectId}}/settings" routerLinkActive="active">cool link</a>
and change your route path in routing module like this:
{ path: ':id/settings', component: YourComponent}
for additional information to get projectId you should follow these step:
Inject ActivatedRoute object in your component constructor.
Define a variable called projectId in your component.
Get params by activatedRoute object in ngOnInit() method.
Finally you should get projectId in your html like this: {{projectId}}
import {Router, ActivatedRoute, Params} from '#angular/router';
#Component({
selector: 'app-your-component',
templateUrl: './your-component.component.html',
styleUrls: ['./your-component.component.css']
})
export class YourComponent implements OnInit {
private projectId: string;
constructor(private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.activatedRoute.params.subscribe((params: Params) => {
this.projectId = params['id'];
});
}}
I generally do this in the component class like this
somefunctionName() {
let link = ['/summary', this.param1, this.param2];
this.router.navigate(link);
}
And then call the function from the html code like this
<a (click)="somefunctionName()">
In your case since I am assuming you are looping through your projects you could call your function with a parameter like this
<a (click)="somefunctionName(pass the parameter here)">
Then change the function definition to reflect this.

Categories

Resources