I wonder if there is a way to execute something after i navigate to a different "view" using angular router.
this.router.navigate(["/search", "1", ""]);
// Everything after navigate does not not get executed.
this.sideFiltersService.discoverFilter(category);
this.router.navigate returns a promise so you can simply use:
this.router.navigate(["/search", "1", ""]).then(()=>{
// do whatever you need after navigation succeeds
});
// In javascript
this.router.navigate(["/search", "1", ""])
.then(succeeded => {
if(succeeded)
{
// Do your stuff
}
else
{
// Do some other stuff
}
})
.catch(error => {
// Handle the error
});
// In typescript you can use the javascript example as well.
// But you can also do:
try
{
let succeeded = await this.router.navigate(["/search", "1", ""]);
if(succeeded)
{
// Do your stuff
}
else
{
// Do some other stuff
}
}
catch(error)
{
// Handle the error
}
Not entirely sure of the context but an option would be to subscribe to a change in the URL using ActivatedRoute
https://angular.io/docs/ts/latest/guide/router.html#!#activated-route
Here's an example:
...
import { ActivatedRoute } from '#angular/router';
...
private _routerSubscription: any;
// Some class or service
constructor(private _route: ActivatedRoute){
this._routerSubscription = this._route.url.subscribe(url => {
// Your action/function will go here
});
}
There are many other observables you can subscribe to in ActivatedRoute which are listed in that link if url isn't quite what you need.
The subscription can be done in the constructor() or in an ngOnInit() depending on what suits you best, just remember to clean up after yourself and unsubscribe in an ngOnDestroy() :)
this._routerSubscription.unsubscribe();
If you are navigated from ComponentA to ComponentB then after navigating you can do any actions in ngOnInit() function of ComponentB, depending upon the parameters passed in the route.
You also have to ensure that there are no ongoing subscriptions... I faced the same problem and in my case there was a subscription which changed route. So the route has been changed twice. But practically you can use promises, thats right
Related
I have a function to get rates from products, so lets say I have one product with two rates. So my product has two rates. Then, when I get those rates I must get the prices attached to my product. So for each rate I have to look for its prices.
The next code below explains this:
this.loadProductInfo = true; // bool to load data in my form
// First of all, I get rates from API
// const rates = this._http....
// Now, for each rate I must search If my product/products have a price:
this.rates.forEach((rate, index, arr) => {
this._glbGetPricesForProduct.getPrice(params).subscribe(response => {
if (!arr[index + 1]) {
this.initForm();
this.loadProductInfo = false;
}
})
});
The variable loadProductInfo it loads content in my form, so in my html I have:
<form *ngIf="!loadProductInfo"></form>
But form it still give me error: could not find control name.
But if I do this instead, it works correctlly:
setTimeout(() => {
this.initForm();
this.loadProductInfo = false;
}, 2000);
So what I want its to say my form to wait until I have all code loaded and then after it, load its contents. But instead it cant find the control because it loads before code. Any help I really appreciate it.
The main mistake I see there is that you are looping over async data which may not be there when your code execute the for each loop (your rates).
I would build an observable with your rates as a source:
...
$rates: Observable<any> = this._http.get(...);
rates.pipe(
mergeMap((rates) => {
const priceByRates: Observable<any>[] = rates.map((rate, index, arr) => this._glbGetPricesForProduct.getPrice(params));
return combineLatest(pricesByRates); // if getPrice complete right away, use forkJoin() instead
})
).subscribe(res => {
// No need to check for the last item, all rates have been checked for possible price
this.initForm();
this.loadProductInfo = false;
});
...
This implementation should wait for your api calls to resolve before printing your form.
Since you are hiding the entire form, it may be better to just move the API call into a resolver so that the page does not render until the data is ready.
Here is a minimal StackBlitz showcasing this behavior: https://stackblitz.com/edit/angular-ivy-4beuww
Component
In your component, include an ActivatedRoute parameter via DI.
#Component(/*omitted for brevity*/)
export class MyComponent {
constructor(private route: ActivatedRoute) {
// note: 'data' is whatever you label your resolver prop in your routing setup
route.data.subscribe(resolved => {
if ("data" in resolved) this.resolveData = resolved["data"];
});
}
}
Route Setup
And in your router setup you would have the following:
const routes: Routes = [
{
path: 'my-route-path',
component: MyComponent,
resolve: {
data: MyResolver
}
}
];
Resolver
Finally, your resolver would make your API call utilizing your service:
#Injectable({providedIn: 'root'})
export class MyResolver() implements Resolve<T> {
constructor(private service: MyService) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | any {
return this.service.myRequest();
}
}
The final result will be that your view will not be rendered until your data is ready.
My code has been refactored and some extracted into a service that subscribes to functions. However, my original code had a call within the subscription that referenced a variable within the file, but now I'm not sure how to best reach it?
I am struggling with where to place the line:
this.select.reset('some string'); found within the subscribeToMessageService() function.
Original code
event.component.ts
select: FormControl;
#ViewChild('mySelect') mySelect: ElementRef;
subscribeToMessageService() {
this.messageService.serviceMsg
.subscribe(res => {
// unrelated code
this.select.reset('some string');
});
}
subscribeToEventService() {
this.eventService.eventSubject
.subscribe(res => {
this.select = new FormControl(res.status);
this.select.valueChanges.subscribe(value => {
// manual blurring required to stop error being thrown when popup appears
this.selector.nativeElement.blur();
// do something else
});
});
}
Refactored code
status.service.ts
subscribeToMessageService(): void {
this.messageService.serviceMsg
.subscribe(res => {
// unrelated code
// This is where 'this.select.reset('some string');' would have gone
});
}
status.component.ts
select: FormControl;
#ViewChild('exceptionalSelect') selector: ElementRef;
subscribeToEventService() {
this.eventService.eventSubject
.subscribe(res => {
this.select = new FormControl(res.status);
this.select.valueChanges.subscribe(value => {
// manual blurring required to stop error being thrown when popup appears
this.selector.nativeElement.blur();
this.onStatusChange(value);
});
});
}
Since you still want to subscribe to the original source messageService.serviceMsg your new StatusService needs to expose this observable to the injecting component (StatusComponent).
This can be done for example by creating a public observable in the StatusService (possibly by utilising rxjs Subject or angular EventEmitter) and triggering the emit in the subscription of messageService.serviceMsg.
Then your StatusComponent only needs to inject StatusService and do
this.statusService.serviceMsg // <-- might choose another name to make clear that this is passed on.
.subscribe(res => {
// unrelated code
this.select.reset('some string');
});
Similar question to Angular2 Get router params outside of router-outlet but targeting the release version of Angular 2 (so version 3.0.0 of the router). I have an app with a list of contacts and a router outlet to either display or edit the selected contact. I want to make sure the proper contact is selected at any point (including on page load), so I would like to be able to read the "id" param from the route whenever the route is changed.
I can get my hands on routing events by subscribing to the router's events property, but the Event object just gives me access to the raw url, not a parsed version of it. I can parse that using the router's parseUrl method, but the format of this isn't particularly helpful and would be rather brittle, so I'd rather not use it. I've also looked all though the router's routerState property in the routing events, but params is always an empty object in the snapshot.
Is there an actual straight forward way to do this that I've just missed? Would I have to wrap the contact list in a router-outlet that never changes to get this to work, or something like that?
If any body was looking for the latest solution of this issue (angular 8) I stumbled upon this article which worked very well for me.
https://medium.com/#eng.ohadb/how-to-get-route-path-parameters-in-an-angular-service-1965afe1470e
Obviously you can do the same implementation straight in a component outside the router outlet and it should still work.
export class MyParamsAwareService {
constructor(private router: Router) {
this.router.events
.pipe(
filter(e => (e instanceof ActivationEnd) && (Object.keys(e.snapshot.params).length > 0)),
map(e => e instanceof ActivationEnd ? e.snapshot.params : {})
)
.subscribe(params => {
console.log(params);
// Do whatever you want here!!!!
});
}
In the hope to spare the same struggle I went through.
I've been struggling with this issue for the whole day, but I think I finally figured out a way on how to do this by listening to one of the router event in particular. Be prepared, it's a little bit tricky (ugly ?), but as of today it's working, at least with the latest version of Angular (4.x) and Angular Router (4.x). This piece of code might not be working in the future if they change something.
Basically, I found a way to get the path of the route, and then to rebuild a custom parameters map by myself.
So here it is:
import { Component, OnInit } from '#angular/core';
import { Router, RoutesRecognized } from '#angular/router';
#Component({
selector: 'outside-router-outlet',
templateUrl: './outside-router-outlet.component.html',
styleUrls: ['./outside-router-outlet.component.css']
})
export class OutSideRouterOutletComponent implements OnInit {
path: string;
routeParams: any = {};
constructor(private router: Router) { }
ngOnInit() {
this.router.events.subscribe(routerEvent => {
if (routerEvent instanceof RoutesRecognized) {
this.path = routerEvent.state.root['_routerState']['_root'].children[0].value['_routeConfig'].path;
this.buildRouteParams(routerEvent);
}
});
}
buildRouteParams(routesRecognized: RoutesRecognized) {
let paramsKey = {};
let splittedPath = this.path.split('/');
splittedPath.forEach((value: string, idx: number, arr: Array<string>) => {
// Checking if the chunk is starting with ':', if yes, we suppose it's a parameter
if (value.indexOf(':') === 0) {
// Attributing each parameters at the index where they were found in the path
paramsKey[idx] = value;
}
});
this.routeParams = {};
let splittedUrl = routesRecognized.url.split('/');
/**
* Removing empty chunks from the url,
* because we're splitting the string with '/', and the url starts with a '/')
*/
splittedUrl = splittedUrl.filter(n => n !== "");
for (let idx in paramsKey) {
this.routeParams[paramsKey[idx]] = splittedUrl[idx];
}
// So here you now have an object with your parameters and their values
console.log(this.routeParams);
}
}
I'm developing a web site using Angular 2.
Is there any way to disable or trigger Browser back button using Angular 2?
Thanks
Not sure if this is already sorted, but posting the answer nonetheless, for future references.
To tackle this, you basically need to add a listener in your app-component and setup a canDeactivate guard on your angular-router.
// in app.component.ts
import { LocationStrategy } from '#angular/common';
#Component({
selector: 'app-root'
})
export class AppComponent {
constructor(
private location: LocationStrategy
) {
// check if back or forward button is pressed.
this.location.onPopState(() => {
// set isBackButtonClicked to true.
this.someNavigationService.setBackClicked(true);
return false;
});
}
}
// in navigation guard
#Injectable()
export class NavigationGuard implements CanDeactivate<any> {
constructor(private someNavigationService: SomeNavigationService) {}
canDeactivate(component: any) {
// will prevent user from going back
if (this.someNavigationService.getBackClicked()) {
this.someNavigationService.setBackClicked(false);
// push current state again to prevent further attempts.
history.pushState(null, null, location.href);
return false;
}
return true;
}
}
import { LocationStrategy } from '#angular/common';
constructor( private location: LocationStrategy){
// preventing back button in browser implemented by "Samba Siva"
history.pushState(null, null, window.location.href);
this.location.onPopState(() => {
history.pushState(null, null, window.location.href);
});
}
its working fine to me 100% in angular2/4/5
This Very simple, use the following code, This example code is from plain javascript i have converted this into angular and using in my 2-3 projects
// Inject LocationStrategy Service into your component
constructor(
private locationStrategy: LocationStrategy
) { }
// Define a function to handle back button and use anywhere
preventBackButton() {
history.pushState(null, null, location.href);
this.locationStrategy.onPopState(() => {
history.pushState(null, null, location.href);
})
}
You can define preventBackButton in any service as well and call it from there
Snippet that I use and works across all major browsers!
ngOnInit() {
history.pushState(null, null, location.href);
this.subscription = fromEvent(window, 'popstate').subscribe(_ => {
history.pushState(null, null, location.href);
this.openModal(`You can't make changes or go back at this time.`, 'Okay');
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
I've tried all the solutions mentioned above but none of them worked perfectly for me. Finally I've found this npm module that worked immediately and perfectly, after two days of failed attempts.
Github: https://github.com/Zatikyan/angular-disable-browser-back-button#readme
A bit late perhaps but maybe somebody can use it.
This is a solution I use for a page with tabs (Bootstrap 4 style) where each tab is a component.
#Injectable()
export class CanNavigateService {
private static _isPermissionGranted = true
public navigationAttempt = new Subject<boolean>()
//-------------------------------------------------------------//
/**Will the next navigation attempt be permitted? */
updatePermission(isPermissionGranted: boolean) {
CanNavigateService._isPermissionGranted = isPermissionGranted
}//updatePermission
//-------------------------------------------------------------//
/**Broadcast the last attempt and whether it was permitted */
updateNavigationAttempt(wasPermissionGranted: boolean) {
this.navigationAttempt.next(wasPermissionGranted)
}//updatePermission
//-------------------------------------------------------------//
/**Can we navigate? */
public isPermissionGranted(): boolean {
return CanNavigateService._isPermissionGranted
}//isPermissionGranted
}//Cls
NavigationGuard like #Jithin Nair above but also broadcasts when an attempt to navigate was made and whether it was permitted. Subscribers of CanNavigateService can use it to decide what to do instead of back navigation.
#Injectable()
export class NavigationGuard implements CanDeactivate<any> {
constructor(private canNavigateService: CanNavigateService) { }
//--------------------------------------------------------------------//
// will prevent user from going back if permission has not been granted
canDeactivate(component: any) {
let permitted = this.canNavigateService.isPermissionGranted()
this.canNavigateService.updateNavigationAttempt(permitted)
if (!permitted) {
// push current state again to prevent further attempts.
history.pushState(null, null, location.href)
return false
}
return true
}//canDeactivate
}//Cls
Usage:
constructor(private _navigateService: CanNavigateService) {
super()
_navigateService.navigationAttempt.subscribe(wasPermitted => {
//If navigation was prevented then just go to first tab
if (!wasPermitted)
this.onTabSelected( this._firstTab)
})
}//ctor
//----------------------------------------------------------------------------//
onTabSelected(tab) {
this._selectedTab = tab
//If it's not the first tab you can't back navigate
this._navigateService.updatePermission(this._selectedTab == this._firstTab)
}//onTabSelected
try to use this
window.onpopstate = function (e) { window.history.forward(1); }
Try this
<script type = "text/javascript" >
history.pushState(null, null, 'pagename');
window.addEventListener('popstate', function(event) {
history.pushState(null, null, 'pagename');
});
</script>
where change 'pagename' to your page name and put this into head section of page.
If you want to prevent a route to be reached you can add the #CanActivate() decorator to your routing component
#Component({selector: 'control-panel-cmp', template: `<div>Settings: ...</div>`})
#CanActivate(checkIfWeHavePermission)
class ControlPanelCmp {
}
See also
- Angular 2: Inject a dependency into #CanActivate? for access to global services.
- Angular2 Router - Anyone know how to use canActivate in app.ts so that I can redirect to home page if not logged in
Why not use just this. Should avoid browser insert automatically things in the history. Just insert in some main.ts (or elsewhere executed at startup)
history.pushState = () => {};
Object.freeze(history);
This issue occurs on IE browser. Use below mentioned code it will resolve your issue.
#HostListener('document:keydown', ['$event'])
onKeyDown(evt: KeyboardEvent) {
if (
evt.keyCode === 8 || evt.which === 8
) {
let doPrevent = true;
const types =['text','password','file','search','email','number','date','color','datetime','datetime-local','month','range','search','tel','time','url','week'];
const target = (<HTMLInputElement>evt.target);
const disabled = target.disabled || (<HTMLInputElement>event.target).readOnly;
if (!disabled) {
if (target.isContentEditable) {
doPrevent = false;
} else if (target.nodeName === 'INPUT') {
let type = target.type;
if (type) {
type = type.toLowerCase();
}
if (types.indexOf(type) > -1) {
doPrevent = false;
}
} else if (target.nodeName === 'TEXTAREA') {
doPrevent = false;
}
}
if (doPrevent) {
evt.preventDefault();
return false;
}
}
}
If you are looking to disable browser back button in angular(7/8/9/10)... Try this link and install package using npm.
1) npm install --save angular-disable-browser-back-button
2) import { NgModule } from '#angular/core';
import { BackButtonDisableModule } from 'angular-disable-browser-back-button';
#NgModule({
...
imports: [
...
BackButtonDisableModule.forRoot()
],
...
})
export class AppModule {}
3) BackButtonDisableModule.forRoot({
preserveScrollPosition: true
})
Please use this link given below.. reference taken from.
[https://www.npmjs.com/package/angular-disable-browser-back-button][1]
This isn't Angular2 related problem. You can send the user back in history. See Manipulating the browser history, history.go() method particular:
window.history.go(-1);
However, I don't think there's a way to cancel or disable default browser action on pressing back button in the browser window because that could be very easily abused.
As an alternative you can show a dialog window when user tries to leave the page: javascript before leaving the page
Add following code in TS file of the component, where you don't want to go back.
#HostListener('window:hashchange', ['$event'])
hashChangeHandler(e) {
window.location.hash = "dontgoback";
}
step 1: Import Locatoion from angular commmon
import {Location} from "#angular/common";
step 2: Initialise in constructor
private location: Location
step 3: Add function in ngOnInit of the respective coponent,
this.location.subscribe(currentLocation => {
if (currentLocation.url === '*/basic-info*') {
window.onpopstate = function (event) {
history.go(1);
}
}
});
Note: Here /basic-info will be replaced by your path.
If first time it is not working, try adding outside subscribe,
let currentUrl = window.location.href;
let tmpVar = currentUrl.includes('/basic-info');
if (currentUrl.includes('/basic-info')) {
window.onpopstate = function (event) {
history.go(1);
}
}
I tried passing parameters in push method of ionic2. like this
this.nav.push(SecondPage, {
thing1: data1,
thing2: data2
});
but is there any way to pass parameter in pop().
This is how I achieved it in ionic-3 and find it easier.
Page from where we pop()
this.navCtrl.getPrevious().data.thing1 =data1;
this.navCtrl.getPrevious().data.thing2 =data2;
this.navCtrl.pop();
Page after pop():
public ionViewWillEnter() {
this.thing1 = this.navParams.get('thing1')|| null;
this.thing2 = this.navParams.get('thing2')|| null;
}
I suggest you use Events. All you have to do is to subscribe to an event on the parent page and then publish the event on the child passing the data you want:
// taken from the docs
import { Events } from 'ionic-angular';
constructor(public events: Events) {}
// first page (publish an event when a user is created)
function createUser(user) {
console.log('User created!')
events.publish('user:created', user);
}
// second page (listen for the user created event)
events.subscribe('user:created', (userEventData) => {
// userEventData is an array of parameters, so grab our first and only arg
console.log('Welcome', userEventData[0]);
});
Currently, I believe that there is no way of accomplishing this.
There is a Github issue for it though, that has got some great discussion on it by the Ionic core team. It sounds like they have added it to the Ionic 2 roadmap, too! The Github issue also has some proposed work-arounds, such as adding the ParentPage to the NavParams going to the ChildPage, but it is all quite a bit hacky.
UPDATE: IT WAS SUPPOSED TO WORK, BUT IT DOES NOT
Seems like there is |See Doc Reference|
pop(opts) takes one parameter of type object
so
to go one step back
this.nav.pop({
thing1: data1,
thing2: data2
});
and to go to a specific view in the history stack
this.nav.popTo(SecondPage, {
thing1: data1,
thing2: data2
});
and to go to root of the stack
this.nav.popToRoot({
thing1: data1,
thing2: data2
});
To retrieve the params (I guess this should work. untested!)
export class SecondPage{
constructor(params: NavParams){
this.params = params;
console.log(this.params.get('thing1'));
}
}
Use popTo() instead of pop()
popTo() has params? parameter where you can pass in your paramaters like so:
this.nav.popTo(SecondPage, {
thing1: data1,
thing2: data2
});
pass in a callback when transitioning by aaronksaunders in this forum
https://forum.ionicframework.com/t/solved-ionic2-navcontroller-pop-with-params/58104/4
Going to try it out.
For sent data with pop you can use getPrevious() method.
When leaving from current page get previous page and send data
ionViewWillLeave() {
this.navCtrl.getPrevious().data.formData = {credentials: {...}};
}
In next page get data from navParams
ionViewWillEnter() {
if (this.navParams.get('formData')) {
// do something
}
}
If you are using ionic-angular application, you can use ionic-angular Events
page1.ts
import { Events,NavController } from 'ionic-angular';
export class page1 {
constructor(private events: Events,
private nvCtrl: NavController
) {}
goToPage2() {
this.navCtrl.pop();
this.event.publish('your-event');
}
}
page2.ts
import { Events} from 'ionic-angular';
export class page1 {
constructor(private events: Events,
private nvCtrl: NavController
) {}
ionViewDidLoad() {
this.events.subscribe('your-event');
}
}