Angular updating values across views - html

I have an Angular app that has the following:
One component has a text input and a button. The user fills in the text input and clicks the button. This updates a the URL for a router link.
The router link loads a component called view and it in turn reads the URL parameter from the router link and places that value in a service and displays it on the component so I know it worked.
So if the user type 'abc' in the text input then the router link URL would be /view/abc. So 'abc' will be displayed in the view component. Sometimes users will paste a router link like /view/def. This works to update the view component.
The part I can't get to work is to update the text input box in the other component to reflect the current value of the pasted link.
I tried using 'AfterViewChecked' to read the value from the service but that executes before the service value is updated so it is always incorrect.
These cannot bind to the same variable because this will eventually turn into a web service call and I don't want the service to be updated while the user is typing into the text input box, only when they click the button.
I'm not sure where else to look. Any searching I do just brings up data binding, but that is not my problem.
The relevant files are below but the full test sample code is on StackBlitz at https://stackblitz.com/edit/github-jwr6wj.
If you change the URL in the text input and click the button the URL display below will update. But if you paste in the pseudo URL https://github-jwr6wj.stackblitz.io/view/http%253A%252F%252Fwww.ebay.com%252F the URL displayed below will update correctly but I can't figure out how to update the text input to reflect what came in with the URL.
update.service.ts contains the URL that is the current one. This service will also load the data from a web service.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class UpdateService {
url: string = "http://www.google.com/";
constructor() { }
}
view.component.ts is where the data selected by the user will be displayed. It parses the URL parameter for the data and updates the service with it.
import { ActivatedRoute, ParamMap } from '#angular/router';
import { UpdateService } from '../update.service';
#Component({
selector: 'app-view',
templateUrl: './view.component.html',
styleUrls: ['./view.component.css']
})
export class ViewComponent implements OnInit {
constructor(public activatedRoute:ActivatedRoute, public updateService: UpdateService) { }
ngOnInit(): void {
this.activatedRoute.paramMap.subscribe((paramMap: ParamMap) =>{
this.getUrl(paramMap);
});
}
getUrl(paramMap: ParamMap): void {
const incomingUrl = paramMap.get("url");
if (incomingUrl == null) {
this.updateService.url = "http://www.google.com/";
} else {
this.updateService.url = decodeURIComponent(incomingUrl);
}
}
}
view.component.html
<p>URL: {{updateService.url}}</p>
toolbar.component.ts is where the user will enter they request. sourceUrl is the variable that will be updated when the user types. However I also want it to update when the page is visited via the browser URL with the correct data as part of that URL. I can send data to the view component via the router but I can't find out how to send data back to the toolbar component.
import { UpdateService } from '../update.service';
#Component({
selector: 'app-toolbar',
templateUrl: './toolbar.component.html',
styleUrls: ['./toolbar.component.css'],
})
export class ToolbarComponent implements OnInit {
sourceUrl: string = '';
constructor(private updateService: UpdateService) {}
ngOnInit(): void {
this.sourceUrl = this.updateService.url;
}
getViewUrl(): string {
return '/view/' + encodeURIComponent(this.sourceUrl);
}
}
toolbar.component.html
<div class="col-sm-12">
<input type="text" [(ngModel)]="sourceUrl" />
<a class="btn btn-primary" [routerLink]="getViewUrl()">
<span class="fa fa-eye"></span>
</a>
</div>

One way to share data between components is using a Service and Observables. Change your url in the Service to be BehaviorSubject with an initial value.
The way BehaviorSubject works is that you emit values from components to update the Observable in the Service. The BehaviorSubject behaves both as an Observer and Observable.
Essentially, an Observer is an object that listens to events, in this case, updating the URL. An Observable is an object that components listen to for updates or changes. In this case, the View Component listens to the BehaviorSubject for this update to the URL.
Service
export class UpdateService {
private url$ = new BehaviorSubject<string>('www.google.com');
public readonly url: Observable<string> = this.url$.asObservable();
constructor() {}
}
Toolbar Component
export class ToolbarComponent implements OnInit {
sourceUrl: string = '';
constructor(private updateService: UpdateService) {}
ngOnInit(): void {
this.updateService.url.subscribe((str) => {
this.sourceUrl = str;
});
}
getViewUrl(): string {
return '/view/' + encodeURIComponent(this.sourceUrl);
}
}
View Component
export class ViewComponent implements OnInit {
constructor(
public activatedRoute: ActivatedRoute,
public updateService: UpdateService
) {}
ngOnInit(): void {
this.activatedRoute.paramMap.subscribe((paramMap: ParamMap) => {
this.getUrl(paramMap);
});
}
getUrl(paramMap: ParamMap): void {
const incomingUrl = paramMap.get('url');
if (incomingUrl == null) {
this.updateService.url.next('http://www.google.com/');
} else {
this.updateService.url.next(decodeURIComponent(incomingUrl));
}
}
}
View Component HTML
<p>URL: {{ updateService.url | async }}</p>

You are right to try with AfterViewChecked because it's just a timing issue. What you could do is have url inside updateService defined as a BehaviourSubject, so that at the moment it's updated in your view component, you see the change in the toolbar component.
Inside the service :
public url$: BehaviorSubject<string> = new BehaviorSubject("http://www.google.com/");
Inside the view component ts :
getUrl(paramMap: ParamMap): void {
const incomingUrl = paramMap.get("url");
if (incomingUrl == null) {
this.updateService.url$.next("http://www.google.com/");
} else {
this.updateService.url$.next(decodeURIComponent(incomingUrl));
}
}
And inside the view component HTML : (you can also subscribe to the Behaviour Subject directly inside the ts)
<p>URL: {{updateService.url$ | async}}</p>
And you will also have to deal with the fact that the url is a Subject inside the toolbar component ts!
Good luck, let me know if this is not clear!

Related

calling back end only for particular component in Angular

I have Tags Components in my project and I reused that component in other components. In my Tags component ngOnInit, I called backend to get all the existing tags. The problem I have right now is that call is applied to every other components even though the call is not needed at other components other than Edit Components. Since I only need the backend call to show existing tags just for Edit Components, I tried to move that call to Edit Components ngOninit but it didn't show me the existing tags anymore. I would be really appreciated if I can get any help or suggestion on how to fix this.
Tags Component TS
ngOnInit(): void {
this.tagService.getAllTagsByType('user').subscribe((normalTags) => {
this.loading = true;
if (normalTags)
this.allnormalTags = normalTags;
this.allnormalTags.forEach(normalTags => {
this.allTagNames.push(normalTags.tag);
});
this.loading = false;
})
}
If i add this call in Tags Component, it show me all the existing tags in drop down. I tried to move this to Edit component ngOnIt since I only want Eidt Component to use that call but It didn't show me existing tags anymore.
Tags.Service.ts
getAllTagsByType(tagType: any){
return this.http.get<Tag[]>(`${environment.api.chart}/tags/all/${tagType}`).pipe(first());
}
You could try to setup a flag to trigger the backend call using #Input.
tags.component.ts
import { Component, OnInit, Input } from '#angular/core';
export class TagsComponent implements OnInit {
#Input() getAllTags = false;
ngOnInit(): void {
if (this.getAllTags) { // <-- check here
this.tagService.getAllTagsByType('user').subscribe(
(normalTags) => {
this.loading = true;
if (normalTags)
this.allnormalTags = normalTags;
this.allnormalTags.forEach(normalTags => {
this.allTagNames.push(normalTags.tag);
});
this.loading = false;
},
error => {
// handle error
}
);
}
}
}
Now pass the value true to getAllTags when you wish to make the backend call. Since ngOnChanges hook is triggered before ngOnInit, the call will not be made if the property isn't passed in the component selector.
<!-- edit component -->
<mc-tags
[getAllTags]="true"
[workspace]="workspace"
[removable]="true"
[selectable]="true"
[canAdd]="true" ]
[editMode]="true"
(added)="tagAdded($event)"
(removed)="tagRemoved($event)"
> </mc-tags>
<!-- other components -->
<mc-tags [workspace]="workspace"></mc-tags>
Try to use RxJS. You should keep your Tags Data in TagService as a Subject (observable). Btw it is always best practise to store data in service layer.
TagService:
#Injectable({
providedIn: 'root'
})
export class TagService {
tagsSource = new BehaviorSubject<Tag[]>(null);
allnormalTags$ = this.tagsSource.asObservable();
getAllTagsByType(type: string){
http.request.subscribe(resultData => this.tagsSource.next(resultData))
}
}
Then in your component you can check whether data are already loaded and don't call it again.
export class ProductListComponent implements OnInit {
constructor(private tagService: TagService) { }
ngOnInit(): void {
if (isNullOrUndefined(this.tagService.tagSource.getValue())
this.tagService.getAllTagsByType('user')
}
P.S. You don't need to explicitly subscribe service observable in your component. Instead you can directly get your data from service subject/observable with async pipe.
<table *ngIf="tagService.allnormalTags$ | async as allnormalTags">
<tbody>
<tr class="product-list-item" *ngFor="let tag of allnormalTags">
<td data-label="name"> {{tag.name}} </td>

How to pass a constant from a component to a service and make a http request [duplicate]

This question already has answers here:
Angular 2 http.post() is not sending the request
(3 answers)
Closed 3 years ago.
Okay so what I'm trying to do is I have an *ngFor that populates with category names what I'm trying to do is get the one the user clicked on and then making an API call with it but when I click the button nothing happens. Is there a way to pass the const to my api.service or is the way I'm doing it in the component the only way to achieve this.
section: string;
sectionlink(text: any): void {
this.section = text;
const endpointlist = 'https://api.nytimes.com/svc/books/v3/lists/{{this.section}}?api-key=my-key-here';
return this.http.get<Listmodel[]>(endpointlist);
console.log(this.section);
}
<li class="uk-active"><button (click)="sectionlink(section.list_name)">{{section.list_name}}</button></li>
service.ts
getSectionlinks(text: string): Observable<Listmodel[]> {
const endpointlist = `https://api.nytimes.com/svc/books/v3/lists/${text}?api-key=7W3E72BGAxGLOHlX9Oe2GQSOtCtRJXAt`;
return this.http.get<Listmodel[]>(endpointlist);
}
component.ts
sectionlink(text: string){
this.service.getSectionlinks(text).subscribe(response => this.sectionData = response);
}
HTML
<li class="uk-active">
<button (click)="sectionlink(section.list_name)">{{section.list_name}}< /button>
</li>
Assuming the text you send in your function is valid, you can do something like this.
sectionlink(text: string): void {
const endpointlist = 'https://api.nytimes.com/svc/books/v3/lists/' + text + '?api-key=7W3E72BGAxGLOHlX9Oe2GQSOtCtRJXAt';
this.http.get<Listmodel[]>(endpointlist).subscribe(result => {
console.log(result);
});
}
This will call your API and subscribe the result. For more info about HttpClient, please check the documentation here
Yes you can retrieve API call results right from the component, as Antoine showed. However, as your app grows, so will your component, so it's a best practice to put your API calls in a separate service like so:
import {HttpClient} from '#angular/common/http';
#Injectable({providedIn:'root'})
export class APIService {
public static API_ENDPOINT = "https://api.nytimes.com/svc/books/v3/lists/";
private static API_KEY = "IUOAHIUSYDFGOIAGHSFDOIHASDH"; // made up api key
// the app will inject the HttpClient for you when this class is instantiated
constructor(private http: HttpClient) {}
// you can call this method whatever you like, as long as it makes sense
public getBooks(text:string):Observable<ListModel[]> {
return this.http.get<ListModel[]>(APIService.API_ENDPOINT + text + "?api-key=" + APIService.API_KEY);
}
}
then in your component you can just call this method:
// angular will inject your custom service here
constructor(private apiService: APIService) {}
public sectionlink(text:string) {
this.apiService.getBooks(text).subscribe(response => {
console.log(response);
// do whatever you need to do with response here
});
}
don't forget to provide this in your module (only if this is part of a feature module; the #injectable({providedIn:'root'}) should take care of this):
#NgModule({
declarations: [
YourCustomComponent
],
imports: [
HttpClientModule
],
providers: [APIService],
})
export class FeatureModule { }

How fixed method POST with angular 5

I develop application for Portal, but when i create new role,
the role is created but the problem is that the added element is not displayed, it must refresh the browser to display this new element !!!, what do I do to display the added element directly in my table , and how to develop the other methods (put and delete) and thank's (i develop this application with angular 5)
thid my code .html:
<form #personForm="ngForm" (ngSubmit)="onSubmit(personForm.value)">
<input name="RoleName" [(ngModel)]="RoleName">
<button type="submit">Save</button>
</form>
and this my code .ts:
export interface Role {
RoleName: string;
}
#Component({
selector: 'app-role',
templateUrl: './role.component.html',
styleUrls: ['./role.component.css']
})
export class RoleComponent implements OnInit, AfterViewInit {
private roles: any;
constructor(private _roleService: RoleService, private http: HttpClient) { }
onSubmit(role: Role) {
return this.http.post('http://172.16.47.34:8282/MonProject.webservices/api/Roles', role).subscribe(status => console.log(JSON.stringify(status)));
}
async ngOnInit() {
this.roles = await this._roleService.getRoles();
}
ngAfterViewInit(): void {
$('#example-table').DataTable({
pageLength: 10,
});
}
}
If your post is successful, then update your data table with the newly added role.
You can achieve this by adding this logic inside the subscribe function.
Put and delete should follow the same concept. If the response shows that the request was successfully processed, then (in the subscribe) you can modify your data table.
Two things I noticed:
Try to avoid using jQuery with Angular. These two are not the best
together. You can get a reference for the data table with
ViewChild.
Using async/await with ngOnInit is a bad practice.

Load Script Tag in Angular 2 App When Src Attribute is from Web API Call

Context:
I have an Angular 2+ application that makes calls to a web API containing URLs for a src attribute on a script tag that is created by a loadScript function in the AfterViewInit lifecycle hook.
The web API returns a JsonResult and is yielding the data I expect. I was able to interpolate some of the data in the component's template.
Additionally, before I added the call to the web API, the loadScript function was working with a hard-coded argument.
Reading a thread on github. A "member" stated that scripts are not supposed to be loaded on demand. So what I implemented with the loadScript function is essentially a work around, but how else would load them? I don't want to have a seemingly endless amount of script tags sitting in the index.html file.
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Http } from '#angular/http';
#Component({
selector: 'app-agriculture-roadmap',
templateUrl: './agriculture-roadmap.component.html',
styleUrls: ['./agriculture-roadmap.component.css']
})
export class RoadmapComponent implements OnInit, AfterViewInit {
constructor(private _httpService: Http, private _route: ActivatedRoute)
{
}
apiRoadmaps: { roadmapName: string, pdfRoadmapURL: string, jsRoadmapURL: string };
ngOnInit() {
this._httpService
.get('/api/roadmaps/' + this._route.params)
.subscribe(values => {
this.apiRoadmaps = values.json() as { roadmapName: string, pdfRoadmapURL: string, jsRoadmapURL: string };
});
}
async ngAfterViewInit() {
await this.loadScript(this.apiRoadmaps.jsRoadmapURL);
}
private loadScript(scriptUrl: string) {
return new Promise((resolve, reject) => {
const scriptElement = document.createElement('script')
scriptElement.src = scriptUrl
scriptElement.onload = resolve
document.body.appendChild(scriptElement)
})
}
}
If you are using angular cli .
Then place these scripts in
angular-cli.json file under scripts array
scripts:[
.....
]
Please refer this [link] (https://rahulrsingh09.github.io/AngularConcepts/faq)
It has a question on how to refer third party js or scripts in Angular with or without typings.

reset or reload the same page in Angular 2

I am trying to reload the same url in Angular 2 by using router.navigate but it is not working.
Url: http://localhost:3000/page1
Scenario: I am on http://localhost:3000/landing and on click of button, will pass routing parameter which should reload the page.
Example:
Suppose user is in page1(Edit form) and url reads as localhost:3000/page1 and there will be a button Create new, on click of button passing a routing paramter using
let navigationExtras: NavigationExtras = { queryParams: { "refresh": "Y" } };
this.router.navigate(['page1'], navigationExtras);
ngOnInit is only called when the component is created. When you navigate to the same page (with different url parameters) the ngOnInit method is not recalled. You should use the ActivatedRoute Params Observable
import { ActivatedRoute } from '#angular/router';
export class MyComponent implements OnInit {
constructor(private _activatedRoute: ActivatedRoute){}
ngOnInit(){
this._activatedRoute.params.subscribe(
params => {
// this is called everytime the url changes
}
}
}
You just need to call window.location.reload();