Let's say I have a component that creates or edits a thing.
The url for the creation is /things/create and the url for edition is /things/edit/4. These routes have the same parent.
Once I fill in the form, I call some webservice and then I go back to my previous state which was /things.
How do I tell angular to go back to the parent route?
I could use this.router.navigate(['../'], {relativeTo : this.route}); but ['../'] would only work for /things/create. For /things/edit/4 it would go back to /thing/edit which doesn't exist.
This component is used in two different places, one of which has an extra step before the creation so I can't use back().
Logic based on the current url
if(router.url.indexOf('create')>= 0){
this.router.navigate(['../'], {relativeTo : this.route});
}else {
this.router.navigate(['../../'], {relativeTo : this.route});
}
so if your current URL path contains the create you know you just have to go back one level, otherwise you go back two levels for the edit.
Use Input to get base URL
Another approach might be to have an #Input for the base URL to go back to. So each component that initializes your component has to pass it the parent url.
{
#Input
parentUrl;
this.router.navigate[parentUrl];
}
You can use
this.router.navigate['/things']
So you should be able to check for the id of the thing in the ActivatedRoute params to get the context if the component is creating or editing a component. If there is a param['thingId'] then go to ../../ otherwise go to ../
May be this idea a bit old school but I am sure it will work and is quite simplistic to implement. You can add query param called 'source' and navigate to 'source once the operation is done.
Related
I have got 2 components, let's say, Component A is a list view and Component B is a details view. Each row from the list view is clickable and will redirect to Component B upon clicking.
Component B allows editing and saving the details. I have added a Back button to Component B to allow me to go back to the list view.
But the problem I am having is that I can't see the updated list view and have to manually refresh the browser, and then I can see the updated list there.
I have tried directly using window.location and it works but really I don't prefer this approach.
public back() {
window.location.assign('/listview');
}
I wonder if there's any better way to solve this problem?
Update:
public onSelected(model: MyModel) {
const detailsViewUrl = `/detailsview/${model.id}`;
this._router.navigateByUrl(detailsViewUrl );
}
You can just emit an #Output EventEmitter with a method on Parent that looks in the event for a change with a variable stored in the component like this:
#Output someOutput: EventEmitter = new Event Emitter<any>;
HTML:
<b-component (someOutput)=getOutput($event)></b-component>
AComponent:
getOut(event){
let output = event;
if(this.something != output){
this.ngOnDestroy(); // or something that you can use to make it
}
That should work as intended.
It sounds like this is an issue with Angular's change detection when changing the contents of an array. See here:
Angular 2: How to detect changes in an array? (#input property)
The solutions in this questions should work but an easy way I have used in the past to force changes in an array to be recognised by Angular is to reassign the array after making the changes:
myArray = [...myArray];
use following routing fuction on back button click
public back() {
this._router.navigateByUrl('/listview')
}
or
public back() {
this._router.navigate('/listview')
}
Try this,
Just called the list view again internally and hit db at same time so updated values will be displayed in the list view.
calling the route by using below:
this.router.navigate(['/listview']);
Seems like a change detection issue, there are some ways to manually trigger change detection like so:
Inject ChangeDetectorRef.
Call it when you go back like so:
public back() {
ChangeDetectorRef.detectChanges()
}
Refer to this: Triggering change detection manually in Angular
I'm currently facing a problem with Meteor and React, where i know some partly solutions but they don't work and imo none of them is pointing in the true direction.
The situation:
All is about an fitness app: I have a structure that represents exercises for customers, while each exercise can have a defined number of sets (a set is how often a exercise should be done). Each set has some properties (all the user can manipulate within the font-end).
Now i have the following component structure with some map-functions (state properties are in {}):
Training {customers,exercises,datetime,otherinfos}
- Overview {customers,exercises}
exercises.map():
- Exercise {exercise,customers}
customers.map():
- Customer {exercise,customer}
exercise.sets.map()
Set {exercise, customer, set, valuesofset}
From a UI-perspective (react) this all works without problems.
Now the idea is to have a button "Save" within the Training component. When the button is pressed, I want to save the state of all Set-Components in a "sets" collection (if it has other values than the default placeholder ones) and at the same time save the Training-Component in a "trainings" collection. But the training should also include information about what Sets are integrated (so at least the Set._id should be in the Training-Component state at time of Saving.
Here now my ideas so far:
Create refs from Training all the way down to all Sets and then, when pressing "Save" iterate over all refs and call a "Mongo.insert" from all Sets. Here i have the problem that i cannot return the inserted _id. Of course i could call a different function in each Component from Set all the way back to Training, but imo this is an overflow.
Try to manage the state of all sets within the Training state by calling a nested function. As i have onChangeHandler on the Inputs, this would always call a method in Training and check which one of the Sets was changed and then changes it. I have tried it this way, but it led to a very bad performance.
Create a temp-ID for Training, forward it to to the Sets (using the componentWillReceiveProps method) and when in Set, insert the Set in the database with the temp-ID. Then receive all Sets with temp-ID and use it to add the Training in the database. --> imo very complicated and I don't really want to do a database call if it is not necessary.
So currently i don't know how to solve this problem. The reason i try to separate "sets" and "trainings" is given through the fact, that later on i would like to give information about the last Set right next to the new empty Set whenever one is on the database. Any tips are welcome!
EDIT:
As suggested, there is also the possibility to solve the problem with Session. So therefor i have added the following code to Set:
componentDidMount() {
Tracker.autorun(() => {
Session.set(`set_${this.state.id}`, {
...this.state
});
});
}
My idea was then to iterate over all Session-Keys from Training which start with "set_" - unfortunately there is no function to that holds all Keys.
Second idea was to to use an array as value for a Session-pair. However, it's quite a procedure to handle the update of the reactive Set component (copy array from session, check whether an element is available or not, create a new one or update the existing one).
EDIT2:
I think i got a solution with Session:
Object.getOwnPropertyNames(Session.keys)
did the trick to get all SessionKeys! Thank you for your help!
If you do not want to use Redux or pass parent bound callbacks in the child component, you can try Session to store data at app level which can be accessed(set/get) in any component
https://docs.meteor.com/api/session.html
In your case, you may set values of "Set" in Session and access it in Training. You may also need https://guide.meteor.com/react.html#using-withTracker. Using withTracker will help in doing reactive update of the database on change of any Session variable.
Okay, I have two use cases for my question here:
I'm working on an application which has a /en/register route. It all works good when I'm at the root and I click a button that does this.router.navigate([this.routeParams.lang, 'register']); and it's all good, this opens up a modal in the constructor (or ngOnInit, anyways) with ($('#modalRegister') as any).modal('show');.
It all works good, but if I close the modal, the route is still /en/register/ (yeah I can make it go to /en but see the use case #2 before you suggest this), so when I click the button, it doesn't do anything. Neither the constructor or ngOnInit are being called, not even route.params.subscribe() or route.url.subscribe() (which I think they should...).
In the same application, I have a button that does a search and centers some markers in a map (in /en/search/blah). That's all good if I'm in the index page or if I change the search query. However, if the user drags the map somewhere else and wants to have the same markers centered again, I also do this.router.navigate(['search', this.searchQuery]); and if it ends up being the same route (click the search button twice, for instance) it doesn't do anything.
While I agree it's good so the components don't get recreated if the URL hasn't changed, this is a bad design because in UI-router you could do the same thing and it'd work (as far as I can remember).
So, in Angular 4, how do I run the same code in the constructor/ngOnInit of the route's component when the same URL is being told to be navigated to? or how do I detect if the URL is the same and act accordingly? (although I still think it's bad design, but anyway...).
Any advice is greatly appreciated. Thanks!
When I needed to "reload" the current component's constructor and ngOnInit functions,
the only solution I found was kind of a workaround:
I used the fact that "this.router.navigate" returns a promise.
So I navigated somewhere else, and returned. It's a bit ugly but it works and the UI is not affected:
this.router.navigate(..[somewhere else]..)
.then(()=>{this.router.navigate(..back..)})
Hope it helps.
For case 1,
Why do you navigate to a new route just to open a modal. Just do it being at the same route with a function call. If you want to move to a new route then you can again call this.router.navigate("/") in modal close event.
For case 2,
Hope this will work.
currentSearchQuery: string;
ngOnInit() {
this.route.paramMap
.switchMap((params: ParamMap) => {
this.currentSearchQuery = params.get('searchQuery');
updateMarkers();
});
}
i use this workaround
this.router.navigate(['path']).then(()=> {window.location.reload();});
Just add dummy parameter at the end of the route /en/register/jk2h4-42hj-234n2-234dfg or query parameter like /en/register?val=jk2h4-42hj-234n2-234dfg
change the parameter value when calling the same route. So browser knows that URL change and Angualr component start to work from full life cycle.
Fellas, Fellaaas.
I'm writing from my phone so please excuse me for anything misleading or unclear.
In my app there's a state that can be inherited from any other parent state, called "deals/:dealId:".
As of now, I have to define the same state over and over again for any other parent state available, so it could be accessible from any URL on the app.
For example: the parent state "dashboard" has a child state "parent.dashboard.deals", so is another state - "parent.lookup.deals", and so on.
While googling for a solution I found an example on plunkr using a parent state variable definition to create a wildcard'ed state environment:
.state(currentState+".deals").
"Boy oh boy", I thought to myself. That's exactly what I need. Well, not.
I was testing it and it on a static "currentState" variable and it worked just fine, thought all I had left to do was to dynamically change the currentState variable between parent states switchings.
It appears that when angular is generating the routes for the first time on loading, it takes the default "currentState" var as a string and defines that state static. So, even though I'm changing the default "currentState" var between route changes, the state is only available to the first generated state definition.
Thank you.
I've posted a feature request on GitHub, just in case I'm the first one in the need for this scenario - or there isn't a proper solution meanwhile for this problem:
github.com/angular-ui/ui-router/issues/1014
Anyhow,
I managed this problem by looping through the parent states and attaching the static child state to them, instead of defining them manually:
angular.forEach(states, function(stateOptions,stateName) {
$stateProvider.state(stateName, stateOptions.options);
if (stateOptions.defaultState){
var dealStateName = stateName+'.deals';
console.log(dealStateName);
$stateProvider.state(dealStateName,{
url: (stateName=='parent.dashboard') ? "deals/:dealId" : "/deals/:dealId",
views:{
'fsItem#parent':{
templateUrl: "/static/html/fsItems/dealNewD.html",
controller: 'dealCtrl',
}
}
});
}
});
The question isn't quite clear, but it sounds like you want a state variable(s) that persists between route changes. I'd suggest using a factory or service. Take a look at Sharing a Variable Between Controllers in AngularJS.
I have an application in ASP.MVC. The requirement is that I select a person from a list of people and click 'Info' and it should load the details of the person in that page. I have the Info controller and everything works fine if I go to the Info page from a different controller. In the page I am trying to make it work with JavaScript and it doesn't seem to take me to the desired page but to a different controller.
I have a ToDoList controller and in the .cshtml I have this code on click of the Info link.
function DoInfo#(i.ToString())() {
$("#sessionid").val("#Model.cSessionId[i]");
alert("hey");
$("#PageController").val(66);
$("#formID").submit();
}
I go to the ToDoList controller to do the redirection like this
if (viewModel.PageController == 66)
{
pass = new PassingData();
pass.personid = TSSessionService.ReadPersonId(viewModel.SessionId);
TempData["pass"] = pass;
return RedirectToAction("Index", "Info");
}
It never goes there and instead goes to a different controller. I cannot seem to find how they are linked and why is it not going back to controller where the Info link button is i.e. back to the ToDoList controller.
Let me know if it is not clear and I will try to explain again and I will give any other details.
I guess I'm confused as to why you are doing this as a combination of form and JavaScript. Are there other properties that you need to pass along that you are not posting above? Why do you need to use JavaScript to do this if you are just returning a new view?
You indicate in your post that when a person is selected from a list you need to go to a controller and display a view. This seems fairly straightforward, and I would like to suggest simplifying the problem.
Start with this: change your link to not use a form or JavaScript. Just make it a link. If it is text, you can use #Html.ActionLink() and even pass in the parameters you need.
If you're not displaying text, just use #Url.ActionLink() in your href property of the anchor you're wrapping your element with. Both of these allow you to leverage routing to ensure the correct path is being constructed.
If the controller that you are trying to get to has access to whatever TSSessionService is, then you don't need to pass through the TempData["pass"] you are trying to push through, so it makes it cleaner in that way as well.
If you do need to submit a more complicated value set, I would recommend coming up with a generic .click() event handler in jQuery that can respond to any of the clicks, bound by a common class name. You can use a data-val attribute in your link and read from $(this).attr('data-val') in your handler to store/fetch other important info. This allows you to more easily build up an object to POST to a controller.
Hope this helps some, but if I'm missing a critical point then please update the question above.