Refresh the parent component view in Angular 4 - javascript

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

Related

Angular - recalculate a variable on every change

I have a variable that stores the available cars at any moment. Is there a way to automatically re-evaluate this function on every change?
Just using this.carFactory.available in this case is not a solution, because this example I'm showing is simplified - the real calculation in my project is alot more complex.
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
How could I do this in Angular 2? In Angular JS there was the possibility to $watch a function.
I could of course manually call this function everytime something changes, but it would be nice not to have to call this function in every part of the application that can change the data.
Using template function reference with auto change detection
You can use this function output on template:
carOutput(): cars[] {
this.calculateAvailableCars()
return this.availableCars;
}
and use output on template:
<p>My car ratio is {{ carOutput() }} </p>
However this will trigger very aggressive change detection strategy on this variable. This solution is the simpliest one, but from engineering perspective rather worst: consumes tons of unnecessary function calls. One note, that hosting element must not be set to detect changes onPush.
Separate data model to parent component and pass as property to child
You can store car list display in separate component, and pass new car array as input property to this component:
<car-display [cars]="availableCars"></car-display>
Then you can set changeDetetcion policy in this component to onPush, and each time input property bind to availableCars will change, <car-display> will re-render.
If update relays on some host binding
If some external host action is triggering new cars calculation, then hostBinding may help:
#hostListener(`hover`) recalculateCars() {
this.calculateAvailableCars()
}
And finally, (because you describe your use case quite cryptically, without many details, thus I'm scratching all possible scenarios) if some external component action shall trigger re-calculation, you can hook to ngLifecycle ngOnChanges() if for example external input property change shall re-trigger cars calculation.
In other words and summing all that up, it depends who and from where triggers changes, that shall re-trigger available cars recalculation.
And very important, see an answer from #chiril.sarajiu, because what we are trying to work around here can be handled automatically by single observable. This requires additional setup (service, provide observable to components, e.c.t.) but it's worth.
--- EDIT ---
If each variable change shall retrigger data
As OP clarified, that changes are related with model bound to component. So another option with mentioned by #marvstar is using set, where each model variable change will retrigger fetching function:
modelSchangeSubject: Subject<Model> = new Subject<Model>();
ngOnInitt() {
this.modelSchangeSubject
.subscribe((v: Model) => {
this.calculateAvailableCars()
})
}
/* Rest of controller code */
set modelBounded(v: Model) {
this.modelSchangeSubject.next(v);
}
You need RxJS. What you do is you create a data service, which will store an Observable (in my case a BehaviorSubject, which is mostly the same, but in my case I start with a value).
export class DataService {
private dataStorage$ = new BehaviorSubject(null); //here is the data you start with
get getDataStorage() {
return this.dataStorage$.asObservable(); // so you won't be able to change it outside the service
}
set setDataStorage(data: any) {
this.dataStorage$.next(data);
}
}
Then you subscribe to this data changes everywhere you need to:
constructor(private dataService: DataService){}
ngOnInit() {
this.dataService.getDataStorage.subscribe((data) => this.calculateAvailableCars(data));
}
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
Read more about best practices of using RxJS in Angular, as there can be quite a bit of pitfalls and problems.
Try using setter and getter.
private _YourVariable:any;
public set YourVariable(value:any){
this._YourVariable = value;
//do your logik stuff here like. calculateAvailableCars
}
public get YourVariable():any{
return this._YourVariable ;
}

Reading OData contexts in onInit of controller

I've tried to prepare data from an OData source to show it in a bar graph in my fiori app. For this, I setup the OData model in the manifest.json. A test with a list, simply using
items="{path : 'modelname>/dataset'}
works fine and shows the content.
To prepare data for a diagram (VizFrame), I used the onInit() function in the controller of the view (mvc:XMLView). The data preparation is similar to the one discussed in question.
At first I obtain the ODataModel:
var oODataModel = this.getOwnerComponent().getModel("modelname");
Next I do the binding:
var oBindings = oODataModel.bindList("/dataset");
Unfortunately, the oBindings().getContexts() array is always empty, and also oBindings.getLength() is zero. As a consequence, the VizFrame shows only "No Data".
May it be that the data model is not fully loaded during the onInit() function, or do I misunderstand the way to access data?
Thanks in advance
Update
I temporary solved the problem by using the automatically created bind from the view displaying the data as list. I grep the "dataReceived" event from the binding getView().byId("myList").getBindings("items") and do my calculation there. The model for the diagram (since it is used in a different view) is created in the Component.js, and registered in the Core sap.ui.getCore().setModel("graphModel").
I think this solution is dirty, because the graph data depends on the list data from a different view, which causes problems, e.g. when you use a growing list (because the data in the binding gets updated and a different range is selected from the odata model).
Any suggestions, how I can get the odata model entries without depending on a different list?
The following image outlines the lifecycle of your UI5 application.
Important are the steps which are highlighted with a red circle. Basically, in your onInit you don't have full access to your model via this.getView().getModel().
That's probably why you tried using this.getOwnerComponent().getModel(). This gives you access to the model, but it's not bound to the view yet so you don't get any contexts.
Similarly metadataLoaded() returns a Promise that is fullfilled a little too early: Right after the metadata has been loaded, which might be before any view binding has been done.
What I usually do is
use onBeforeRendering
This is the lifecycle hook that gets called right after onInit. The view and its models exist, but they are not yet shown to the user. Good possibility to do stuff with your model.
use onRouteMatched
This is not really a lifecycle hook but an event handler which can be bound to the router object of your app. Since you define the event handler in your onInit it will be called later (but not too late) and you can then do your desired stuff. This obviously works only if you've set up routing.
You'll have to wait until the models metadata has been loaded. Try this:
onInit: function() {
var oBindings;
var oODataModel = this.getComponent().getModel("modelname");
oODataModel.metadataLoaded().then(function() {
oBindings = oODataModel.bindList("/dataset");
}.bind(this));
},
May it be that the data model is not fully loaded during the onInit()
function, or do I misunderstand the way to access data?
You could test if your model is fully loaded by console log it before you do the list binding
console.log(oODataModel);
var oBindings = oODataModel.bindList("/dataset");
If your model contains no data, then that's the problem.
My basic misunderstanding was to force the use of the bindings. This seems to work only with UI elements, which organize the data handling. I switched to
oODataModel.read("/dataset", {success: function(oEvent) {
// do all my calculations on the oEvent.results array
// write result into graphModel
}
});
This whole calculation is in a function attached to the requestSent event of the graphModel, which is set as model for the VizFrame in the onBeforeRendering part of the view/controller.

Go to parent route in Angular 2

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.

Angular 2 - Rerun constructor once revisit the page

I would like to know how can I refresh data once revisit the Page.
Here is my github project for example: https://github.com/eshk12/Parabi/tree/Medicine-module-complete-add/edit/del/view/list
you can see there is A page called Medicine and there is an option to remove/edit Medicine's value (slide left).
When you have succsfully edited the data and return the Page Medicine the data is still the same because the variable is not reset.
If I want to view the current state of the data i have to refresh the page, that makes the constructor rerun.
There is a way/idea that can I do it?
Thanks A Head!
You should use the angular2/ionic2 life cycle hooks.
A constructor is executed when the class is created but has no relation to the actual view.
For ionic2 use:
ionViewDidLoad() {
console.log('Hi');
}
For angular2 use:
ngOnInit() {
console.log('Hi');
}
You can look the whole list here https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
You can use Observables and poll the data every 2 seconds on the service.
the observable will update every time the data is changed.
I have find a function that called every time there is enter to the page:
ionViewWillEnter(){
// your code goes here.
}
Thanks!

How to redirect to different controller?

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.

Categories