I have a button that redirects to a new page and at the same time should save data to a Service. As I use it now it looks like this:
<button [disabled]="!isValid" (click)="saveToService()" routerLink="/link">Next</button>
Now I wonder if this is best practice. It feels like the html button is somewhat cluttered by so many seperate functionalities. The obvious alternative is to move the router navigation to a function that does both things, as in:
<button [disabled]="!isValid" (click)="saveAndNavigate()">Next</button>
and in ts:
private saveAndNavigate():void { this.service.setData(data); this.router.navigate(['/link]); }
Is there a 'right' way to do this? Are there some unwanted side effects from doing both actions in html?
Thanks
I would suggest you to do it in router promises. So you can:
this.router.navigate(['/link]).then(() => {
this.service.setData(data);
});
I would implement the OnDestroy function in your component, so you can store the data when the component terminates.
Something like this in HTML:
<button [disabled]="!isValid" routerLink="/link">Next</button>
And like this in your component:
import { Component } from '#angular/core';
#Component({...})
export class ThisComponent implements OnDestroy {
ngOnDestroy(){
saveToService()
}
}
If your navigation is performed regardless of the outcome of the service call, then Fatih's answer would work just fine.
On the other hand, and what I've normally seen, is that page navigation should only occur after (successful) completion of the request. If this is the case, I would remove the routerLink directive from your button and keep the (click) function. That function could look like this:
// if your service is making an Http request
public saveToService() {
this.service.saveStuff().pipe(
tap(() => this.router.navigate(['/somewhere']))
)
}
tap simply performs some action without affecting the data stream, so it's perfect for router navigation.
Related
I am building a restaurant review website with react js,html and css. I need to make a child component RestaurantInput update a sibling component Restaurant list.
I created handlers which pass informations to App component(the parent) by a callback and when there is an input change in the RestaurantInput it get updated by the handlers. The App component pass then the information to RestaurantList component by props which will render the new restaurant on the UI.
Unfortunatly there is no rendering of the new restaurant . I do not know where i got it wrong. Is there anyone who can help?
I have tried to console log the Restaurants imported from a Json at my local pc. But it look like it was not updated either.
I went to the React js documentation but did not get any clear answer either.
Many solution are for when there is a proper JSON file from the back end and I could not figure out how to apply them in my current situation.
RestauranInput.jsx:
handlechange(e){
const name=e.target.name;
const value=e.target.value;
this.setState((prevState)=>{
prevState.restaurant[name]=value;
return{restaurant:prevState.restaurant};
});
}
handleSave=(e)=>{
this.props.onSave(this.state.restaurant);
this.setState({
restaurant:Object.assign({},Init_value),
error:{}});
e.preventDefault();
}
App.js:
class App extends React.Component {
constructor(props){
super(props);
this.handlerestaurantclick=this.handlerestaurantclick.bind(this);
this.saveRestaurant=this.saveRestaurant.bind(this);
this.state={restaurants:Restaurantlist,showcomponent:false,
restaurantClicked:-1,newrestaurant:{}}
}
saveRestaurant(restaurant){
if(!restaurant.key){
restaurant.key= Object.keys(this.state.restaurants).length;}
this.setState((prevState)=>
{
let restaurants=prevState.restaurants;
restaurants[restaurant.key]=restaurant;
return{restaurants};
});
}
RestaurantList.jsx:
let list=[];
restaurantArray.forEach((item,index)=>{
list.push(<Restaurant key={index} name=
{item.restaurantName}
adress={item.address} ratings={item.ratings} onClick=
{()=>this.handleclick(index)}> </Restaurant>)})
return(<div className="restaurant-list">
<Filter getmin_filter={this.state.handle_min} get_max=
{this.state.handle_max}/>
{list}
</div>);
}
props are not states if they changeĀ on the parent the child components are not rerender so you have to use "componentDidUpdate" check the link below
Re-render React component when prop changes
any communication that is not parent to child, you can either use events or states manager like redux
I have a component A which only contain a div with an id and a buttons that renders a component inside the div using innterHTML document.getElementById('my-router-outlet').innerHTML = '<app-component-b-page></app-component-b-page>';. But this is not rendering I wonder why?.
I'm trying to avoid using ngIf to be a selector for which component should be rendered for performance reason. Also if I clear the innerHTML does the resources of that component will be cleared?
Okay so a few things here
innerHTML = '<app-component-b-page></app-component-b-page>' is never going to work, angular wont recognise the angular component tag from a innerHTML call
using *ngIf wont affect the performance of the page, so doing the following
<app-component-b-page *ngIf="value === true"></app-component-b-page>
is probably you best option here
If you really don't want to use *ngIf you can use #ViewChild and ComponentFactoryResolver
In your HTML
<!-- this is where your component will be rendered -->
<div #entry></div>
In your component
import { Component, OnInit, ViewChild, ViewContainerRef, ComponentFactoryResolver } from '#angular/core'
import { YourComponent } from ... // import the component you want to inject
// ...
export class ...
#ViewChild('entry', {read: ViewContainerRef, static: true }) entry: ViewContainerRef;
constructor(
private _resolver: ComponentFactoryResolver
) {}
showComponent() {
const factory = this._resolver.resolveComponentFactory(YourComponent);
// this will insert your component onto the page
const component = this.entry.createComponent(factory);
}
// and if you want to dynamically remove the created component you can do this
removeComponent() {
this.entry.clear();
}
You are adding the element to the dom directly and it's not rendered by Angular.
You should go for the *ngIf.
My function isn't called when I click the <a... tag.
I have the following code in my component:
public htmlstr: string;
public idUser:number;
this.idUser = 1;
this.htmlstr = `<a (click)="delete(idUser)">${idUser}</a>`;
public delete(idUser){
alert("id " + idUser);
}
My html
<div [innerHTML]="htmlstr"></div>
but the function delete isn't called and does not show the alert.
The <div... is created dynamically
If anyone face same issue and above all answer not working then try my trick :
In HTML :
<button onclick="Window.myComponent.test()"> test </button>
In component :
class
constructor(){
Window["myComponent"] = this;
}
test(){
console.log("testing");
}
Your main issue here, on-top of the things pointed out by #Matt Clyde and #Marciej21592, is that you're trying to dynamically add HTML code that needs to be compiled before it can be used (you're trying to bind to a method and variable).
Some ways of doing this can be seen here.
From the code you have supplied, however, there are much easier ways to accomplish what you are after. For starters, I would have that code in the HTML to begin with and hide/show it as needed with ngIf.
i use this method and its work
public htmlstr: string;
public idUser:number;
this.idUser = 1;
this.htmlstr = `<a id='innerHtmlClick'>${idUser}</a>`
this.htmlstr.querySelector(`innerHtmlClick`).addEventListener('click', () => {
this.delete(idUser);
});
public delete(idUser){
alert("id " + idUser);
}
EventListener listen the event bye using id of innerHtml
I assume that it is not a bug but rather Angular's security measure against XSS attacks - for more information I would suggest taking a look here https://angular.io/guide/security#sanitization-example
I somewhat also fail to understand why you insist on passing the event via string literal instead of just simply using:
<div>
<a (click)="delete(idUser)">${this.idUser}</a>
</div>
Your component has inner Html.
Angular will not allow events inside inner Html portions for security reasons. You can use Child components. to make events from inside of inner Html portions. Create a child component and put your html inside the child component and pass the data by using any angular events between parent and child using Input, Output features in Angular
I don't often use [innerHTML], but it looks like the template string you're using <a (click)="delete(idUser)">${idUser}</a> is referencing ${idUser} when you might have meant ${this.idUser}?
Below code snippet worked for me:-
In component :
ngAfterViewChecked () {
if (this.elementRef.nativeElement.querySelector('ID or Class of the Html element')) {
this.elementRef.nativeElement.querySelector('ID or Class of the Html element').addEventListener('click', this.editToken.bind(this));
}
}
inside constructor parameter:-
constructor( private readonly elementRef: ElementRef) {}
import { ElementRef } from '#angular/core';---> at the top of the file
implement 'AfterViewChecked'
The manual solutions for Auto Reloading the HTML page of a specific component:
Either by navigating to the HTML page on click.
Or calling the ngOnInit() of that component on click.
I am doing it manually using a click event from the HTML code as follows:
HTML Code: app.component.html
<button (click) = reloadPage()>
TS Code: app.component.ts
reloadPage() {
// Solution 1:
this.router.navigate('localhost:4200/new');
// Solution 2:
this.ngOnInit();
}
But I need to achieve this automatically. I hope I am clear. The page should auto-reload after some specific interval and call the ngOnInit() on each interval.
Add correct call to setInterval anywhere in your call:
setInterval(() => reloadPage(), 150000); and inside the method reloadPage put the same logic you have for the button.
An example:
Just put the reloadPage function call inside the constructor:
export class SomeComponent {
constructor() {
setInterval(() => this.reloadPage(), 150000);
}
reloadPage() {
// anything your button doeas
}
}
also note, that correct call of setInterval would be:
setInterval(() => this.reloadPage(), 150000);
Note: My answer just fixes the code you presented. But it seems there is some bigger logical misunderstanding of "reloading page" in angular and using ngOnInit
I would like to keep web page in memory so that when I click on back button (not the one on web browser) or on a routerlink, the HTML page instantly loads if I already visit it(because I have some data to load that I don't want to be reload).
I've seen a method with tabbed interface : https://www.w3.org/Style/Examples/007/target.en.html#tab1
but it is not adapted for my code architecture.
I'm using routerlinkin angular2 to navigate threw pages and a back button calling a function on click to go to the previous page.
I try to detail as far as I can so people can understand better my code architecture and the way routerlink method works.
Back button function (works independently from routerlink) :
goBack() {
window.history.back();
}
The router link method from page 1 to page 2:
page1.html :
<a[routerLink]="['PAGE2']"> go to page 2</a>
page1.ts component:
import { Router, ROUTER_DIRECTIVES } from '#angular/router-deprecated';
#Component({
selector: 'page1',
templateUrl: 'page1.html',
styleUrls: ['page1.css'],
directives: [ROUTER_DIRECTIVES]
})
main.ts :
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '#angular/router-deprecated';
#Component({
providers: [ROUTER_PROVIDERS]
})
#RouteConfig([
{ path: '/page2', name: 'PAGE2', component: Page2}]) //component representing class Page2 in page2.ts
Any idea to help me manage it is welcomed, thanks by advance !
Just cache the data in the service like explained in What is the correct way to share the result of an Angular 2 Http network call in RxJs 5?
There is currently no way to prevent the router from re-creating the component when you route away and back to the component.
Well, for those having the same problem, I think the best way to manage it is to map the data into a localStorage key like this :
localStorage.setItem('favoris',JSON.stringify(my_array)); //set my data array
array = JSON.parse(localStorage.getItem('key_name')); //get in array
And then ngOnInitin the class called by the router will call the initial function depending of localStorage key being true or not.